From 285717de33c25422db60420030d46d10cf3b0121 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Mon, 1 Jun 2020 15:36:51 +0200 Subject: Rename assist --- .../src/handlers/change_lifetime_anon_to_named.rs | 306 --------------------- .../src/handlers/introduce_named_lifetime.rs | 303 ++++++++++++++++++++ crates/ra_assists/src/lib.rs | 4 +- crates/ra_assists/src/tests/generated.rs | 50 ++-- 4 files changed, 330 insertions(+), 333 deletions(-) delete mode 100644 crates/ra_assists/src/handlers/change_lifetime_anon_to_named.rs create mode 100644 crates/ra_assists/src/handlers/introduce_named_lifetime.rs (limited to 'crates') diff --git a/crates/ra_assists/src/handlers/change_lifetime_anon_to_named.rs b/crates/ra_assists/src/handlers/change_lifetime_anon_to_named.rs deleted file mode 100644 index 0fdbc63dd..000000000 --- a/crates/ra_assists/src/handlers/change_lifetime_anon_to_named.rs +++ /dev/null @@ -1,306 +0,0 @@ -use ra_syntax::{ - ast::{self, NameOwner, TypeAscriptionOwner, TypeParamsOwner}, - AstNode, SyntaxKind, TextRange, TextSize, -}; -use rustc_hash::FxHashSet; - -use crate::{assist_context::AssistBuilder, AssistContext, AssistId, Assists}; - -static ASSIST_NAME: &str = "change_lifetime_anon_to_named"; -static ASSIST_LABEL: &str = "Give anonymous lifetime a name"; - -// Assist: change_lifetime_anon_to_named -// -// Change an anonymous lifetime to a named lifetime. -// -// ``` -// impl Cursor<'_<|>> { -// fn node(self) -> &SyntaxNode { -// match self { -// Cursor::Replace(node) | Cursor::Before(node) => node, -// } -// } -// } -// ``` -// -> -// ``` -// impl<'a> Cursor<'a> { -// fn node(self) -> &SyntaxNode { -// match self { -// Cursor::Replace(node) | Cursor::Before(node) => node, -// } -// } -// } -// ``` -// FIXME: How can we handle renaming any one of multiple anonymous lifetimes? -// FIXME: should also add support for the case fun(f: &Foo) -> &<|>Foo -pub(crate) fn change_lifetime_anon_to_named(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { - let lifetime_token = ctx - .find_token_at_offset(SyntaxKind::LIFETIME) - .filter(|lifetime| lifetime.text() == "'_")?; - if let Some(fn_def) = lifetime_token.ancestors().find_map(ast::FnDef::cast) { - generate_fn_def_assist(acc, &fn_def, lifetime_token.text_range()) - } else if let Some(impl_def) = lifetime_token.ancestors().find_map(ast::ImplDef::cast) { - // only allow naming the last anonymous lifetime - lifetime_token.next_token().filter(|tok| tok.kind() == SyntaxKind::R_ANGLE)?; - generate_impl_def_assist(acc, &impl_def, lifetime_token.text_range()) - } else { - None - } -} - -/// Generate the assist for the fn def case -fn generate_fn_def_assist( - acc: &mut Assists, - fn_def: &ast::FnDef, - lifetime_loc: TextRange, -) -> Option<()> { - let param_list: ast::ParamList = fn_def.param_list()?; - let new_lifetime_param = generate_unique_lifetime_param_name(&fn_def.type_param_list())?; - let end_of_fn_ident = fn_def.name()?.ident_token()?.text_range().end(); - let self_param = - // use the self if it's a reference and has no explicit lifetime - param_list.self_param().filter(|p| p.lifetime_token().is_none() && p.amp_token().is_some()); - // compute the location which implicitly has the same lifetime as the anonymous lifetime - let loc_needing_lifetime = if let Some(self_param) = self_param { - // if we have a self reference, use that - Some(self_param.self_token()?.text_range().start()) - } else { - // otherwise, if there's a single reference parameter without a named liftime, use that - let fn_params_without_lifetime: Vec<_> = param_list - .params() - .filter_map(|param| match param.ascribed_type() { - Some(ast::TypeRef::ReferenceType(ascribed_type)) - if ascribed_type.lifetime_token() == None => - { - Some(ascribed_type.amp_token()?.text_range().end()) - } - _ => None, - }) - .collect(); - match fn_params_without_lifetime.len() { - 1 => Some(fn_params_without_lifetime.into_iter().nth(0)?), - 0 => None, - // multiple unnnamed is invalid. assist is not applicable - _ => return None, - } - }; - acc.add(AssistId(ASSIST_NAME), ASSIST_LABEL, lifetime_loc, |builder| { - add_lifetime_param(fn_def, builder, end_of_fn_ident, new_lifetime_param); - builder.replace(lifetime_loc, format!("'{}", new_lifetime_param)); - loc_needing_lifetime.map(|loc| builder.insert(loc, format!("'{} ", new_lifetime_param))); - }) -} - -/// Generate the assist for the impl def case -fn generate_impl_def_assist( - acc: &mut Assists, - impl_def: &ast::ImplDef, - lifetime_loc: TextRange, -) -> Option<()> { - let new_lifetime_param = generate_unique_lifetime_param_name(&impl_def.type_param_list())?; - let end_of_impl_kw = impl_def.impl_token()?.text_range().end(); - acc.add(AssistId(ASSIST_NAME), ASSIST_LABEL, lifetime_loc, |builder| { - add_lifetime_param(impl_def, builder, end_of_impl_kw, new_lifetime_param); - builder.replace(lifetime_loc, format!("'{}", new_lifetime_param)); - }) -} - -/// Given a type parameter list, generate a unique lifetime parameter name -/// which is not in the list -fn generate_unique_lifetime_param_name( - existing_type_param_list: &Option, -) -> Option { - match existing_type_param_list { - Some(type_params) => { - let used_lifetime_params: FxHashSet<_> = type_params - .lifetime_params() - .map(|p| p.syntax().text().to_string()[1..].to_owned()) - .collect(); - (b'a'..=b'z').map(char::from).find(|c| !used_lifetime_params.contains(&c.to_string())) - } - None => Some('a'), - } -} - -/// Add the lifetime param to `builder`. If there are type parameters in `type_params_owner`, add it to the end. Otherwise -/// add new type params brackets with the lifetime parameter at `new_type_params_loc`. -fn add_lifetime_param( - type_params_owner: &TypeParamsOwner, - builder: &mut AssistBuilder, - new_type_params_loc: TextSize, - new_lifetime_param: char, -) { - match type_params_owner.type_param_list() { - // add the new lifetime parameter to an existing type param list - Some(type_params) => { - builder.insert( - (u32::from(type_params.syntax().text_range().end()) - 1).into(), - format!(", '{}", new_lifetime_param), - ); - } - // create a new type param list containing only the new lifetime parameter - None => { - builder.insert(new_type_params_loc, format!("<'{}>", new_lifetime_param)); - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::tests::{check_assist, check_assist_not_applicable}; - - #[test] - fn test_example_case() { - check_assist( - change_lifetime_anon_to_named, - r#"impl Cursor<'_<|>> { - fn node(self) -> &SyntaxNode { - match self { - Cursor::Replace(node) | Cursor::Before(node) => node, - } - } - }"#, - r#"impl<'a> Cursor<'a> { - fn node(self) -> &SyntaxNode { - match self { - Cursor::Replace(node) | Cursor::Before(node) => node, - } - } - }"#, - ); - } - - #[test] - fn test_example_case_simplified() { - check_assist( - change_lifetime_anon_to_named, - r#"impl Cursor<'_<|>> {"#, - r#"impl<'a> Cursor<'a> {"#, - ); - } - - #[test] - fn test_example_case_cursor_after_tick() { - check_assist( - change_lifetime_anon_to_named, - r#"impl Cursor<'<|>_> {"#, - r#"impl<'a> Cursor<'a> {"#, - ); - } - - #[test] - fn test_example_case_cursor_before_tick() { - check_assist( - change_lifetime_anon_to_named, - r#"impl Cursor<<|>'_> {"#, - r#"impl<'a> Cursor<'a> {"#, - ); - } - - #[test] - fn test_not_applicable_cursor_position() { - check_assist_not_applicable(change_lifetime_anon_to_named, r#"impl Cursor<'_><|> {"#); - check_assist_not_applicable(change_lifetime_anon_to_named, r#"impl Cursor<|><'_> {"#); - } - - #[test] - fn test_not_applicable_lifetime_already_name() { - check_assist_not_applicable(change_lifetime_anon_to_named, r#"impl Cursor<'a<|>> {"#); - check_assist_not_applicable( - change_lifetime_anon_to_named, - r#"fn my_fun<'a>() -> X<'a<|>>"#, - ); - } - - #[test] - fn test_with_type_parameter() { - check_assist( - change_lifetime_anon_to_named, - r#"impl Cursor>"#, - r#"impl Cursor"#, - ); - } - - #[test] - fn test_with_existing_lifetime_name_conflict() { - check_assist( - change_lifetime_anon_to_named, - r#"impl<'a, 'b> Cursor<'a, 'b, '_<|>>"#, - r#"impl<'a, 'b, 'c> Cursor<'a, 'b, 'c>"#, - ); - } - - #[test] - fn test_function_return_value_anon_lifetime_param() { - check_assist( - change_lifetime_anon_to_named, - r#"fn my_fun() -> X<'_<|>>"#, - r#"fn my_fun<'a>() -> X<'a>"#, - ); - } - - #[test] - fn test_function_return_value_anon_reference_lifetime() { - check_assist( - change_lifetime_anon_to_named, - r#"fn my_fun() -> &'_<|> X"#, - r#"fn my_fun<'a>() -> &'a X"#, - ); - } - - #[test] - fn test_function_param_anon_lifetime() { - check_assist( - change_lifetime_anon_to_named, - r#"fn my_fun(x: X<'_<|>>)"#, - r#"fn my_fun<'a>(x: X<'a>)"#, - ); - } - - #[test] - fn test_function_add_lifetime_to_params() { - check_assist( - change_lifetime_anon_to_named, - r#"fn my_fun(f: &Foo) -> X<'_<|>>"#, - r#"fn my_fun<'a>(f: &'a Foo) -> X<'a>"#, - ); - } - - #[test] - fn test_function_add_lifetime_to_params_in_presence_of_other_lifetime() { - check_assist( - change_lifetime_anon_to_named, - r#"fn my_fun<'other>(f: &Foo, b: &'other Bar) -> X<'_<|>>"#, - r#"fn my_fun<'other, 'a>(f: &'a Foo, b: &'other Bar) -> X<'a>"#, - ); - } - - #[test] - fn test_function_not_applicable_without_self_and_multiple_unnamed_param_lifetimes() { - // this is not permitted under lifetime elision rules - check_assist_not_applicable( - change_lifetime_anon_to_named, - r#"fn my_fun(f: &Foo, b: &Bar) -> X<'_<|>>"#, - ); - } - - #[test] - fn test_function_add_lifetime_to_self_ref_param() { - check_assist( - change_lifetime_anon_to_named, - r#"fn my_fun<'other>(&self, f: &Foo, b: &'other Bar) -> X<'_<|>>"#, - r#"fn my_fun<'other, 'a>(&'a self, f: &Foo, b: &'other Bar) -> X<'a>"#, - ); - } - - #[test] - fn test_function_add_lifetime_to_param_with_non_ref_self() { - check_assist( - change_lifetime_anon_to_named, - r#"fn my_fun<'other>(self, f: &Foo, b: &'other Bar) -> X<'_<|>>"#, - r#"fn my_fun<'other, 'a>(self, f: &'a Foo, b: &'other Bar) -> X<'a>"#, - ); - } -} diff --git a/crates/ra_assists/src/handlers/introduce_named_lifetime.rs b/crates/ra_assists/src/handlers/introduce_named_lifetime.rs new file mode 100644 index 000000000..beb5b7366 --- /dev/null +++ b/crates/ra_assists/src/handlers/introduce_named_lifetime.rs @@ -0,0 +1,303 @@ +use ra_syntax::{ + ast::{self, NameOwner, TypeAscriptionOwner, TypeParamsOwner}, + AstNode, SyntaxKind, TextRange, TextSize, +}; +use rustc_hash::FxHashSet; + +use crate::{assist_context::AssistBuilder, AssistContext, AssistId, Assists}; + +static ASSIST_NAME: &str = "introduce_named_lifetime"; +static ASSIST_LABEL: &str = "Introduce named lifetime"; + +// Assist: introduce_named_lifetime +// +// Change an anonymous lifetime to a named lifetime. +// +// ``` +// impl Cursor<'_<|>> { +// fn node(self) -> &SyntaxNode { +// match self { +// Cursor::Replace(node) | Cursor::Before(node) => node, +// } +// } +// } +// ``` +// -> +// ``` +// impl<'a> Cursor<'a> { +// fn node(self) -> &SyntaxNode { +// match self { +// Cursor::Replace(node) | Cursor::Before(node) => node, +// } +// } +// } +// ``` +// FIXME: How can we handle renaming any one of multiple anonymous lifetimes? +// FIXME: should also add support for the case fun(f: &Foo) -> &<|>Foo +pub(crate) fn introduce_named_lifetime(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { + let lifetime_token = ctx + .find_token_at_offset(SyntaxKind::LIFETIME) + .filter(|lifetime| lifetime.text() == "'_")?; + if let Some(fn_def) = lifetime_token.ancestors().find_map(ast::FnDef::cast) { + generate_fn_def_assist(acc, &fn_def, lifetime_token.text_range()) + } else if let Some(impl_def) = lifetime_token.ancestors().find_map(ast::ImplDef::cast) { + // only allow naming the last anonymous lifetime + lifetime_token.next_token().filter(|tok| tok.kind() == SyntaxKind::R_ANGLE)?; + generate_impl_def_assist(acc, &impl_def, lifetime_token.text_range()) + } else { + None + } +} + +/// Generate the assist for the fn def case +fn generate_fn_def_assist( + acc: &mut Assists, + fn_def: &ast::FnDef, + lifetime_loc: TextRange, +) -> Option<()> { + let param_list: ast::ParamList = fn_def.param_list()?; + let new_lifetime_param = generate_unique_lifetime_param_name(&fn_def.type_param_list())?; + let end_of_fn_ident = fn_def.name()?.ident_token()?.text_range().end(); + let self_param = + // use the self if it's a reference and has no explicit lifetime + param_list.self_param().filter(|p| p.lifetime_token().is_none() && p.amp_token().is_some()); + // compute the location which implicitly has the same lifetime as the anonymous lifetime + let loc_needing_lifetime = if let Some(self_param) = self_param { + // if we have a self reference, use that + Some(self_param.self_token()?.text_range().start()) + } else { + // otherwise, if there's a single reference parameter without a named liftime, use that + let fn_params_without_lifetime: Vec<_> = param_list + .params() + .filter_map(|param| match param.ascribed_type() { + Some(ast::TypeRef::ReferenceType(ascribed_type)) + if ascribed_type.lifetime_token() == None => + { + Some(ascribed_type.amp_token()?.text_range().end()) + } + _ => None, + }) + .collect(); + match fn_params_without_lifetime.len() { + 1 => Some(fn_params_without_lifetime.into_iter().nth(0)?), + 0 => None, + // multiple unnnamed is invalid. assist is not applicable + _ => return None, + } + }; + acc.add(AssistId(ASSIST_NAME), ASSIST_LABEL, lifetime_loc, |builder| { + add_lifetime_param(fn_def, builder, end_of_fn_ident, new_lifetime_param); + builder.replace(lifetime_loc, format!("'{}", new_lifetime_param)); + loc_needing_lifetime.map(|loc| builder.insert(loc, format!("'{} ", new_lifetime_param))); + }) +} + +/// Generate the assist for the impl def case +fn generate_impl_def_assist( + acc: &mut Assists, + impl_def: &ast::ImplDef, + lifetime_loc: TextRange, +) -> Option<()> { + let new_lifetime_param = generate_unique_lifetime_param_name(&impl_def.type_param_list())?; + let end_of_impl_kw = impl_def.impl_token()?.text_range().end(); + acc.add(AssistId(ASSIST_NAME), ASSIST_LABEL, lifetime_loc, |builder| { + add_lifetime_param(impl_def, builder, end_of_impl_kw, new_lifetime_param); + builder.replace(lifetime_loc, format!("'{}", new_lifetime_param)); + }) +} + +/// Given a type parameter list, generate a unique lifetime parameter name +/// which is not in the list +fn generate_unique_lifetime_param_name( + existing_type_param_list: &Option, +) -> Option { + match existing_type_param_list { + Some(type_params) => { + let used_lifetime_params: FxHashSet<_> = type_params + .lifetime_params() + .map(|p| p.syntax().text().to_string()[1..].to_owned()) + .collect(); + (b'a'..=b'z').map(char::from).find(|c| !used_lifetime_params.contains(&c.to_string())) + } + None => Some('a'), + } +} + +/// Add the lifetime param to `builder`. If there are type parameters in `type_params_owner`, add it to the end. Otherwise +/// add new type params brackets with the lifetime parameter at `new_type_params_loc`. +fn add_lifetime_param( + type_params_owner: &TypeParamsOwner, + builder: &mut AssistBuilder, + new_type_params_loc: TextSize, + new_lifetime_param: char, +) { + match type_params_owner.type_param_list() { + // add the new lifetime parameter to an existing type param list + Some(type_params) => { + builder.insert( + (u32::from(type_params.syntax().text_range().end()) - 1).into(), + format!(", '{}", new_lifetime_param), + ); + } + // create a new type param list containing only the new lifetime parameter + None => { + builder.insert(new_type_params_loc, format!("<'{}>", new_lifetime_param)); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::tests::{check_assist, check_assist_not_applicable}; + + #[test] + fn test_example_case() { + check_assist( + introduce_named_lifetime, + r#"impl Cursor<'_<|>> { + fn node(self) -> &SyntaxNode { + match self { + Cursor::Replace(node) | Cursor::Before(node) => node, + } + } + }"#, + r#"impl<'a> Cursor<'a> { + fn node(self) -> &SyntaxNode { + match self { + Cursor::Replace(node) | Cursor::Before(node) => node, + } + } + }"#, + ); + } + + #[test] + fn test_example_case_simplified() { + check_assist( + introduce_named_lifetime, + r#"impl Cursor<'_<|>> {"#, + r#"impl<'a> Cursor<'a> {"#, + ); + } + + #[test] + fn test_example_case_cursor_after_tick() { + check_assist( + introduce_named_lifetime, + r#"impl Cursor<'<|>_> {"#, + r#"impl<'a> Cursor<'a> {"#, + ); + } + + #[test] + fn test_example_case_cursor_before_tick() { + check_assist( + introduce_named_lifetime, + r#"impl Cursor<<|>'_> {"#, + r#"impl<'a> Cursor<'a> {"#, + ); + } + + #[test] + fn test_not_applicable_cursor_position() { + check_assist_not_applicable(introduce_named_lifetime, r#"impl Cursor<'_><|> {"#); + check_assist_not_applicable(introduce_named_lifetime, r#"impl Cursor<|><'_> {"#); + } + + #[test] + fn test_not_applicable_lifetime_already_name() { + check_assist_not_applicable(introduce_named_lifetime, r#"impl Cursor<'a<|>> {"#); + check_assist_not_applicable(introduce_named_lifetime, r#"fn my_fun<'a>() -> X<'a<|>>"#); + } + + #[test] + fn test_with_type_parameter() { + check_assist( + introduce_named_lifetime, + r#"impl Cursor>"#, + r#"impl Cursor"#, + ); + } + + #[test] + fn test_with_existing_lifetime_name_conflict() { + check_assist( + introduce_named_lifetime, + r#"impl<'a, 'b> Cursor<'a, 'b, '_<|>>"#, + r#"impl<'a, 'b, 'c> Cursor<'a, 'b, 'c>"#, + ); + } + + #[test] + fn test_function_return_value_anon_lifetime_param() { + check_assist( + introduce_named_lifetime, + r#"fn my_fun() -> X<'_<|>>"#, + r#"fn my_fun<'a>() -> X<'a>"#, + ); + } + + #[test] + fn test_function_return_value_anon_reference_lifetime() { + check_assist( + introduce_named_lifetime, + r#"fn my_fun() -> &'_<|> X"#, + r#"fn my_fun<'a>() -> &'a X"#, + ); + } + + #[test] + fn test_function_param_anon_lifetime() { + check_assist( + introduce_named_lifetime, + r#"fn my_fun(x: X<'_<|>>)"#, + r#"fn my_fun<'a>(x: X<'a>)"#, + ); + } + + #[test] + fn test_function_add_lifetime_to_params() { + check_assist( + introduce_named_lifetime, + r#"fn my_fun(f: &Foo) -> X<'_<|>>"#, + r#"fn my_fun<'a>(f: &'a Foo) -> X<'a>"#, + ); + } + + #[test] + fn test_function_add_lifetime_to_params_in_presence_of_other_lifetime() { + check_assist( + introduce_named_lifetime, + r#"fn my_fun<'other>(f: &Foo, b: &'other Bar) -> X<'_<|>>"#, + r#"fn my_fun<'other, 'a>(f: &'a Foo, b: &'other Bar) -> X<'a>"#, + ); + } + + #[test] + fn test_function_not_applicable_without_self_and_multiple_unnamed_param_lifetimes() { + // this is not permitted under lifetime elision rules + check_assist_not_applicable( + introduce_named_lifetime, + r#"fn my_fun(f: &Foo, b: &Bar) -> X<'_<|>>"#, + ); + } + + #[test] + fn test_function_add_lifetime_to_self_ref_param() { + check_assist( + introduce_named_lifetime, + r#"fn my_fun<'other>(&self, f: &Foo, b: &'other Bar) -> X<'_<|>>"#, + r#"fn my_fun<'other, 'a>(&'a self, f: &Foo, b: &'other Bar) -> X<'a>"#, + ); + } + + #[test] + fn test_function_add_lifetime_to_param_with_non_ref_self() { + check_assist( + introduce_named_lifetime, + r#"fn my_fun<'other>(self, f: &Foo, b: &'other Bar) -> X<'_<|>>"#, + r#"fn my_fun<'other, 'a>(self, f: &'a Foo, b: &'other Bar) -> X<'a>"#, + ); + } +} diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs index 3f8f7ffbf..fb5d59a87 100644 --- a/crates/ra_assists/src/lib.rs +++ b/crates/ra_assists/src/lib.rs @@ -112,7 +112,6 @@ mod handlers { mod add_turbo_fish; mod apply_demorgan; mod auto_import; - mod change_lifetime_anon_to_named; mod change_return_type_to_result; mod change_visibility; mod early_return; @@ -122,6 +121,7 @@ mod handlers { mod flip_comma; mod flip_trait_bound; mod inline_local_variable; + mod introduce_named_lifetime; mod introduce_variable; mod invert_if; mod merge_imports; @@ -152,7 +152,6 @@ mod handlers { add_turbo_fish::add_turbo_fish, apply_demorgan::apply_demorgan, auto_import::auto_import, - change_lifetime_anon_to_named::change_lifetime_anon_to_named, change_return_type_to_result::change_return_type_to_result, change_visibility::change_visibility, early_return::convert_to_guarded_return, @@ -162,6 +161,7 @@ mod handlers { flip_comma::flip_comma, flip_trait_bound::flip_trait_bound, inline_local_variable::inline_local_variable, + introduce_named_lifetime::introduce_named_lifetime, introduce_variable::introduce_variable, invert_if::invert_if, merge_imports::merge_imports, diff --git a/crates/ra_assists/src/tests/generated.rs b/crates/ra_assists/src/tests/generated.rs index 73d43283d..d17504529 100644 --- a/crates/ra_assists/src/tests/generated.rs +++ b/crates/ra_assists/src/tests/generated.rs @@ -287,31 +287,6 @@ pub mod std { pub mod collections { pub struct HashMap { } } } ) } -#[test] -fn doctest_change_lifetime_anon_to_named() { - check_doc_test( - "change_lifetime_anon_to_named", - r#####" -impl Cursor<'_<|>> { - fn node(self) -> &SyntaxNode { - match self { - Cursor::Replace(node) | Cursor::Before(node) => node, - } - } -} -"#####, - r#####" -impl<'a> Cursor<'a> { - fn node(self) -> &SyntaxNode { - match self { - Cursor::Replace(node) | Cursor::Before(node) => node, - } - } -} -"#####, - ) -} - #[test] fn doctest_change_return_type_to_result() { check_doc_test( @@ -476,6 +451,31 @@ fn main() { ) } +#[test] +fn doctest_introduce_named_lifetime() { + check_doc_test( + "introduce_named_lifetime", + r#####" +impl Cursor<'_<|>> { + fn node(self) -> &SyntaxNode { + match self { + Cursor::Replace(node) | Cursor::Before(node) => node, + } + } +} +"#####, + r#####" +impl<'a> Cursor<'a> { + fn node(self) -> &SyntaxNode { + match self { + Cursor::Replace(node) | Cursor::Before(node) => node, + } + } +} +"#####, + ) +} + #[test] fn doctest_introduce_variable() { check_doc_test( -- cgit v1.2.3