1use ide_db::{famous_defs::FamousDefs, source_change::SourceChangeBuilder};
2use stdx::{format_to, to_lower_snake_case};
3use syntax::{
4 TextRange,
5 ast::{
6 self, AstNode, HasGenericParams, HasName, HasVisibility, edit::AstNodeEdit,
7 syntax_factory::SyntaxFactory,
8 },
9 syntax_editor::Position,
10};
11
12use crate::{
13 AssistContext, AssistId, Assists, GroupLabel,
14 utils::{convert_reference_type, find_struct_impl, is_selected},
15};
16
17pub(crate) fn generate_setter(acc: &mut Assists, ctx: &AssistContext<'_, '_>) -> Option<()> {
39 let (strukt, info_of_record_fields, mut fn_names) = extract_and_parse(ctx, AssistType::Set)?;
47
48 if info_of_record_fields.is_empty() {
50 return None;
51 }
52
53 fn_names.iter_mut().for_each(|name| *name = format!("set_{name}"));
55
56 let impl_def = find_struct_impl(ctx, &ast::Adt::Struct(strukt.clone()), &fn_names)?;
58
59 let target: TextRange = info_of_record_fields
61 .iter()
62 .map(|record_field_info| record_field_info.target)
63 .reduce(|acc, target| acc.cover(target))?;
64
65 let setter_info = AssistInfo { impl_def, strukt, assist_type: AssistType::Set };
66
67 acc.add_group(
68 &GroupLabel("Generate getter/setter".to_owned()),
69 AssistId::generate("generate_setter"),
70 "Generate a setter method",
71 target,
72 |builder| build_source_change(builder, ctx, info_of_record_fields, setter_info),
73 );
74 Some(())
75}
76
77pub(crate) fn generate_getter(acc: &mut Assists, ctx: &AssistContext<'_, '_>) -> Option<()> {
128 generate_getter_impl(acc, ctx, false)
129}
130
131pub(crate) fn generate_getter_mut(acc: &mut Assists, ctx: &AssistContext<'_, '_>) -> Option<()> {
153 generate_getter_impl(acc, ctx, true)
154}
155
156#[derive(Clone, Debug)]
157struct RecordFieldInfo {
158 field_name: syntax::ast::Name,
159 field_ty: syntax::ast::Type,
160 fn_name: String,
161 target: TextRange,
162}
163
164struct AssistInfo {
165 impl_def: Option<ast::Impl>,
166 strukt: ast::Struct,
167 assist_type: AssistType,
168}
169
170enum AssistType {
171 Get,
172 MutGet,
173 Set,
174}
175
176pub(crate) fn generate_getter_impl(
177 acc: &mut Assists,
178 ctx: &AssistContext<'_, '_>,
179 mutable: bool,
180) -> Option<()> {
181 let (strukt, info_of_record_fields, fn_names) =
182 extract_and_parse(ctx, if mutable { AssistType::MutGet } else { AssistType::Get })?;
183 if info_of_record_fields.is_empty() {
185 return None;
186 }
187
188 let impl_def = find_struct_impl(ctx, &ast::Adt::Struct(strukt.clone()), &fn_names)?;
189
190 let (id, label) = if mutable {
191 ("generate_getter_mut", "Generate a mut getter method")
192 } else {
193 ("generate_getter", "Generate a getter method")
194 };
195
196 let target: TextRange = info_of_record_fields
198 .iter()
199 .map(|record_field_info| record_field_info.target)
200 .reduce(|acc, target| acc.cover(target))?;
201
202 let getter_info = AssistInfo {
203 impl_def,
204 strukt,
205 assist_type: if mutable { AssistType::MutGet } else { AssistType::Get },
206 };
207
208 acc.add_group(
209 &GroupLabel("Generate getter/setter".to_owned()),
210 AssistId::generate(id),
211 label,
212 target,
213 |builder| build_source_change(builder, ctx, info_of_record_fields, getter_info),
214 )
215}
216
217fn generate_getter_from_info(
218 ctx: &AssistContext<'_, '_>,
219 info: &AssistInfo,
220 record_field_info: &RecordFieldInfo,
221 make: &SyntaxFactory,
222) -> ast::Fn {
223 let (ty, body) = if matches!(info.assist_type, AssistType::MutGet) {
224 let self_expr = make.expr_path(make.ident_path("self"));
225 (
226 make.ty_ref(record_field_info.field_ty.clone(), true),
227 make.expr_ref(
228 make.expr_field(self_expr, &record_field_info.field_name.text()).into(),
229 true,
230 ),
231 )
232 } else {
233 (|| {
234 let module = ctx.sema.scope(record_field_info.field_ty.syntax())?.module();
235 let famous_defs = &FamousDefs(&ctx.sema, module.krate(ctx.db()));
236 ctx.sema
237 .resolve_type(&record_field_info.field_ty)
238 .and_then(|ty| convert_reference_type(ty, ctx.db(), famous_defs))
239 .map(|conversion| {
240 cov_mark::hit!(convert_reference_type);
241 (
242 conversion.convert_type_with_factory(make, ctx.db(), module),
243 conversion.getter(make, record_field_info.field_name.to_string()),
244 )
245 })
246 })()
247 .unwrap_or_else(|| {
248 (
249 make.ty_ref(record_field_info.field_ty.clone(), false),
250 make.expr_ref(
251 make.expr_field(
252 make.expr_path(make.ident_path("self")),
253 &record_field_info.field_name.text(),
254 )
255 .into(),
256 false,
257 ),
258 )
259 })
260 };
261
262 let self_param = if matches!(info.assist_type, AssistType::MutGet) {
263 make.mut_self_param()
264 } else {
265 make.self_param()
266 };
267
268 let strukt = &info.strukt;
269 let fn_name = make.name(&record_field_info.fn_name);
270 let params = make.param_list(Some(self_param), []);
271 let ret_type = Some(make.ret_type(ty));
272 let body = make.block_expr([], Some(body));
273
274 make.fn_(
275 None,
276 strukt.visibility(),
277 fn_name,
278 None,
279 None,
280 params,
281 body,
282 ret_type,
283 false,
284 false,
285 false,
286 false,
287 )
288}
289
290fn generate_setter_from_info(
291 info: &AssistInfo,
292 record_field_info: &RecordFieldInfo,
293 make: &SyntaxFactory,
294) -> ast::Fn {
295 let strukt = &info.strukt;
296 let field_name = &record_field_info.fn_name;
297 let fn_name = make.name(&format!("set_{field_name}"));
298 let field_ty = &record_field_info.field_ty;
299
300 let field_param =
303 make.param(make.ident_pat(false, false, make.name(field_name)).into(), field_ty.clone());
304 let params = make.param_list(Some(make.mut_self_param()), [field_param]);
305
306 let self_expr = make.expr_path(make.ident_path("self"));
309 let lhs = make.expr_field(self_expr, field_name);
310 let rhs = make.expr_path(make.ident_path(field_name));
311 let assign_stmt = make.expr_stmt(make.expr_assignment(lhs.into(), rhs).into());
312 let body = make.block_expr([assign_stmt.into()], None);
313
314 make.fn_(
316 None,
317 strukt.visibility(),
318 fn_name,
319 None,
320 None,
321 params,
322 body,
323 None,
324 false,
325 false,
326 false,
327 false,
328 )
329}
330
331fn extract_and_parse(
332 ctx: &AssistContext<'_, '_>,
333 assist_type: AssistType,
334) -> Option<(ast::Struct, Vec<RecordFieldInfo>, Vec<String>)> {
335 if !ctx.has_empty_selection() {
339 let node = ctx.covering_element();
341
342 let node = match node {
343 syntax::NodeOrToken::Node(n) => n,
344 syntax::NodeOrToken::Token(t) => t.parent()?,
345 };
346
347 let parent_struct = node.ancestors().find_map(ast::Struct::cast)?;
348
349 let (info_of_record_fields, field_names) =
350 extract_and_parse_record_fields(&parent_struct, ctx.selection_trimmed(), &assist_type)?;
351
352 return Some((parent_struct, info_of_record_fields, field_names));
353 }
354
355 let strukt = ctx.find_node_at_offset::<ast::Struct>()?;
357 let field = ctx.find_node_at_offset::<ast::RecordField>()?;
358 let record_field_info = parse_record_field(field, &assist_type)?;
359 let fn_name = record_field_info.fn_name.clone();
360 Some((strukt, vec![record_field_info], vec![fn_name]))
361}
362
363fn extract_and_parse_record_fields(
364 node: &ast::Struct,
365 selection_range: TextRange,
366 assist_type: &AssistType,
367) -> Option<(Vec<RecordFieldInfo>, Vec<String>)> {
368 let mut field_names: Vec<String> = vec![];
369 let field_list = node.field_list()?;
370
371 match field_list {
372 ast::FieldList::RecordFieldList(ele) => {
373 let info_of_record_fields_in_selection = ele
374 .fields()
375 .filter_map(|record_field| {
376 if is_selected(&record_field, selection_range, false) {
377 let record_field_info = parse_record_field(record_field, assist_type)?;
378 field_names.push(record_field_info.fn_name.clone());
379 return Some(record_field_info);
380 }
381
382 None
383 })
384 .collect::<Vec<RecordFieldInfo>>();
385
386 if info_of_record_fields_in_selection.is_empty() {
387 return None;
388 }
389
390 Some((info_of_record_fields_in_selection, field_names))
391 }
392 ast::FieldList::TupleFieldList(_) => None,
393 }
394}
395
396fn parse_record_field(
397 record_field: ast::RecordField,
398 assist_type: &AssistType,
399) -> Option<RecordFieldInfo> {
400 let field_name = record_field.name()?;
401 let field_ty = record_field.ty()?;
402
403 let mut fn_name = to_lower_snake_case(&field_name.to_string());
404 if matches!(assist_type, AssistType::MutGet) {
405 format_to!(fn_name, "_mut");
406 }
407
408 let target = record_field.syntax().text_range();
409
410 Some(RecordFieldInfo { field_name, field_ty, fn_name, target })
411}
412
413fn items(
414 ctx: &AssistContext<'_, '_>,
415 info_of_record_fields: Vec<RecordFieldInfo>,
416 assist_info: &AssistInfo,
417 make: &SyntaxFactory,
418) -> Vec<ast::AssocItem> {
419 info_of_record_fields
420 .iter()
421 .map(|record_field_info| {
422 let method = match assist_info.assist_type {
423 AssistType::Set => generate_setter_from_info(assist_info, record_field_info, make),
424 _ => generate_getter_from_info(ctx, assist_info, record_field_info, make),
425 };
426 let new_fn = method;
427 let new_fn = new_fn.indent(1.into());
428 new_fn.into()
429 })
430 .collect()
431}
432
433fn build_source_change(
434 builder: &mut SourceChangeBuilder,
435 ctx: &AssistContext<'_, '_>,
436 info_of_record_fields: Vec<RecordFieldInfo>,
437 assist_info: AssistInfo,
438) {
439 if let Some(impl_def) = &assist_info.impl_def {
440 let editor = builder.make_editor(impl_def.syntax());
442 let items = items(ctx, info_of_record_fields, &assist_info, editor.make());
443 impl_def.assoc_item_list().unwrap().add_items(&editor, items.clone());
444
445 if let Some(cap) = ctx.config.snippet_cap
446 && let Some(ast::AssocItem::Fn(fn_)) = items.last()
447 && let Some(name) = fn_.name()
448 {
449 let tabstop = builder.make_tabstop_before(cap);
450 editor.add_annotation(name.syntax(), tabstop);
451 }
452
453 builder.add_file_edits(ctx.vfs_file_id(), editor);
454 return;
455 }
456
457 let editor = builder.make_editor(assist_info.strukt.syntax());
458 let make = editor.make();
459 let items = items(ctx, info_of_record_fields, &assist_info, make);
460 let ty_params = assist_info.strukt.generic_param_list();
461 let ty_args = ty_params.as_ref().map(|it| it.to_generic_args(make));
462 let impl_def = make.impl_(
463 None,
464 ty_params,
465 ty_args,
466 make.ty_path(make.ident_path(&assist_info.strukt.name().unwrap().to_string())).into(),
467 None,
468 Some(make.assoc_item_list(items)),
469 );
470 editor.insert_all(
471 Position::after(assist_info.strukt.syntax()),
472 vec![make.whitespace("\n\n").into(), impl_def.syntax().clone().into()],
473 );
474
475 if let Some(cap) = ctx.config.snippet_cap
476 && let Some(assoc_list) = impl_def.assoc_item_list()
477 && let Some(ast::AssocItem::Fn(fn_)) = assoc_list.assoc_items().last()
478 && let Some(name) = fn_.name()
479 {
480 let tabstop = builder.make_tabstop_before(cap);
481 editor.add_annotation(name.syntax().clone(), tabstop);
482 }
483
484 builder.add_file_edits(ctx.vfs_file_id(), editor);
485}
486
487#[cfg(test)]
488mod tests_getter {
489 use crate::tests::{check_assist, check_assist_no_snippet_cap, check_assist_not_applicable};
490
491 use super::*;
492
493 #[test]
494 fn test_generate_getter_from_field() {
495 check_assist(
496 generate_getter,
497 r#"
498struct Context {
499 dat$0a: Data,
500}
501"#,
502 r#"
503struct Context {
504 data: Data,
505}
506
507impl Context {
508 fn $0data(&self) -> &Data {
509 &self.data
510 }
511}
512"#,
513 );
514
515 check_assist(
516 generate_getter_mut,
517 r#"
518struct Context {
519 dat$0a: Data,
520}
521"#,
522 r#"
523struct Context {
524 data: Data,
525}
526
527impl Context {
528 fn $0data_mut(&mut self) -> &mut Data {
529 &mut self.data
530 }
531}
532"#,
533 );
534 }
535
536 #[test]
537 fn test_generate_getter_from_field_no_snippet_cap() {
538 check_assist_no_snippet_cap(
539 generate_getter,
540 r#"
541struct Context {
542 dat$0a: Data,
543}
544"#,
545 r#"
546struct Context {
547 data: Data,
548}
549
550impl Context {
551 fn data(&self) -> &Data {
552 &self.data
553 }
554}
555"#,
556 );
557
558 check_assist_no_snippet_cap(
559 generate_getter_mut,
560 r#"
561struct Context {
562 dat$0a: Data,
563}
564"#,
565 r#"
566struct Context {
567 data: Data,
568}
569
570impl Context {
571 fn data_mut(&mut self) -> &mut Data {
572 &mut self.data
573 }
574}
575"#,
576 );
577 }
578
579 #[test]
580 fn test_generate_getter_already_implemented() {
581 check_assist_not_applicable(
582 generate_getter,
583 r#"
584struct Context {
585 dat$0a: Data,
586}
587
588impl Context {
589 fn data(&self) -> &Data {
590 &self.data
591 }
592}
593"#,
594 );
595
596 check_assist_not_applicable(
597 generate_getter_mut,
598 r#"
599struct Context {
600 dat$0a: Data,
601}
602
603impl Context {
604 fn data_mut(&mut self) -> &mut Data {
605 &mut self.data
606 }
607}
608"#,
609 );
610 }
611
612 #[test]
613 fn test_generate_getter_from_field_with_visibility_marker() {
614 check_assist(
615 generate_getter,
616 r#"
617pub(crate) struct Context {
618 dat$0a: Data,
619}
620"#,
621 r#"
622pub(crate) struct Context {
623 data: Data,
624}
625
626impl Context {
627 pub(crate) fn $0data(&self) -> &Data {
628 &self.data
629 }
630}
631"#,
632 );
633 }
634
635 #[test]
636 fn test_generate_getter_from_field_with_visibility_marker_no_snippet_cap() {
637 check_assist_no_snippet_cap(
638 generate_getter,
639 r#"
640pub(crate) struct Context {
641 dat$0a: Data,
642}
643"#,
644 r#"
645pub(crate) struct Context {
646 data: Data,
647}
648
649impl Context {
650 pub(crate) fn data(&self) -> &Data {
651 &self.data
652 }
653}
654"#,
655 );
656 }
657
658 #[test]
659 fn test_multiple_generate_getter() {
660 check_assist(
661 generate_getter,
662 r#"
663struct Context {
664 data: Data,
665 cou$0nt: usize,
666}
667
668impl Context {
669 fn data(&self) -> &Data {
670 &self.data
671 }
672}
673"#,
674 r#"
675struct Context {
676 data: Data,
677 count: usize,
678}
679
680impl Context {
681 fn data(&self) -> &Data {
682 &self.data
683 }
684
685 fn $0count(&self) -> &usize {
686 &self.count
687 }
688}
689"#,
690 );
691 }
692
693 #[test]
694 fn test_multiple_generate_getter_no_snippet_cap() {
695 check_assist_no_snippet_cap(
696 generate_getter,
697 r#"
698struct Context {
699 data: Data,
700 cou$0nt: usize,
701}
702
703impl Context {
704 fn data(&self) -> &Data {
705 &self.data
706 }
707}
708"#,
709 r#"
710struct Context {
711 data: Data,
712 count: usize,
713}
714
715impl Context {
716 fn data(&self) -> &Data {
717 &self.data
718 }
719
720 fn count(&self) -> &usize {
721 &self.count
722 }
723}
724"#,
725 );
726 }
727
728 #[test]
729 fn test_not_a_special_case() {
730 cov_mark::check_count!(convert_reference_type, 0);
731 check_assist(
733 generate_getter,
734 r#"
735pub struct String;
736
737struct S { foo: $0String }
738"#,
739 r#"
740pub struct String;
741
742struct S { foo: String }
743
744impl S {
745 fn $0foo(&self) -> &String {
746 &self.foo
747 }
748}
749"#,
750 );
751 }
752
753 #[test]
754 fn test_convert_reference_type() {
755 cov_mark::check_count!(convert_reference_type, 6);
756
757 check_assist(
759 generate_getter,
760 r#"
761//- minicore: copy
762struct S { foo: $0bool }
763"#,
764 r#"
765struct S { foo: bool }
766
767impl S {
768 fn $0foo(&self) -> bool {
769 self.foo
770 }
771}
772"#,
773 );
774
775 check_assist(
777 generate_getter,
778 r#"
779//- minicore: as_ref
780pub struct String;
781impl AsRef<str> for String {
782 fn as_ref(&self) -> &str {
783 ""
784 }
785}
786
787struct S { foo: $0String }
788"#,
789 r#"
790pub struct String;
791impl AsRef<str> for String {
792 fn as_ref(&self) -> &str {
793 ""
794 }
795}
796
797struct S { foo: String }
798
799impl S {
800 fn $0foo(&self) -> &str {
801 self.foo.as_ref()
802 }
803}
804"#,
805 );
806
807 check_assist(
809 generate_getter,
810 r#"
811//- minicore: as_ref
812struct Sweets;
813
814pub struct Box<T>(T);
815impl<T> AsRef<T> for Box<T> {
816 fn as_ref(&self) -> &T {
817 &self.0
818 }
819}
820
821struct S { foo: $0Box<Sweets> }
822"#,
823 r#"
824struct Sweets;
825
826pub struct Box<T>(T);
827impl<T> AsRef<T> for Box<T> {
828 fn as_ref(&self) -> &T {
829 &self.0
830 }
831}
832
833struct S { foo: Box<Sweets> }
834
835impl S {
836 fn $0foo(&self) -> &Sweets {
837 self.foo.as_ref()
838 }
839}
840"#,
841 );
842
843 check_assist(
845 generate_getter,
846 r#"
847//- minicore: as_ref
848pub struct Vec<T>;
849impl<T> AsRef<[T]> for Vec<T> {
850 fn as_ref(&self) -> &[T] {
851 &[]
852 }
853}
854
855struct S { foo: $0Vec<()> }
856"#,
857 r#"
858pub struct Vec<T>;
859impl<T> AsRef<[T]> for Vec<T> {
860 fn as_ref(&self) -> &[T] {
861 &[]
862 }
863}
864
865struct S { foo: Vec<()> }
866
867impl S {
868 fn $0foo(&self) -> &[()] {
869 self.foo.as_ref()
870 }
871}
872"#,
873 );
874
875 check_assist(
877 generate_getter,
878 r#"
879//- minicore: option
880struct Failure;
881
882struct S { foo: $0Option<Failure> }
883"#,
884 r#"
885struct Failure;
886
887struct S { foo: Option<Failure> }
888
889impl S {
890 fn $0foo(&self) -> Option<&Failure> {
891 self.foo.as_ref()
892 }
893}
894"#,
895 );
896
897 check_assist(
899 generate_getter,
900 r#"
901//- minicore: result
902struct Context {
903 dat$0a: Result<bool, i32>,
904}
905"#,
906 r#"
907struct Context {
908 data: Result<bool, i32>,
909}
910
911impl Context {
912 fn $0data(&self) -> Result<&bool, &i32> {
913 self.data.as_ref()
914 }
915}
916"#,
917 );
918 }
919
920 #[test]
921 fn test_generate_multiple_getters_from_selection() {
922 check_assist(
923 generate_getter,
924 r#"
925struct Context {
926 $0data: Data,
927 count: usize,$0
928}
929 "#,
930 r#"
931struct Context {
932 data: Data,
933 count: usize,
934}
935
936impl Context {
937 fn data(&self) -> &Data {
938 &self.data
939 }
940
941 fn $0count(&self) -> &usize {
942 &self.count
943 }
944}
945 "#,
946 );
947 }
948
949 #[test]
950 fn test_generate_multiple_getters_from_partial_selection() {
951 check_assist(
952 generate_getter,
953 r#"
954struct Context {
955 data$0: Data,
956 count$0: usize,
957 other: usize,
958}
959 "#,
960 r#"
961struct Context {
962 data: Data,
963 count: usize,
964 other: usize,
965}
966
967impl Context {
968 fn data(&self) -> &Data {
969 &self.data
970 }
971
972 fn $0count(&self) -> &usize {
973 &self.count
974 }
975}
976 "#,
977 );
978 }
979
980 #[test]
981 fn test_generate_multiple_getters_from_selection_one_already_exists() {
982 check_assist_not_applicable(
984 generate_getter,
985 r#"
986struct Context {
987 $0data: Data,
988 count: usize,$0
989}
990
991impl Context {
992 fn data(&self) -> &Data {
993 &self.data
994 }
995}
996 "#,
997 );
998 }
999}
1000
1001#[cfg(test)]
1002mod tests_setter {
1003 use crate::tests::{check_assist, check_assist_not_applicable};
1004
1005 use super::*;
1006
1007 fn check_not_applicable(#[rust_analyzer::rust_fixture] ra_fixture: &str) {
1008 check_assist_not_applicable(generate_setter, ra_fixture)
1009 }
1010
1011 #[test]
1012 fn test_generate_setter_from_field() {
1013 check_assist(
1014 generate_setter,
1015 r#"
1016struct Person<T: Clone> {
1017 dat$0a: T,
1018}"#,
1019 r#"
1020struct Person<T: Clone> {
1021 data: T,
1022}
1023
1024impl<T: Clone> Person<T> {
1025 fn $0set_data(&mut self, data: T) {
1026 self.data = data;
1027 }
1028}"#,
1029 );
1030 }
1031
1032 #[test]
1033 fn test_generate_setter_already_implemented() {
1034 check_not_applicable(
1035 r#"
1036struct Person<T: Clone> {
1037 dat$0a: T,
1038}
1039
1040impl<T: Clone> Person<T> {
1041 fn set_data(&mut self, data: T) {
1042 self.data = data;
1043 }
1044}"#,
1045 );
1046 }
1047
1048 #[test]
1049 fn test_generate_setter_from_field_with_visibility_marker() {
1050 check_assist(
1051 generate_setter,
1052 r#"
1053pub(crate) struct Person<T: Clone> {
1054 dat$0a: T,
1055}"#,
1056 r#"
1057pub(crate) struct Person<T: Clone> {
1058 data: T,
1059}
1060
1061impl<T: Clone> Person<T> {
1062 pub(crate) fn $0set_data(&mut self, data: T) {
1063 self.data = data;
1064 }
1065}"#,
1066 );
1067 }
1068
1069 #[test]
1070 fn test_multiple_generate_setter() {
1071 check_assist(
1072 generate_setter,
1073 r#"
1074struct Context<T: Clone> {
1075 data: T,
1076 cou$0nt: usize,
1077}
1078
1079impl<T: Clone> Context<T> {
1080 fn set_data(&mut self, data: T) {
1081 self.data = data;
1082 }
1083}"#,
1084 r#"
1085struct Context<T: Clone> {
1086 data: T,
1087 count: usize,
1088}
1089
1090impl<T: Clone> Context<T> {
1091 fn set_data(&mut self, data: T) {
1092 self.data = data;
1093 }
1094
1095 fn $0set_count(&mut self, count: usize) {
1096 self.count = count;
1097 }
1098}"#,
1099 );
1100 }
1101}