aboutsummaryrefslogtreecommitdiff
path: root/crates/ide_assists
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide_assists')
-rw-r--r--crates/ide_assists/src/handlers/extract_function.rs112
-rw-r--r--crates/ide_assists/src/handlers/fill_match_arms.rs150
-rw-r--r--crates/ide_assists/src/handlers/flip_comma.rs18
-rw-r--r--crates/ide_assists/src/handlers/generate_deref.rs227
-rw-r--r--crates/ide_assists/src/lib.rs4
-rw-r--r--crates/ide_assists/src/tests.rs7
-rw-r--r--crates/ide_assists/src/tests/generated.rs27
7 files changed, 485 insertions, 60 deletions
diff --git a/crates/ide_assists/src/handlers/extract_function.rs b/crates/ide_assists/src/handlers/extract_function.rs
index af9543766..78a57fbdc 100644
--- a/crates/ide_assists/src/handlers/extract_function.rs
+++ b/crates/ide_assists/src/handlers/extract_function.rs
@@ -599,7 +599,12 @@ fn extraction_target(node: &SyntaxNode, selection_range: TextRange) -> Option<Fu
599 // we have selected a few statements in a block 599 // we have selected a few statements in a block
600 // so covering_element returns the whole block 600 // so covering_element returns the whole block
601 if node.kind() == BLOCK_EXPR { 601 if node.kind() == BLOCK_EXPR {
602 let body = FunctionBody::from_range(node.clone(), selection_range); 602 // Extract the full statements.
603 let statements_range = node
604 .children()
605 .filter(|c| selection_range.intersect(c.text_range()).is_some())
606 .fold(selection_range, |acc, c| acc.cover(c.text_range()));
607 let body = FunctionBody::from_range(node.clone(), statements_range);
603 if body.is_some() { 608 if body.is_some() {
604 return body; 609 return body;
605 } 610 }
@@ -610,7 +615,8 @@ fn extraction_target(node: &SyntaxNode, selection_range: TextRange) -> Option<Fu
610 // so we try to expand covering_element to parent and repeat the previous 615 // so we try to expand covering_element to parent and repeat the previous
611 if let Some(parent) = node.parent() { 616 if let Some(parent) = node.parent() {
612 if parent.kind() == BLOCK_EXPR { 617 if parent.kind() == BLOCK_EXPR {
613 let body = FunctionBody::from_range(parent, selection_range); 618 // Extract the full statement.
619 let body = FunctionBody::from_range(parent, node.text_range());
614 if body.is_some() { 620 if body.is_some() {
615 return body; 621 return body;
616 } 622 }
@@ -736,6 +742,14 @@ fn reference_is_exclusive(
736 742
737/// checks if this expr requires `&mut` access, recurses on field access 743/// checks if this expr requires `&mut` access, recurses on field access
738fn expr_require_exclusive_access(ctx: &AssistContext, expr: &ast::Expr) -> Option<bool> { 744fn expr_require_exclusive_access(ctx: &AssistContext, expr: &ast::Expr) -> Option<bool> {
745 match expr {
746 ast::Expr::MacroCall(_) => {
747 // FIXME: expand macro and check output for mutable usages of the variable?
748 return None;
749 }
750 _ => (),
751 }
752
739 let parent = expr.syntax().parent()?; 753 let parent = expr.syntax().parent()?;
740 754
741 if let Some(bin_expr) = ast::BinExpr::cast(parent.clone()) { 755 if let Some(bin_expr) = ast::BinExpr::cast(parent.clone()) {
@@ -794,7 +808,7 @@ impl HasTokenAtOffset for SyntaxNode {
794 } 808 }
795} 809}
796 810
797/// find relevant `ast::PathExpr` for reference 811/// find relevant `ast::Expr` for reference
798/// 812///
799/// # Preconditions 813/// # Preconditions
800/// 814///
@@ -811,7 +825,11 @@ fn path_element_of_reference(
811 stdx::never!(false, "cannot find path parent of variable usage: {:?}", token); 825 stdx::never!(false, "cannot find path parent of variable usage: {:?}", token);
812 None 826 None
813 })?; 827 })?;
814 stdx::always!(matches!(path, ast::Expr::PathExpr(_))); 828 stdx::always!(
829 matches!(path, ast::Expr::PathExpr(_) | ast::Expr::MacroCall(_)),
830 "unexpected expression type for variable usage: {:?}",
831 path
832 );
815 Some(path) 833 Some(path)
816} 834}
817 835
@@ -1773,6 +1791,60 @@ fn $0fun_name() -> i32 {
1773 } 1791 }
1774 1792
1775 #[test] 1793 #[test]
1794 fn extract_partial_block_single_line() {
1795 check_assist(
1796 extract_function,
1797 r#"
1798fn foo() {
1799 let n = 1;
1800 let mut v = $0n * n;$0
1801 v += 1;
1802}"#,
1803 r#"
1804fn foo() {
1805 let n = 1;
1806 let mut v = fun_name(n);
1807 v += 1;
1808}
1809
1810fn $0fun_name(n: i32) -> i32 {
1811 let mut v = n * n;
1812 v
1813}"#,
1814 );
1815 }
1816
1817 #[test]
1818 fn extract_partial_block() {
1819 check_assist(
1820 extract_function,
1821 r#"
1822fn foo() {
1823 let m = 2;
1824 let n = 1;
1825 let mut v = m $0* n;
1826 let mut w = 3;$0
1827 v += 1;
1828 w += 1;
1829}"#,
1830 r#"
1831fn foo() {
1832 let m = 2;
1833 let n = 1;
1834 let (mut v, mut w) = fun_name(m, n);
1835 v += 1;
1836 w += 1;
1837}
1838
1839fn $0fun_name(m: i32, n: i32) -> (i32, i32) {
1840 let mut v = m * n;
1841 let mut w = 3;
1842 (v, w)
1843}"#,
1844 );
1845 }
1846
1847 #[test]
1776 fn argument_form_expr() { 1848 fn argument_form_expr() {
1777 check_assist( 1849 check_assist(
1778 extract_function, 1850 extract_function,
@@ -3462,4 +3534,36 @@ fn foo() -> Result<(), i64> {
3462}"##, 3534}"##,
3463 ); 3535 );
3464 } 3536 }
3537
3538 #[test]
3539 fn param_usage_in_macro() {
3540 check_assist(
3541 extract_function,
3542 r"
3543macro_rules! m {
3544 ($val:expr) => { $val };
3545}
3546
3547fn foo() {
3548 let n = 1;
3549 $0let k = n * m!(n);$0
3550 let m = k + 1;
3551}",
3552 r"
3553macro_rules! m {
3554 ($val:expr) => { $val };
3555}
3556
3557fn foo() {
3558 let n = 1;
3559 let k = fun_name(n);
3560 let m = k + 1;
3561}
3562
3563fn $0fun_name(n: i32) -> i32 {
3564 let k = n * m!(n);
3565 k
3566}",
3567 );
3568 }
3465} 3569}
diff --git a/crates/ide_assists/src/handlers/fill_match_arms.rs b/crates/ide_assists/src/handlers/fill_match_arms.rs
index 878b3a3fa..a30c4d04e 100644
--- a/crates/ide_assists/src/handlers/fill_match_arms.rs
+++ b/crates/ide_assists/src/handlers/fill_match_arms.rs
@@ -1,5 +1,6 @@
1use std::iter; 1use std::iter;
2 2
3use either::Either;
3use hir::{Adt, HasSource, ModuleDef, Semantics}; 4use hir::{Adt, HasSource, ModuleDef, Semantics};
4use ide_db::helpers::{mod_path_to_ast, FamousDefs}; 5use ide_db::helpers::{mod_path_to_ast, FamousDefs};
5use ide_db::RootDatabase; 6use ide_db::RootDatabase;
@@ -7,7 +8,7 @@ use itertools::Itertools;
7use syntax::ast::{self, make, AstNode, MatchArm, NameOwner, Pat}; 8use syntax::ast::{self, make, AstNode, MatchArm, NameOwner, Pat};
8 9
9use crate::{ 10use crate::{
10 utils::{does_pat_match_variant, render_snippet, Cursor}, 11 utils::{self, render_snippet, Cursor},
11 AssistContext, AssistId, AssistKind, Assists, 12 AssistContext, AssistId, AssistKind, Assists,
12}; 13};
13 14
@@ -48,6 +49,18 @@ pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<
48 } 49 }
49 } 50 }
50 51
52 let top_lvl_pats: Vec<_> = arms
53 .iter()
54 .filter_map(ast::MatchArm::pat)
55 .flat_map(|pat| match pat {
56 // Special casee OrPat as separate top-level pats
57 Pat::OrPat(or_pat) => Either::Left(or_pat.pats()),
58 _ => Either::Right(iter::once(pat)),
59 })
60 // Exclude top level wildcards so that they are expanded by this assist, retains status quo in #8129.
61 .filter(|pat| !matches!(pat, Pat::WildcardPat(_)))
62 .collect();
63
51 let module = ctx.sema.scope(expr.syntax()).module()?; 64 let module = ctx.sema.scope(expr.syntax()).module()?;
52 65
53 let missing_arms: Vec<MatchArm> = if let Some(enum_def) = resolve_enum_def(&ctx.sema, &expr) { 66 let missing_arms: Vec<MatchArm> = if let Some(enum_def) = resolve_enum_def(&ctx.sema, &expr) {
@@ -56,7 +69,7 @@ pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<
56 let mut variants = variants 69 let mut variants = variants
57 .into_iter() 70 .into_iter()
58 .filter_map(|variant| build_pat(ctx.db(), module, variant)) 71 .filter_map(|variant| build_pat(ctx.db(), module, variant))
59 .filter(|variant_pat| is_variant_missing(&mut arms, variant_pat)) 72 .filter(|variant_pat| is_variant_missing(&top_lvl_pats, variant_pat))
60 .map(|pat| make::match_arm(iter::once(pat), make::expr_empty_block())) 73 .map(|pat| make::match_arm(iter::once(pat), make::expr_empty_block()))
61 .collect::<Vec<_>>(); 74 .collect::<Vec<_>>();
62 if Some(enum_def) == FamousDefs(&ctx.sema, Some(module.krate())).core_option_Option() { 75 if Some(enum_def) == FamousDefs(&ctx.sema, Some(module.krate())).core_option_Option() {
@@ -66,17 +79,6 @@ pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<
66 } 79 }
67 variants 80 variants
68 } else if let Some(enum_defs) = resolve_tuple_of_enum_def(&ctx.sema, &expr) { 81 } else if let Some(enum_defs) = resolve_tuple_of_enum_def(&ctx.sema, &expr) {
69 // Partial fill not currently supported for tuple of enums.
70 if !arms.is_empty() {
71 return None;
72 }
73
74 // We do not currently support filling match arms for a tuple
75 // containing a single enum.
76 if enum_defs.len() < 2 {
77 return None;
78 }
79
80 // When calculating the match arms for a tuple of enums, we want 82 // When calculating the match arms for a tuple of enums, we want
81 // to create a match arm for each possible combination of enum 83 // to create a match arm for each possible combination of enum
82 // values. The `multi_cartesian_product` method transforms 84 // values. The `multi_cartesian_product` method transforms
@@ -91,7 +93,7 @@ pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<
91 variants.into_iter().filter_map(|variant| build_pat(ctx.db(), module, variant)); 93 variants.into_iter().filter_map(|variant| build_pat(ctx.db(), module, variant));
92 ast::Pat::from(make::tuple_pat(patterns)) 94 ast::Pat::from(make::tuple_pat(patterns))
93 }) 95 })
94 .filter(|variant_pat| is_variant_missing(&mut arms, variant_pat)) 96 .filter(|variant_pat| is_variant_missing(&top_lvl_pats, variant_pat))
95 .map(|pat| make::match_arm(iter::once(pat), make::expr_empty_block())) 97 .map(|pat| make::match_arm(iter::once(pat), make::expr_empty_block()))
96 .collect() 98 .collect()
97 } else { 99 } else {
@@ -134,16 +136,19 @@ pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<
134 ) 136 )
135} 137}
136 138
137fn is_variant_missing(existing_arms: &mut Vec<MatchArm>, var: &Pat) -> bool { 139fn is_variant_missing(existing_pats: &[Pat], var: &Pat) -> bool {
138 existing_arms.iter().filter_map(|arm| arm.pat()).all(|pat| { 140 !existing_pats.iter().any(|pat| does_pat_match_variant(pat, var))
139 // Special casee OrPat as separate top-level pats 141}
140 let top_level_pats: Vec<Pat> = match pat {
141 Pat::OrPat(pats) => pats.pats().collect::<Vec<_>>(),
142 _ => vec![pat],
143 };
144 142
145 !top_level_pats.iter().any(|pat| does_pat_match_variant(pat, var)) 143// Fixme: this is still somewhat limited, use hir_ty::diagnostics::match_check?
146 }) 144fn does_pat_match_variant(pat: &Pat, var: &Pat) -> bool {
145 match (pat, var) {
146 (Pat::WildcardPat(_), _) => true,
147 (Pat::TuplePat(tpat), Pat::TuplePat(tvar)) => {
148 tpat.fields().zip(tvar.fields()).all(|(p, v)| does_pat_match_variant(&p, &v))
149 }
150 _ => utils::does_pat_match_variant(pat, var),
151 }
147} 152}
148 153
149fn resolve_enum_def(sema: &Semantics<RootDatabase>, expr: &ast::Expr) -> Option<hir::Enum> { 154fn resolve_enum_def(sema: &Semantics<RootDatabase>, expr: &ast::Expr) -> Option<hir::Enum> {
@@ -473,20 +478,81 @@ fn main() {
473 478
474 #[test] 479 #[test]
475 fn fill_match_arms_tuple_of_enum_partial() { 480 fn fill_match_arms_tuple_of_enum_partial() {
476 check_assist_not_applicable( 481 check_assist(
477 fill_match_arms, 482 fill_match_arms,
478 r#" 483 r#"
479 enum A { One, Two } 484enum A { One, Two }
480 enum B { One, Two } 485enum B { One, Two }
481 486
482 fn main() { 487fn main() {
483 let a = A::One; 488 let a = A::One;
484 let b = B::One; 489 let b = B::One;
485 match (a$0, b) { 490 match (a$0, b) {
486 (A::Two, B::One) => {} 491 (A::Two, B::One) => {}
487 } 492 }
488 } 493}
489 "#, 494"#,
495 r#"
496enum A { One, Two }
497enum B { One, Two }
498
499fn main() {
500 let a = A::One;
501 let b = B::One;
502 match (a, b) {
503 (A::Two, B::One) => {}
504 $0(A::One, B::One) => {}
505 (A::One, B::Two) => {}
506 (A::Two, B::Two) => {}
507 }
508}
509"#,
510 );
511 }
512
513 #[test]
514 fn fill_match_arms_tuple_of_enum_partial_with_wildcards() {
515 let ra_fixture = r#"
516fn main() {
517 let a = Some(1);
518 let b = Some(());
519 match (a$0, b) {
520 (Some(_), _) => {}
521 (None, Some(_)) => {}
522 }
523}
524"#;
525 check_assist(
526 fill_match_arms,
527 &format!("//- /main.rs crate:main deps:core{}{}", ra_fixture, FamousDefs::FIXTURE),
528 r#"
529fn main() {
530 let a = Some(1);
531 let b = Some(());
532 match (a, b) {
533 (Some(_), _) => {}
534 (None, Some(_)) => {}
535 $0(None, None) => {}
536 }
537}
538"#,
539 );
540 }
541
542 #[test]
543 fn fill_match_arms_partial_with_deep_pattern() {
544 // Fixme: cannot handle deep patterns
545 let ra_fixture = r#"
546fn main() {
547 match $0Some(true) {
548 Some(true) => {}
549 None => {}
550 }
551}
552"#;
553 check_assist_not_applicable(
554 fill_match_arms,
555 &format!("//- /main.rs crate:main deps:core{}{}", ra_fixture, FamousDefs::FIXTURE),
490 ); 556 );
491 } 557 }
492 558
@@ -514,10 +580,7 @@ fn main() {
514 580
515 #[test] 581 #[test]
516 fn fill_match_arms_single_element_tuple_of_enum() { 582 fn fill_match_arms_single_element_tuple_of_enum() {
517 // For now we don't hande the case of a single element tuple, but 583 check_assist(
518 // we could handle this in the future if `make::tuple_pat` allowed
519 // creating a tuple with a single pattern.
520 check_assist_not_applicable(
521 fill_match_arms, 584 fill_match_arms,
522 r#" 585 r#"
523 enum A { One, Two } 586 enum A { One, Two }
@@ -528,6 +591,17 @@ fn main() {
528 } 591 }
529 } 592 }
530 "#, 593 "#,
594 r#"
595 enum A { One, Two }
596
597 fn main() {
598 let a = A::One;
599 match (a, ) {
600 $0(A::One,) => {}
601 (A::Two,) => {}
602 }
603 }
604 "#,
531 ); 605 );
532 } 606 }
533 607
diff --git a/crates/ide_assists/src/handlers/flip_comma.rs b/crates/ide_assists/src/handlers/flip_comma.rs
index 7f5e75d34..99be5e090 100644
--- a/crates/ide_assists/src/handlers/flip_comma.rs
+++ b/crates/ide_assists/src/handlers/flip_comma.rs
@@ -66,26 +66,12 @@ mod tests {
66 } 66 }
67 67
68 #[test] 68 #[test]
69 #[should_panic]
70 fn flip_comma_before_punct() { 69 fn flip_comma_before_punct() {
71 // See https://github.com/rust-analyzer/rust-analyzer/issues/1619 70 // See https://github.com/rust-analyzer/rust-analyzer/issues/1619
72 // "Flip comma" assist shouldn't be applicable to the last comma in enum or struct 71 // "Flip comma" assist shouldn't be applicable to the last comma in enum or struct
73 // declaration body. 72 // declaration body.
74 check_assist_target( 73 check_assist_not_applicable(flip_comma, "pub enum Test { A,$0 }");
75 flip_comma, 74 check_assist_not_applicable(flip_comma, "pub struct Test { foo: usize,$0 }");
76 "pub enum Test { \
77 A,$0 \
78 }",
79 ",",
80 );
81
82 check_assist_target(
83 flip_comma,
84 "pub struct Test { \
85 foo: usize,$0 \
86 }",
87 ",",
88 );
89 } 75 }
90 76
91 #[test] 77 #[test]
diff --git a/crates/ide_assists/src/handlers/generate_deref.rs b/crates/ide_assists/src/handlers/generate_deref.rs
new file mode 100644
index 000000000..4998ff7a4
--- /dev/null
+++ b/crates/ide_assists/src/handlers/generate_deref.rs
@@ -0,0 +1,227 @@
1use std::fmt::Display;
2
3use ide_db::{helpers::FamousDefs, RootDatabase};
4use syntax::{
5 ast::{self, NameOwner},
6 AstNode, SyntaxNode,
7};
8
9use crate::{
10 assist_context::{AssistBuilder, AssistContext, Assists},
11 utils::generate_trait_impl_text,
12 AssistId, AssistKind,
13};
14
15// Assist: generate_deref
16//
17// Generate `Deref` impl using the given struct field.
18//
19// ```
20// struct A;
21// struct B {
22// $0a: A
23// }
24// ```
25// ->
26// ```
27// struct A;
28// struct B {
29// a: A
30// }
31//
32// impl std::ops::Deref for B {
33// type Target = A;
34//
35// fn deref(&self) -> &Self::Target {
36// &self.a
37// }
38// }
39// ```
40pub(crate) fn generate_deref(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
41 generate_record_deref(acc, ctx).or_else(|| generate_tuple_deref(acc, ctx))
42}
43
44fn generate_record_deref(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
45 let strukt = ctx.find_node_at_offset::<ast::Struct>()?;
46 let field = ctx.find_node_at_offset::<ast::RecordField>()?;
47
48 if existing_deref_impl(&ctx.sema, &strukt).is_some() {
49 cov_mark::hit!(test_add_record_deref_impl_already_exists);
50 return None;
51 }
52
53 let field_type = field.ty()?;
54 let field_name = field.name()?;
55 let target = field.syntax().text_range();
56 acc.add(
57 AssistId("generate_deref", AssistKind::Generate),
58 format!("Generate `Deref` impl using `{}`", field_name),
59 target,
60 |edit| generate_edit(edit, strukt, field_type.syntax(), field_name.syntax()),
61 )
62}
63
64fn generate_tuple_deref(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
65 let strukt = ctx.find_node_at_offset::<ast::Struct>()?;
66 let field = ctx.find_node_at_offset::<ast::TupleField>()?;
67 let field_list = ctx.find_node_at_offset::<ast::TupleFieldList>()?;
68 let field_list_index =
69 field_list.syntax().children().into_iter().position(|s| &s == field.syntax())?;
70
71 if existing_deref_impl(&ctx.sema, &strukt).is_some() {
72 cov_mark::hit!(test_add_field_deref_impl_already_exists);
73 return None;
74 }
75
76 let field_type = field.ty()?;
77 let target = field.syntax().text_range();
78 acc.add(
79 AssistId("generate_deref", AssistKind::Generate),
80 format!("Generate `Deref` impl using `{}`", field.syntax()),
81 target,
82 |edit| generate_edit(edit, strukt, field_type.syntax(), field_list_index),
83 )
84}
85
86fn generate_edit(
87 edit: &mut AssistBuilder,
88 strukt: ast::Struct,
89 field_type_syntax: &SyntaxNode,
90 field_name: impl Display,
91) {
92 let start_offset = strukt.syntax().text_range().end();
93 let impl_code = format!(
94 r#" type Target = {0};
95
96 fn deref(&self) -> &Self::Target {{
97 &self.{1}
98 }}"#,
99 field_type_syntax, field_name
100 );
101 let strukt_adt = ast::Adt::Struct(strukt);
102 let deref_impl = generate_trait_impl_text(&strukt_adt, "std::ops::Deref", &impl_code);
103 edit.insert(start_offset, deref_impl);
104}
105
106fn existing_deref_impl(
107 sema: &'_ hir::Semantics<'_, RootDatabase>,
108 strukt: &ast::Struct,
109) -> Option<()> {
110 let strukt = sema.to_def(strukt)?;
111 let krate = strukt.module(sema.db).krate();
112
113 let deref_trait = FamousDefs(sema, Some(krate)).core_ops_Deref()?;
114 let strukt_type = strukt.ty(sema.db);
115
116 if strukt_type.impls_trait(sema.db, deref_trait, &[]) {
117 Some(())
118 } else {
119 None
120 }
121}
122
123#[cfg(test)]
124mod tests {
125 use crate::tests::{check_assist, check_assist_not_applicable};
126
127 use super::*;
128
129 #[test]
130 fn test_generate_record_deref() {
131 check_assist(
132 generate_deref,
133 r#"struct A { }
134struct B { $0a: A }"#,
135 r#"struct A { }
136struct B { a: A }
137
138impl std::ops::Deref for B {
139 type Target = A;
140
141 fn deref(&self) -> &Self::Target {
142 &self.a
143 }
144}"#,
145 );
146 }
147
148 #[test]
149 fn test_generate_field_deref_idx_0() {
150 check_assist(
151 generate_deref,
152 r#"struct A { }
153struct B($0A);"#,
154 r#"struct A { }
155struct B(A);
156
157impl std::ops::Deref for B {
158 type Target = A;
159
160 fn deref(&self) -> &Self::Target {
161 &self.0
162 }
163}"#,
164 );
165 }
166 #[test]
167 fn test_generate_field_deref_idx_1() {
168 check_assist(
169 generate_deref,
170 r#"struct A { }
171struct B(u8, $0A);"#,
172 r#"struct A { }
173struct B(u8, A);
174
175impl std::ops::Deref for B {
176 type Target = A;
177
178 fn deref(&self) -> &Self::Target {
179 &self.1
180 }
181}"#,
182 );
183 }
184
185 fn check_not_applicable(ra_fixture: &str) {
186 let fixture = format!(
187 "//- /main.rs crate:main deps:core,std\n{}\n{}",
188 ra_fixture,
189 FamousDefs::FIXTURE
190 );
191 check_assist_not_applicable(generate_deref, &fixture)
192 }
193
194 #[test]
195 fn test_generate_record_deref_not_applicable_if_already_impl() {
196 cov_mark::check!(test_add_record_deref_impl_already_exists);
197 check_not_applicable(
198 r#"struct A { }
199struct B { $0a: A }
200
201impl std::ops::Deref for B {
202 type Target = A;
203
204 fn deref(&self) -> &Self::Target {
205 &self.a
206 }
207}"#,
208 )
209 }
210
211 #[test]
212 fn test_generate_field_deref_not_applicable_if_already_impl() {
213 cov_mark::check!(test_add_field_deref_impl_already_exists);
214 check_not_applicable(
215 r#"struct A { }
216struct B($0A)
217
218impl std::ops::Deref for B {
219 type Target = A;
220
221 fn deref(&self) -> &Self::Target {
222 &self.0
223 }
224}"#,
225 )
226 }
227}
diff --git a/crates/ide_assists/src/lib.rs b/crates/ide_assists/src/lib.rs
index 3e2c82dac..8996c1b61 100644
--- a/crates/ide_assists/src/lib.rs
+++ b/crates/ide_assists/src/lib.rs
@@ -28,7 +28,9 @@ pub use assist_config::AssistConfig;
28 28
29#[derive(Debug, Clone, Copy, PartialEq, Eq)] 29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub enum AssistKind { 30pub enum AssistKind {
31 // FIXME: does the None variant make sense? Probably not.
31 None, 32 None,
33
32 QuickFix, 34 QuickFix,
33 Generate, 35 Generate,
34 Refactor, 36 Refactor,
@@ -132,6 +134,7 @@ mod handlers {
132 mod generate_default_from_enum_variant; 134 mod generate_default_from_enum_variant;
133 mod generate_default_from_new; 135 mod generate_default_from_new;
134 mod generate_is_empty_from_len; 136 mod generate_is_empty_from_len;
137 mod generate_deref;
135 mod generate_derive; 138 mod generate_derive;
136 mod generate_enum_is_method; 139 mod generate_enum_is_method;
137 mod generate_enum_projection_method; 140 mod generate_enum_projection_method;
@@ -199,6 +202,7 @@ mod handlers {
199 generate_default_from_enum_variant::generate_default_from_enum_variant, 202 generate_default_from_enum_variant::generate_default_from_enum_variant,
200 generate_default_from_new::generate_default_from_new, 203 generate_default_from_new::generate_default_from_new,
201 generate_is_empty_from_len::generate_is_empty_from_len, 204 generate_is_empty_from_len::generate_is_empty_from_len,
205 generate_deref::generate_deref,
202 generate_derive::generate_derive, 206 generate_derive::generate_derive,
203 generate_enum_is_method::generate_enum_is_method, 207 generate_enum_is_method::generate_enum_is_method,
204 generate_enum_projection_method::generate_enum_as_method, 208 generate_enum_projection_method::generate_enum_as_method,
diff --git a/crates/ide_assists/src/tests.rs b/crates/ide_assists/src/tests.rs
index a7a923beb..49533e7d2 100644
--- a/crates/ide_assists/src/tests.rs
+++ b/crates/ide_assists/src/tests.rs
@@ -84,7 +84,8 @@ fn check_doc_test(assist_id: &str, before: &str, after: &str) {
84 }); 84 });
85 85
86 let actual = { 86 let actual = {
87 let source_change = assist.source_change.unwrap(); 87 let source_change =
88 assist.source_change.expect("Assist did not contain any source changes");
88 let mut actual = before; 89 let mut actual = before;
89 if let Some(source_file_edit) = source_change.get_source_edit(file_id) { 90 if let Some(source_file_edit) = source_change.get_source_edit(file_id) {
90 source_file_edit.apply(&mut actual); 91 source_file_edit.apply(&mut actual);
@@ -121,7 +122,8 @@ fn check(handler: Handler, before: &str, expected: ExpectedResult, assist_label:
121 122
122 match (assist, expected) { 123 match (assist, expected) {
123 (Some(assist), ExpectedResult::After(after)) => { 124 (Some(assist), ExpectedResult::After(after)) => {
124 let source_change = assist.source_change.unwrap(); 125 let source_change =
126 assist.source_change.expect("Assist did not contain any source changes");
125 assert!(!source_change.source_file_edits.is_empty()); 127 assert!(!source_change.source_file_edits.is_empty());
126 let skip_header = source_change.source_file_edits.len() == 1 128 let skip_header = source_change.source_file_edits.len() == 1
127 && source_change.file_system_edits.len() == 0; 129 && source_change.file_system_edits.len() == 0;
@@ -191,6 +193,7 @@ fn assist_order_field_struct() {
191 let mut assists = assists.iter(); 193 let mut assists = assists.iter();
192 194
193 assert_eq!(assists.next().expect("expected assist").label, "Change visibility to pub(crate)"); 195 assert_eq!(assists.next().expect("expected assist").label, "Change visibility to pub(crate)");
196 assert_eq!(assists.next().expect("expected assist").label, "Generate `Deref` impl using `bar`");
194 assert_eq!(assists.next().expect("expected assist").label, "Generate a mut getter method"); 197 assert_eq!(assists.next().expect("expected assist").label, "Generate a mut getter method");
195 assert_eq!(assists.next().expect("expected assist").label, "Generate a getter method"); 198 assert_eq!(assists.next().expect("expected assist").label, "Generate a getter method");
196 assert_eq!(assists.next().expect("expected assist").label, "Generate a setter method"); 199 assert_eq!(assists.next().expect("expected assist").label, "Generate a setter method");
diff --git a/crates/ide_assists/src/tests/generated.rs b/crates/ide_assists/src/tests/generated.rs
index 27a22ca10..41559b43a 100644
--- a/crates/ide_assists/src/tests/generated.rs
+++ b/crates/ide_assists/src/tests/generated.rs
@@ -552,6 +552,33 @@ impl Default for Example {
552} 552}
553 553
554#[test] 554#[test]
555fn doctest_generate_deref() {
556 check_doc_test(
557 "generate_deref",
558 r#####"
559struct A;
560struct B {
561 $0a: A
562}
563"#####,
564 r#####"
565struct A;
566struct B {
567 a: A
568}
569
570impl std::ops::Deref for B {
571 type Target = A;
572
573 fn deref(&self) -> &Self::Target {
574 &self.a
575 }
576}
577"#####,
578 )
579}
580
581#[test]
555fn doctest_generate_derive() { 582fn doctest_generate_derive() {
556 check_doc_test( 583 check_doc_test(
557 "generate_derive", 584 "generate_derive",