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