@@ -9,7 +9,7 @@ use stdx::to_lower_snake_case;
99use syntax:: {
1010 AstNode , Edition , SmolStr , SmolStrBuilder , ToSmolStr ,
1111 ast:: { self , HasName } ,
12- match_ast,
12+ format_smolstr , match_ast,
1313} ;
1414
1515use crate :: RootDatabase ;
@@ -232,6 +232,40 @@ impl NameGenerator {
232232 self . suggest_name ( "var_name" )
233233 }
234234
235+ /// Suggest a unique lifetime name following Rust conventions.
236+ ///
237+ /// Generates lifetime names in alphabetical order: `'a`, `'b`, `'c`, ..., `'z`.
238+ /// This follows Rust's idiomatic lifetime naming conventions.
239+ ///
240+ /// # Examples
241+ ///
242+ /// ```
243+ /// # use ide_db::syntax_helpers::suggest_name::NameGenerator;
244+ /// let mut gen = NameGenerator::default();
245+ /// assert_eq!(gen.for_lifetime(), "'a");
246+ /// assert_eq!(gen.for_lifetime(), "'b");
247+ /// assert_eq!(gen.for_lifetime(), "'c");
248+ /// ```
249+ ///
250+ /// When initialized with existing lifetimes:
251+ ///
252+ /// ```
253+ /// # use ide_db::syntax_helpers::suggest_name::NameGenerator;
254+ /// let mut gen = NameGenerator::new_with_names(["'a", "'c"].iter().copied());
255+ /// assert_eq!(gen.for_lifetime(), "'b");
256+ /// assert_eq!(gen.for_lifetime(), "'d");
257+ /// ```
258+ pub fn for_lifetime ( & mut self ) -> SmolStr {
259+ for c in 'a' ..='z' {
260+ let candidate = format_smolstr ! ( "'{c}" ) ;
261+ if !self . pool . contains_key ( & candidate) {
262+ self . pool . insert ( candidate. clone ( ) , 0 ) ;
263+ return candidate;
264+ }
265+ }
266+ self . suggest_name ( "'a" )
267+ }
268+
235269 /// Insert a name into the pool
236270 fn insert ( & mut self , name : & str ) {
237271 let ( prefix, suffix) = Self :: split_numeric_suffix ( name) ;
@@ -1142,4 +1176,40 @@ fn main() {
11421176
11431177 assert_eq ! ( generator. suggest_name( "c" ) , "c5" ) ;
11441178 }
1179+
1180+ #[ test]
1181+ fn for_lifetime_generates_alphabetical_names ( ) {
1182+ let mut generator = NameGenerator :: default ( ) ;
1183+ assert_eq ! ( generator. for_lifetime( ) , "'a" ) ;
1184+ assert_eq ! ( generator. for_lifetime( ) , "'b" ) ;
1185+ assert_eq ! ( generator. for_lifetime( ) , "'c" ) ;
1186+ }
1187+
1188+ #[ test]
1189+ fn for_lifetime_avoids_existing_names ( ) {
1190+ let mut generator =
1191+ NameGenerator :: new_with_names ( [ "'a" , "'b" , "'d" , "'e" , "'f" ] . into_iter ( ) ) ;
1192+ assert_eq ! ( generator. for_lifetime( ) , "'c" ) ;
1193+ assert_eq ! ( generator. for_lifetime( ) , "'g" ) ;
1194+ }
1195+
1196+ #[ test]
1197+ fn for_lifetime_exhaustive ( ) {
1198+ let mut generator = NameGenerator :: default ( ) ;
1199+ let mut lifetimes = Vec :: new ( ) ;
1200+ for _ in 0 ..10 {
1201+ lifetimes. push ( generator. for_lifetime ( ) ) ;
1202+ }
1203+ assert_eq ! ( lifetimes[ 0 ] , "'a" ) ;
1204+ assert_eq ! ( lifetimes[ 1 ] , "'b" ) ;
1205+ assert_eq ! ( lifetimes[ 9 ] , "'j" ) ;
1206+ }
1207+
1208+ #[ test]
1209+ fn for_lifetime_fallback_when_exhausted ( ) {
1210+ let all_lifetimes: Vec < _ > = ( 'a' ..='z' ) . map ( |c| format ! ( "'{c}" ) ) . collect ( ) ;
1211+ let mut generator = NameGenerator :: new_with_names ( all_lifetimes. iter ( ) . map ( |s| s. as_str ( ) ) ) ;
1212+ let fallback = generator. for_lifetime ( ) ;
1213+ assert_eq ! ( fallback, "'a1" ) ;
1214+ }
11451215}
0 commit comments