@@ -28,7 +28,9 @@ use crate::{
2828} ;
2929use base_db:: AnchoredPathBuf ;
3030use either:: Either ;
31- use hir:: { FieldSource , FileRange , InFile , ModuleSource , Name , Semantics , sym} ;
31+ use hir:: { FieldSource , FileRange , HasCrate , InFile , ModuleSource , Name , Semantics , sym} ;
32+ use itertools:: Itertools ;
33+ use rustc_hash:: FxHashSet ;
3234use span:: { Edition , FileId , SyntaxContext } ;
3335use stdx:: { TupleExt , never} ;
3436use syntax:: {
@@ -405,6 +407,11 @@ fn rename_reference(
405407 source_edit_from_references ( sema. db , references, def, & new_name, edition) ,
406408 )
407409 } ) ) ;
410+
411+ if let Definition :: Field ( field) = def {
412+ rename_field_constructors ( sema, field, & new_name, & mut source_change, config) ;
413+ }
414+
408415 if rename_definition == RenameDefinition :: Yes {
409416 // This needs to come after the references edits, because we change the annotation of existing edits
410417 // if a conflict is detected.
@@ -415,6 +422,104 @@ fn rename_reference(
415422 Ok ( source_change)
416423}
417424
425+ fn rename_field_constructors (
426+ sema : & Semantics < ' _ , RootDatabase > ,
427+ field : hir:: Field ,
428+ new_name : & Name ,
429+ source_change : & mut SourceChange ,
430+ config : & RenameConfig ,
431+ ) {
432+ let db = sema. db ;
433+ let old_name = field. name ( db) ;
434+ let adt = field. parent_def ( db) . adt ( db) ;
435+ adt. ty ( db) . iterate_assoc_items ( db, |assoc_item| {
436+ let ctor = assoc_item. as_function ( ) ?;
437+ if ctor. has_self_param ( db) {
438+ return None ;
439+ }
440+ if ctor. ret_type ( db) . as_adt ( ) != Some ( adt) {
441+ return None ;
442+ }
443+
444+ let source = sema. source ( ctor) ;
445+ let return_values = sema
446+ . fn_return_points ( ctor)
447+ . into_iter ( )
448+ . filter_map ( |ret| ret. value . expr ( ) )
449+ . chain ( source. and_then ( |source| source. value . body ( ) ?. tail_expr ( ) ) ) ;
450+ // FIXME: We could maybe skip ifs etc..
451+
452+ let get_renamed_field = |mut expr| {
453+ while let ast:: Expr :: ParenExpr ( e) = & expr {
454+ expr = e. expr ( ) ?;
455+ }
456+ let ast:: Expr :: RecordExpr ( expr) = expr else { return None } ;
457+ if sema. type_of_expr ( & expr. clone ( ) . into ( ) ) ?. original . as_adt ( ) ? != adt {
458+ return None ;
459+ } ;
460+ expr. record_expr_field_list ( ) ?. fields ( ) . find_map ( |record_field| {
461+ if record_field. name_ref ( ) . is_none ( )
462+ && Name :: new_root ( & record_field. field_name ( ) ?. text ( ) ) == old_name
463+ && let ast:: Expr :: PathExpr ( field_name) = record_field. expr ( ) ?
464+ {
465+ field_name. path ( )
466+ } else {
467+ None
468+ }
469+ } )
470+ } ;
471+ let renamed_fields = return_values
472+ . map ( get_renamed_field)
473+ . map ( |renamed_field| {
474+ let renamed_field = renamed_field?;
475+ let hir:: PathResolution :: Local ( local) = sema. resolve_path ( & renamed_field) ? else {
476+ return None ;
477+ } ;
478+ let range = sema. original_range_opt ( renamed_field. syntax ( ) ) ?. range ;
479+ Some ( ( range, local) )
480+ } )
481+ . collect :: < Option < Vec < _ > > > ( ) ?;
482+
483+ let edition = ctor. krate ( db) . edition ( db) ;
484+ let locals = renamed_fields. iter ( ) . map ( |& ( _, local) | local) . collect :: < FxHashSet < _ > > ( ) ;
485+ let mut all_locals_source_change = SourceChange :: default ( ) ;
486+ for local in locals {
487+ let mut local_source_change = Definition :: Local ( local)
488+ . rename ( sema, new_name. as_str ( ) , RenameDefinition :: Yes , config)
489+ . ok ( ) ?;
490+
491+ let ( edit, _snippet) =
492+ local_source_change. source_file_edits . values_mut ( ) . exactly_one ( ) . ok ( ) ?;
493+
494+ // The struct literal will have an edit `old_name -> old_name: new_name`, and we need to remove
495+ // that, as we want an overlapping edit `old_name -> new_name`.
496+ for & ( field_range, _) in & renamed_fields {
497+ edit. cancel_edits_touching ( field_range) ;
498+ }
499+
500+ all_locals_source_change =
501+ std:: mem:: take ( & mut all_locals_source_change) . merge ( local_source_change) ;
502+ }
503+ let ( edit, _snippet) =
504+ all_locals_source_change. source_file_edits . values_mut ( ) . exactly_one ( ) . ok ( ) ?;
505+ for & ( field_range, _) in & renamed_fields {
506+ edit. union ( TextEdit :: replace ( field_range, new_name. display ( db, edition) . to_string ( ) ) )
507+ . unwrap ( ) ;
508+ }
509+
510+ let file_id = * all_locals_source_change. source_file_edits . keys ( ) . exactly_one ( ) . ok ( ) ?;
511+ if let Some ( ( edit, _snippet) ) = source_change. source_file_edits . get_mut ( & file_id) {
512+ for & ( field_range, _) in & renamed_fields {
513+ edit. cancel_edits_touching ( field_range) ;
514+ }
515+ }
516+
517+ * source_change = std:: mem:: take ( source_change) . merge ( all_locals_source_change) ;
518+
519+ None :: < std:: convert:: Infallible >
520+ } ) ;
521+ }
522+
418523pub fn source_edit_from_references (
419524 db : & RootDatabase ,
420525 references : & [ FileReference ] ,
0 commit comments