Skip to content

Commit fa7a6d3

Browse files
committed
Add fixes for non_exhaustive_let diagnostic
Example --- ```rust fn foo() { let None$0 = Some(5); } ``` -> ```rust fn foo() { let None = Some(5) else { return }; } ```
1 parent a56e577 commit fa7a6d3

File tree

1 file changed

+178
-7
lines changed

1 file changed

+178
-7
lines changed

crates/ide-diagnostics/src/handlers/non_exhaustive_let.rs

Lines changed: 178 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
1-
use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
1+
use either::Either;
2+
use hir::Semantics;
3+
use ide_db::text_edit::TextEdit;
4+
use ide_db::ty_filter::TryEnum;
5+
use ide_db::{RootDatabase, source_change::SourceChange};
6+
use syntax::{AstNode, ast};
7+
8+
use crate::{Assist, Diagnostic, DiagnosticCode, DiagnosticsContext, fix};
29

310
// Diagnostic: non-exhaustive-let
411
//
@@ -15,11 +22,68 @@ pub(crate) fn non_exhaustive_let(
1522
d.pat.map(Into::into),
1623
)
1724
.stable()
25+
.with_fixes(fixes(&ctx.sema, d))
26+
}
27+
fn fixes(sema: &Semantics<'_, RootDatabase>, d: &hir::NonExhaustiveLet) -> Option<Vec<Assist>> {
28+
let root = sema.parse_or_expand(d.pat.file_id);
29+
let pat = d.pat.value.to_node(&root);
30+
let let_stmt = ast::LetStmt::cast(pat.syntax().parent()?)?;
31+
let early_node = let_stmt.syntax().ancestors().find_map(AstNode::cast)?;
32+
let early_text = early_text(sema, &early_node);
33+
34+
if let_stmt.let_else().is_some() {
35+
return None;
36+
}
37+
38+
let file_id = d.pat.file_id.file_id()?.file_id(sema.db);
39+
let semicolon = if let_stmt.semicolon_token().is_none() { ";" } else { "" };
40+
let else_block = format!(" else {{ {early_text} }}{semicolon}");
41+
let insert_offset = let_stmt
42+
.semicolon_token()
43+
.map(|it| it.text_range().start())
44+
.unwrap_or_else(|| let_stmt.syntax().text_range().end());
45+
46+
let source_change =
47+
SourceChange::from_text_edit(file_id, TextEdit::insert(insert_offset, else_block));
48+
let target = sema.original_range(let_stmt.syntax()).range;
49+
Some(vec![fix("add_let_else_block", "Add let-else block", source_change, target)])
50+
}
51+
52+
fn early_text(
53+
sema: &Semantics<'_, RootDatabase>,
54+
early_node: &Either<ast::AnyHasLoopBody, Either<ast::Fn, ast::ClosureExpr>>,
55+
) -> &'static str {
56+
match early_node {
57+
Either::Left(_any_loop) => "continue",
58+
Either::Right(Either::Left(fn_)) => sema
59+
.to_def(fn_)
60+
.map(|fn_def| fn_def.ret_type(sema.db))
61+
.map(|ty| return_text(&ty, sema))
62+
.unwrap_or("return"),
63+
Either::Right(Either::Right(closure)) => closure
64+
.body()
65+
.and_then(|expr| sema.type_of_expr(&expr))
66+
.map(|ty| return_text(&ty.adjusted(), sema))
67+
.unwrap_or("return"),
68+
}
69+
}
70+
71+
fn return_text(ty: &hir::Type<'_>, sema: &Semantics<'_, RootDatabase>) -> &'static str {
72+
if ty.is_unit() {
73+
"return"
74+
} else if let Some(try_enum) = TryEnum::from_ty(sema, ty) {
75+
match try_enum {
76+
TryEnum::Option => "return None",
77+
TryEnum::Result => "return Err($0)",
78+
}
79+
} else {
80+
"return $0"
81+
}
1882
}
1983

