Skip to main content

ide_diagnostics/handlers/
non_exhaustive_let.rs

1use 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
10// Diagnostic: non-exhaustive-let
11//
12// This diagnostic is triggered if a `let` statement without an `else` branch has a non-exhaustive
13// pattern.
14pub(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}