ide_diagnostics/handlers/
non_exhaustive_let.rs1use either::Either;
2use hir::Semantics;
3use ide_db::text_edit::TextEdit;
4use ide_db::ty_filter::TryEnum;
5use ide_db::{RootDatabase, source_change::SourceChange};
6use syntax::{AstNode, ast};
7
8use crate::{Assist, Diagnostic, DiagnosticCode, DiagnosticsContext, fix};
9
10pub(crate) fn non_exhaustive_let(
15 ctx: &DiagnosticsContext<'_, '_>,
16 d: &hir::NonExhaustiveLet,
17) -> Diagnostic {
18 Diagnostic::new_with_syntax_node_ptr(
19 ctx,
20 DiagnosticCode::RustcHardError("E0005"),
21 format!("non-exhaustive pattern: {}", d.uncovered_patterns),
22 d.pat.map(Into::into),
23 )
24 .stable()
25 .with_fixes(fixes(&ctx.sema, d))
26}
27
28fn fixes(sema: &Semantics<'_, RootDatabase>, d: &hir::NonExhaustiveLet) -> Option<Vec<Assist>> {
29 let root = sema.parse_or_expand(d.pat.file_id);
30 let pat = d.pat.value.to_node(&root);
31 let let_stmt = ast::LetStmt::cast(pat.syntax().parent()?)?;
32 let early_node =
33 sema.ancestors_with_macros(let_stmt.syntax().clone()).find_map(AstNode::cast)?;
34 let early_text = early_text(sema, &early_node);
35
36 if let_stmt.let_else().is_some() {
37 return None;
38 }
39 let hir::FileRangeWrapper { file_id, range } = sema.original_range_opt(let_stmt.syntax())?;
40 let insert_offset = if let Some(semicolon) = let_stmt.semicolon_token()
41 && let Some(token) = sema.parse(file_id).syntax().token_at_offset(range.end()).left_biased()
42 && token.kind() == semicolon.kind()
43 {
44 token.text_range().start()
45 } else {
46 range.end()
47 };
48 let semicolon = if let_stmt.semicolon_token().is_none() { ";" } else { "" };
49 let else_block = format!(" else {{ {early_text} }}{semicolon}");
50 let file_id = file_id.file_id(sema.db);
51
52 let source_change =
53 SourceChange::from_text_edit(file_id, TextEdit::insert(insert_offset, else_block));
54 let target = sema.original_range(let_stmt.syntax()).range;
55 Some(vec![fix("add_let_else_block", "Add let-else block", source_change, target)])
56}
57
58fn early_text(
59 sema: &Semantics<'_, RootDatabase>,
60 early_node: &Either<ast::AnyHasLoopBody, Either<ast::Fn, ast::ClosureExpr>>,
61) -> &'static str {
62 match early_node {
63 Either::Left(_any_loop) => "continue",
64 Either::Right(Either::Left(fn_)) => sema
65 .to_def(fn_)
66 .map(|fn_def| fn_def.ret_type(sema.db))
67 .map(|ty| return_text(&ty, sema))
68 .unwrap_or("return"),
69 Either::Right(Either::Right(closure)) => closure
70 .body()
71 .and_then(|expr| sema.type_of_expr(&expr))
72 .map(|ty| return_text(&ty.adjusted(), sema))
73 .unwrap_or("return"),
74 }
75}
76
77fn return_text(ty: &hir::Type<'_>, sema: &Semantics<'_, RootDatabase>) -> &'static str {
78 if ty.is_unit() {
79 "return"
80 } else if let Some(try_enum) = TryEnum::from_ty(sema, ty) {
81 match try_enum {
82 TryEnum::Option => "return None",
83 TryEnum::Result => "return Err($0)",
84 }
85 } else {
86 "return $0"
87 }
88}
89
90#[cfg(test)]
91mod tests {
92 use crate::tests::{check_diagnostics, check_fix};
93
94 #[test]
95 fn option_nonexhaustive() {
96 check_diagnostics(
97 r#"
98//- minicore: option
99fn main() {
100 let None = Some(5);
101 //^^^^ 💡 error: non-exhaustive pattern: `Some(_)` not covered
102}
103"#,
104 );
105 }
106
107 #[test]
108 fn option_exhaustive() {
109 check_diagnostics(
110 r#"
111//- minicore: option
112fn main() {
113 let Some(_) | None = Some(5);
114}
115"#,
116 );
117 }
118
119 #[test]
120 fn option_nonexhaustive_inside_blocks() {
121 check_diagnostics(
122 r#"
123//- minicore: option
124fn main() {
125 '_a: {
126 let None = Some(5);
127 //^^^^ 💡 error: non-exhaustive pattern: `Some(_)` not covered
128 }
129}
130"#,
131 );
132
133 check_diagnostics(
134 r#"
135//- minicore: future, option
136fn main() {
137 let _ = async {
138 let None = Some(5);
139 //^^^^ 💡 error: non-exhaustive pattern: `Some(_)` not covered
140 };
141}
142"#,
143 );
144
145 check_diagnostics(
146 r#"
147//- minicore: option
148fn main() {
149 unsafe {
150 let None = Some(5);
151 //^^^^ 💡 error: non-exhaustive pattern: `Some(_)` not covered
152 }
153}
154"#,
155 );
156 }
157
158 #[test]
159 fn min_exhaustive() {
160 check_diagnostics(
161 r#"
162//- minicore: result
163fn test(x: Result<i32, !>) {
164 let Ok(_y) = x;
165}
166"#,
167 );
168
169 check_diagnostics(
170 r#"
171//- minicore: result
172fn test(x: Result<i32, &'static !>) {
173 let Ok(_y) = x;
174 //^^^^^^ 💡 error: non-exhaustive pattern: `Err(_)` not covered
175}
176"#,
177 );
178 }
179
180 #[test]
181 fn empty_patterns_normalize() {
182 check_diagnostics(
183 r#"
184enum Infallible {}
185
186trait Foo {
187 type Assoc;
188}
189enum Enum<T: Foo> {
190 A,
191 B(T::Assoc),
192}
193
194impl Foo for () {
195 type Assoc = Infallible;
196}
197
198fn foo(v: Enum<()>) {
199 let Enum::A = v;
200}
201 "#,
202 );
203 }
204
205 #[test]
206 fn fix_return_in_loop() {
207 check_fix(
208 r#"
209//- minicore: option
210fn foo() {
211 while cond {
212 let None$0 = Some(5);
213 }
214}
215"#,
216 r#"
217fn foo() {
218 while cond {
219 let None = Some(5) else { continue };
220 }
221}
222"#,
223 );
224 }
225
226 #[test]
227 fn fix_return_in_fn() {
228 check_fix(
229 r#"
230//- minicore: option
231fn foo() {
232 let None$0 = Some(5);
233}
234"#,
235 r#"
236fn foo() {
237 let None = Some(5) else { return };
238}
239"#,
240 );
241 }
242
243 #[test]
244 fn fix_return_in_macro_expanded() {
245 check_fix(
246 r#"
247//- minicore: option
248macro_rules! identity { ($($t:tt)*) => { $($t)* }; }
249fn foo() {
250 identity! {
251 let None$0 = Some(5);
252 }
253}
254"#,
255 r#"
256macro_rules! identity { ($($t:tt)*) => { $($t)* }; }
257fn foo() {
258 identity! {
259 let None = Some(5) else { return };
260 }
261}
262"#,
263 );
264 }
265
266 #[test]
267 fn fix_return_in_incomplete_let() {
268 check_fix(
269 r#"
270//- minicore: option
271fn foo() {
272 let None$0 = Some(5)
273}
274"#,
275 r#"
276fn foo() {
277 let None = Some(5) else { return };
278}
279"#,
280 );
281 }
282
283 #[test]
284 fn fix_return_in_closure() {
285 check_fix(
286 r#"
287//- minicore: option
288fn foo() -> Option<()> {
289 let _f = || {
290 let None$0 = Some(5);
291 };
292}
293"#,
294 r#"
295fn foo() -> Option<()> {
296 let _f = || {
297 let None = Some(5) else { return };
298 };
299}
300"#,
301 );
302 }
303
304 #[test]
305 fn fix_return_try_in_fn() {
306 check_fix(
307 r#"
308//- minicore: option
309fn foo() -> Option<()> {
310 let None$0 = Some(5);
311}
312"#,
313 r#"
314fn foo() -> Option<()> {
315 let None = Some(5) else { return None };
316}
317"#,
318 );
319
320 check_fix(
321 r#"
322//- minicore: option, result
323fn foo() -> Result<(), i32> {
324 let None$0 = Some(5);
325}
326"#,
327 r#"
328fn foo() -> Result<(), i32> {
329 let None = Some(5) else { return Err($0) };
330}
331"#,
332 );
333 }
334
335 #[test]
336 fn regression_20259() {
337 check_diagnostics(
338 r#"
339//- minicore: deref
340use core::ops::Deref;
341
342struct Foo<T>(T);
343
344impl<T> Deref for Foo<T> {
345 type Target = T;
346
347 fn deref(&self) -> &Self::Target {
348 &self.0
349 }
350}
351
352fn test(x: Foo<(i32, bool)>) {
353 let (_a, _b): &(i32, bool) = &x;
354}
355"#,
356 );
357 }
358
359 #[test]
360 fn uninhabited_variants() {
361 check_diagnostics(
362 r#"
363//- minicore: result
364enum Infallible {}
365
366trait Foo {
367 type Bar;
368}
369
370struct Wrapper<T> {
371 error: T,
372}
373
374struct FooWrapper<T: Foo> {
375 error: T::Bar,
376}
377
378fn foo<T: Foo<Bar = Infallible>>(result: Result<T, T::Bar>) -> T {
379 let Ok(ok) = result;
380 ok
381}
382
383fn bar<T: Foo<Bar = Infallible>>(result: Result<T, (T::Bar,)>) -> T {
384 let Ok(ok) = result;
385 ok
386}
387
388fn baz<T: Foo<Bar = Infallible>>(result: Result<T, Wrapper<T::Bar>>) -> T {
389 let Ok(ok) = result;
390 ok
391}
392
393fn qux<T: Foo<Bar = Infallible>>(result: Result<T, FooWrapper<T>>) -> T {
394 let Ok(ok) = result;
395 ok
396}
397
398fn quux<T: Foo<Bar = Infallible>>(result: Result<T, [T::Bar; 1]>) -> T {
399 let Ok(ok) = result;
400 ok
401}
402
403fn corge<T: Foo<Bar = Infallible>>(result: Result<T, (i32, T::Bar)>) -> T {
404 let Ok(ok) = result;
405 ok
406}
407"#,
408 );
409 }
410}