2084
#[cfg(test)]
2185
mod tests {
22-
use crate::tests::check_diagnostics;
86+
use crate::tests::{check_diagnostics, check_fix};
2387

2488
#[test]
2589
fn option_nonexhaustive() {
@@ -28,7 +92,7 @@ mod tests {
2892
//- minicore: option
2993
fn main() {
3094
let None = Some(5);
31-
//^^^^ error: non-exhaustive pattern: `Some(_)` not covered
95+
//^^^^ 💡 error: non-exhaustive pattern: `Some(_)` not covered
3296
}
3397
"#,
3498
);
@@ -54,7 +118,7 @@ fn main() {
54118
fn main() {
55119
'_a: {
56120
let None = Some(5);
57-
//^^^^ error: non-exhaustive pattern: `Some(_)` not covered
121+
//^^^^ 💡 error: non-exhaustive pattern: `Some(_)` not covered
58122
}
59123
}
60124
"#,
@@ -66,7 +130,7 @@ fn main() {
66130
fn main() {
67131
let _ = async {
68132
let None = Some(5);
69-
//^^^^ error: non-exhaustive pattern: `Some(_)` not covered
133+
//^^^^ 💡 error: non-exhaustive pattern: `Some(_)` not covered
70134
};
71135
}
72136
"#,
@@ -78,7 +142,7 @@ fn main() {
78142
fn main() {
79143
unsafe {
80144
let None = Some(5);
81-
//^^^^ error: non-exhaustive pattern: `Some(_)` not covered
145+
//^^^^ 💡 error: non-exhaustive pattern: `Some(_)` not covered
82146
}
83147
}
84148
"#,
@@ -101,7 +165,7 @@ fn test(x: Result<i32, !>) {
101165
//- minicore: result
102166
fn test(x: Result<i32, &'static !>) {
103167
let Ok(_y) = x;
104-
//^^^^^^ error: non-exhaustive pattern: `Err(_)` not covered
168+
//^^^^^^ 💡 error: non-exhaustive pattern: `Err(_)` not covered
105169
}
106170
"#,
107171
);
@@ -132,6 +196,113 @@ fn foo(v: Enum<()>) {
132196
);
133197
}
134198

199+
#[test]
200+
fn fix_return_in_loop() {
201+
check_fix(
202+
r#"
203+
//- minicore: option
204+
fn foo() {
205+
while cond {
206+
let None$0 = Some(5);
207+
}
208+
}
209+
"#,
210+
r#"
211+
fn foo() {
212+
while cond {
213+
let None = Some(5) else { continue };
214+
}
215+
}
216+
"#,
217+
);
218+
}
219+
220+
#[test]
221+
fn fix_return_in_fn() {
222+
check_fix(
223+
r#"
224+
//- minicore: option
225+
fn foo() {
226+
let None$0 = Some(5);
227+
}
228+
"#,
229+
r#"
230+
fn foo() {
231+
let None = Some(5) else { return };
232+
}
233+
"#,
234+
);
235+
}
236+
237+
#[test]
238+
fn fix_return_in_incomplete_let() {
239+
check_fix(
240+
r#"
241+
//- minicore: option
242+
fn foo() {
243+
let None$0 = Some(5)
244+
}
245+
"#,
246+
r#"
247+
fn foo() {
248+
let None = Some(5) else { return };
249+
}
250+
"#,
251+
);
252+
}
253+
254+
#[test]
255+
fn fix_return_in_closure() {
256+
check_fix(
257+
r#"
258+
//- minicore: option
259+
fn foo() -> Option<()> {
260+
let _f = || {
261+
let None$0 = Some(5);
262+
};
263+
}
264+
"#,
265+
r#"
266+
fn foo() -> Option<()> {
267+
let _f = || {
268+
let None = Some(5) else { return };
269+
};
270+
}
271+
"#,
272+
);
273+
}
274+
275+
#[test]
276+
fn fix_return_try_in_fn() {
277+
check_fix(
278+
r#"
279+
//- minicore: option
280+
fn foo() -> Option<()> {
281+
let None$0 = Some(5);
282+
}
283+
"#,
284+
r#"
285+
fn foo() -> Option<()> {
286+
let None = Some(5) else { return None };
287+
}
288+
"#,
289+
);
290+
291+
check_fix(
292+
r#"
293+
//- minicore: option, result
294+
fn foo() -> Result<(), i32> {
295+
let None$0 = Some(5);
296+
}
297+
"#,
298+
r#"
299+
fn foo() -> Result<(), i32> {
300+
let None = Some(5) else { return Err($0) };
301+
}
302+
"#,
303+
);
304+
}
305+
135306
#[test]
136307
fn regression_20259() {
137308
check_diagnostics(

0 commit comments

Comments
 (0)