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