11use syntax:: {
2- SyntaxKind , SyntaxNode , SyntaxToken ,
2+ SyntaxKind , SyntaxNode ,
33 ast:: { self , AstNode , HasGenericParams , HasName } ,
4+ match_ast,
45} ;
56
67use crate :: { AssistContext , AssistId , Assists } ;
@@ -23,9 +24,8 @@ use crate::{AssistContext, AssistId, Assists};
2324// }
2425// ```
2526pub ( crate ) fn add_lifetime_to_type ( acc : & mut Assists , ctx : & AssistContext < ' _ , ' _ > ) -> Option < ( ) > {
26- if !trigger_assist ( ctx) {
27- return None ;
28- }
27+ // XXX: Maybe delete this and allow it to be triggered conveniently on ADT
28+ let _trigger = Missing :: from_node ( ctx. covering_element ( ) . ancestors ( ) . next ( ) ?, ctx) ?;
2929 let node = ctx. find_node_at_offset :: < ast:: Adt > ( ) ?;
3030 let has_lifetime = node
3131 . generic_param_list ( )
@@ -35,7 +35,7 @@ pub(crate) fn add_lifetime_to_type(acc: &mut Assists, ctx: &AssistContext<'_, '_
3535 return None ;
3636 }
3737
38- let changes = fetch_borrowed_types ( & node) ?;
38+ let changes = fetch_borrowed_types ( & node, ctx ) ?;
3939 let target = node. syntax ( ) . text_range ( ) ;
4040
4141 acc. add ( AssistId :: quick_fix ( "add_lifetime_to_type" ) , "Add lifetime" , target, |builder| {
@@ -54,28 +54,23 @@ pub(crate) fn add_lifetime_to_type(acc: &mut Assists, ctx: &AssistContext<'_, '_
5454
5555 for change in changes {
5656 match change {
57- Change :: Replace ( it) => {
58- builder. replace ( it. text_range ( ) , "'a" ) ;
57+ Missing :: Lifetime ( lt) => {
58+ builder. replace ( lt. syntax ( ) . text_range ( ) , "'a" ) ;
59+ }
60+ Missing :: RefType ( ref_type) => {
61+ if let Some ( amp_token) = ref_type. amp_token ( ) {
62+ builder. insert ( amp_token. text_range ( ) . end ( ) , "'a " ) ;
63+ }
5964 }
60- Change :: Insert ( it ) => {
61- builder. insert ( it . text_range ( ) . end ( ) , "'a " ) ;
65+ Missing :: PathType ( path_type ) => {
66+ builder. insert ( path_type . syntax ( ) . text_range ( ) . end ( ) , "<'a> " ) ;
6267 }
6368 }
6469 }
6570 } )
6671}
6772
68- fn trigger_assist ( ctx : & AssistContext < ' _ , ' _ > ) -> bool {
69- ctx. find_node_at_offset :: < ast:: RefType > ( )
70- . is_some_and ( |it| it. lifetime ( ) . is_none_or ( |it| it. text ( ) == "'_" ) )
71- || ctx
72- . find_node_at_offset :: < ast:: PathType > ( )
73- . map ( ast:: Type :: from)
74- . and_then ( |it| it. generic_arg_list ( ) ?. lifetime_args ( ) . next ( ) ?. lifetime ( ) )
75- . is_some_and ( |it| it. text ( ) == "'_" )
76- }
77-
78- fn fetch_borrowed_types ( node : & ast:: Adt ) -> Option < Vec < Change > > {
73+ fn fetch_borrowed_types ( node : & ast:: Adt , ctx : & AssistContext < ' _ , ' _ > ) -> Option < Vec < Missing > > {
7974 let ref_types: Vec < _ > = match node {
8075 ast:: Adt :: Enum ( enum_) => {
8176 let variant_list = enum_. variant_list ( ) ?;
@@ -84,58 +79,71 @@ fn fetch_borrowed_types(node: &ast::Adt) -> Option<Vec<Change>> {
8479 . filter_map ( |variant| {
8580 let field_list = variant. field_list ( ) ?;
8681
87- find_ref_types_from_field_list ( & field_list)
82+ find_ref_types_from_field_list ( & field_list, ctx )
8883 } )
8984 . flatten ( )
9085 . collect ( )
9186 }
9287 ast:: Adt :: Struct ( strukt) => {
9388 let field_list = strukt. field_list ( ) ?;
94- find_ref_types_from_field_list ( & field_list) ?
89+ find_ref_types_from_field_list ( & field_list, ctx ) ?
9590 }
9691 ast:: Adt :: Union ( un) => {
9792 let record_field_list = un. record_field_list ( ) ?;
98- find_ref_types_from_field_list ( & record_field_list. into ( ) ) ?
93+ find_ref_types_from_field_list ( & record_field_list. into ( ) , ctx ) ?
9994 }
10095 } ;
10196
10297 if ref_types. is_empty ( ) { None } else { Some ( ref_types) }
10398}
10499
105- fn find_ref_types_from_field_list ( field_list : & ast:: FieldList ) -> Option < Vec < Change > > {
100+ fn find_ref_types_from_field_list (
101+ field_list : & ast:: FieldList ,
102+ ctx : & AssistContext < ' _ , ' _ > ,
103+ ) -> Option < Vec < Missing > > {
106104 let ref_types: Vec < _ > = match field_list {
107105 ast:: FieldList :: RecordFieldList ( record_list) => {
108- record_list. fields ( ) . flat_map ( |f| infer_lifetimes ( f. syntax ( ) ) ) . collect ( )
106+ record_list. fields ( ) . flat_map ( |f| infer_lifetimes ( f. syntax ( ) , ctx ) ) . collect ( )
109107 }
110108 ast:: FieldList :: TupleFieldList ( tuple_field_list) => {
111- tuple_field_list. fields ( ) . flat_map ( |f| infer_lifetimes ( f. syntax ( ) ) ) . collect ( )
109+ tuple_field_list. fields ( ) . flat_map ( |f| infer_lifetimes ( f. syntax ( ) , ctx ) ) . collect ( )
112110 }
113111 } ;
114112
115113 if ref_types. is_empty ( ) { None } else { Some ( ref_types) }
116114}
117115
118- enum Change {
119- Replace ( SyntaxToken ) ,
120- Insert ( SyntaxToken ) ,
116+ enum Missing {
117+ RefType ( ast:: RefType ) ,
118+ PathType ( ast:: PathType ) ,
119+ Lifetime ( ast:: Lifetime ) ,
120+ }
121+
122+ impl Missing {
123+ fn from_node ( node : SyntaxNode , ctx : & AssistContext < ' _ , ' _ > ) -> Option < Self > {
124+ match_ast ! {
125+ match node {
126+ ast:: Lifetime ( it) => ( it. syntax( ) . text( ) == "'_" ) . then_some( Missing :: Lifetime ( it) ) ,
127+ ast:: RefType ( it) => ( it. lifetime( ) . is_none( ) && it. amp_token( ) . is_some( ) ) . then_some( Missing :: RefType ( it) ) ,
128+ ast:: PathType ( it) => {
129+ let has_lifetime = match ctx. sema. resolve_path( & it. path( ) ?) ? {
130+ hir:: PathResolution :: Def ( hir:: ModuleDef :: Adt ( adt) ) => adt. lifetime( ctx. db( ) ) . is_some( ) ,
131+ // FIXME: check TypeAlias and Trait lifetime params
132+ _ => false ,
133+ } ;
134+ ( has_lifetime && ast:: Type :: from( it. clone( ) ) . generic_arg_list( ) . is_some( ) )
135+ . then_some( Missing :: PathType ( it) )
136+ } ,
137+ _ => None ,
138+ }
139+ }
140+ }
121141}
122142
123- fn infer_lifetimes ( node : & SyntaxNode ) -> Vec < Change > {
143+ fn infer_lifetimes ( node : & SyntaxNode , ctx : & AssistContext < ' _ , ' _ > ) -> Vec < Missing > {
124144 node. children ( )
125145 . filter ( |it| !matches ! ( it. kind( ) , SyntaxKind :: FN_PTR_TYPE | SyntaxKind :: TYPE_BOUND_LIST ) )
126- . flat_map ( |it| {
127- infer_lifetimes ( & it)
128- . into_iter ( )
129- . chain ( ast:: Lifetime :: cast ( it. clone ( ) ) . and_then ( |lt| {
130- lt. lifetime_ident_token ( ) . filter ( |lt| lt. text ( ) == "'_" ) . map ( Change :: Replace )
131- } ) )
132- . chain (
133- ast:: RefType :: cast ( it)
134- . filter ( |ty| ty. lifetime ( ) . is_none ( ) )
135- . and_then ( |ty| ty. amp_token ( ) )
136- . map ( Change :: Insert ) ,
137- )
138- } )
146+ . flat_map ( |it| infer_lifetimes ( & it, ctx) . into_iter ( ) . chain ( Missing :: from_node ( it, ctx) ) )
139147 . collect ( )
140148}
141149
@@ -188,13 +196,13 @@ mod tests {
188196 fn add_lifetime_to_explicit_infer_lifetime ( ) {
189197 check_assist (
190198 add_lifetime_to_type,
191- r#"struct Foo { a: &'_ $0i32 , b: &'_ (&'_ i32, fn(&str) -> &str) }"# ,
199+ r#"struct Foo { a: &'_$0 i32 , b: &'_ (&'_ i32, fn(&str) -> &str) }"# ,
192200 r#"struct Foo<'a> { a: &'a i32, b: &'a (&'a i32, fn(&str) -> &str) }"# ,
193201 ) ;
194202
195203 check_assist (
196204 add_lifetime_to_type,
197- r#"struct Foo { a: &'_ $0i32 , b: Foo<'_> }"# ,
205+ r#"struct Foo { a: &'_$0 i32 , b: Foo<'_> }"# ,
198206 r#"struct Foo<'a> { a: &'a i32, b: Foo<'a> }"# ,
199207 ) ;
200208
0 commit comments