1 use hir::{db::AstDatabase, TypeInfo};
2 use ide_db::{assists::Assist, helpers::for_each_tail_expr, source_change::SourceChange};
3 use syntax::AstNode;
4 use text_edit::TextEdit;
5 
6 use crate::{fix, Diagnostic, DiagnosticsContext};
7 
8 // Diagnostic: missing-ok-or-some-in-tail-expr
9 //
10 // This diagnostic is triggered if a block that should return `Result` returns a value not wrapped in `Ok`,
11 // or if a block that should return `Option` returns a value not wrapped in `Some`.
12 //
13 // Example:
14 //
15 // ```rust
16 // fn foo() -> Result<u8, ()> {
17 //     10
18 // }
19 // ```
missing_ok_or_some_in_tail_expr( ctx: &DiagnosticsContext<'_>, d: &hir::MissingOkOrSomeInTailExpr, ) -> Diagnostic20 pub(crate) fn missing_ok_or_some_in_tail_expr(
21     ctx: &DiagnosticsContext<'_>,
22     d: &hir::MissingOkOrSomeInTailExpr,
23 ) -> Diagnostic {
24     Diagnostic::new(
25         "missing-ok-or-some-in-tail-expr",
26         format!("wrap return expression in {}", d.required),
27         ctx.sema.diagnostics_display_range(d.expr.clone().map(|it| it.into())).range,
28     )
29     .with_fixes(fixes(ctx, d))
30 }
31 
fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingOkOrSomeInTailExpr) -> Option<Vec<Assist>>32 fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingOkOrSomeInTailExpr) -> Option<Vec<Assist>> {
33     let root = ctx.sema.db.parse_or_expand(d.expr.file_id)?;
34     let tail_expr = d.expr.value.to_node(&root);
35     let tail_expr_range = tail_expr.syntax().text_range();
36     let mut builder = TextEdit::builder();
37     for_each_tail_expr(&tail_expr, &mut |expr| {
38         if ctx.sema.type_of_expr(expr).map(TypeInfo::original).as_ref() != Some(&d.expected) {
39             builder.insert(expr.syntax().text_range().start(), format!("{}(", d.required));
40             builder.insert(expr.syntax().text_range().end(), ")".to_string());
41         }
42     });
43     let source_change =
44         SourceChange::from_text_edit(d.expr.file_id.original_file(ctx.sema.db), builder.finish());
45     let name = if d.required == "Ok" { "Wrap with Ok" } else { "Wrap with Some" };
46     Some(vec![fix("wrap_tail_expr", name, source_change, tail_expr_range)])
47 }
48 
49 #[cfg(test)]
50 mod tests {
51     use crate::tests::{check_diagnostics, check_fix};
52 
53     #[test]
test_wrap_return_type_option()54     fn test_wrap_return_type_option() {
55         check_fix(
56             r#"
57 //- minicore: option, result
58 fn div(x: i32, y: i32) -> Option<i32> {
59     if y == 0 {
60         return None;
61     }
62     x / y$0
63 }
64 "#,
65             r#"
66 fn div(x: i32, y: i32) -> Option<i32> {
67     if y == 0 {
68         return None;
69     }
70     Some(x / y)
71 }
72 "#,
73         );
74     }
75 
76     #[test]
test_wrap_return_type_option_tails()77     fn test_wrap_return_type_option_tails() {
78         check_fix(
79             r#"
80 //- minicore: option, result
81 fn div(x: i32, y: i32) -> Option<i32> {
82     if y == 0 {
83         0
84     } else if true {
85         100
86     } else {
87         None
88     }$0
89 }
90 "#,
91             r#"
92 fn div(x: i32, y: i32) -> Option<i32> {
93     if y == 0 {
94         Some(0)
95     } else if true {
96         Some(100)
97     } else {
98         None
99     }
100 }
101 "#,
102         );
103     }
104 
105     #[test]
test_wrap_return_type()106     fn test_wrap_return_type() {
107         check_fix(
108             r#"
109 //- minicore: option, result
110 fn div(x: i32, y: i32) -> Result<i32, ()> {
111     if y == 0 {
112         return Err(());
113     }
114     x / y$0
115 }
116 "#,
117             r#"
118 fn div(x: i32, y: i32) -> Result<i32, ()> {
119     if y == 0 {
120         return Err(());
121     }
122     Ok(x / y)
123 }
124 "#,
125         );
126     }
127 
128     #[test]
test_wrap_return_type_handles_generic_functions()129     fn test_wrap_return_type_handles_generic_functions() {
130         check_fix(
131             r#"
132 //- minicore: option, result
133 fn div<T>(x: T) -> Result<T, i32> {
134     if x == 0 {
135         return Err(7);
136     }
137     $0x
138 }
139 "#,
140             r#"
141 fn div<T>(x: T) -> Result<T, i32> {
142     if x == 0 {
143         return Err(7);
144     }
145     Ok(x)
146 }
147 "#,
148         );
149     }
150 
151     #[test]
test_wrap_return_type_handles_type_aliases()152     fn test_wrap_return_type_handles_type_aliases() {
153         check_fix(
154             r#"
155 //- minicore: option, result
156 type MyResult<T> = Result<T, ()>;
157 
158 fn div(x: i32, y: i32) -> MyResult<i32> {
159     if y == 0 {
160         return Err(());
161     }
162     x $0/ y
163 }
164 "#,
165             r#"
166 type MyResult<T> = Result<T, ()>;
167 
168 fn div(x: i32, y: i32) -> MyResult<i32> {
169     if y == 0 {
170         return Err(());
171     }
172     Ok(x / y)
173 }
174 "#,
175         );
176     }
177 
178     #[test]
test_in_const_and_static()179     fn test_in_const_and_static() {
180         check_fix(
181             r#"
182 //- minicore: option, result
183 static A: Option<()> = {($0)};
184             "#,
185             r#"
186 static A: Option<()> = {Some(())};
187             "#,
188         );
189         check_fix(
190             r#"
191 //- minicore: option, result
192 const _: Option<()> = {($0)};
193             "#,
194             r#"
195 const _: Option<()> = {Some(())};
196             "#,
197         );
198     }
199 
200     #[test]
test_wrap_return_type_not_applicable_when_expr_type_does_not_match_ok_type()201     fn test_wrap_return_type_not_applicable_when_expr_type_does_not_match_ok_type() {
202         check_diagnostics(
203             r#"
204 //- minicore: option, result
205 fn foo() -> Result<(), i32> { 0 }
206 "#,
207         );
208     }
209 
210     #[test]
test_wrap_return_type_not_applicable_when_return_type_is_not_result_or_option()211     fn test_wrap_return_type_not_applicable_when_return_type_is_not_result_or_option() {
212         check_diagnostics(
213             r#"
214 //- minicore: option, result
215 enum SomeOtherEnum { Ok(i32), Err(String) }
216 
217 fn foo() -> SomeOtherEnum { 0 }
218 "#,
219         );
220     }
221 }
222