1+ use crate :: { LateContext , LateLintPass , LintContext } ;
2+
3+ use hir:: { Expr , Pat } ;
4+ use rustc_hir as hir;
5+ use rustc_middle:: ty;
6+ use rustc_span:: sym;
7+
8+ declare_lint ! {
9+ /// ### What it does
10+ ///
11+ /// Checks for `for` loops over `Option` or `Result` values.
12+ ///
13+ /// ### Why is this bad?
14+ /// Readability. This is more clearly expressed as an `if
15+ /// let`.
16+ ///
17+ /// ### Example
18+ ///
19+ /// ```rust
20+ /// # let opt = Some(1);
21+ /// # let res: Result<i32, std::io::Error> = Ok(1);
22+ /// for x in opt {
23+ /// // ..
24+ /// }
25+ ///
26+ /// for x in &res {
27+ /// // ..
28+ /// }
29+ ///
30+ /// for x in res.iter() {
31+ /// // ..
32+ /// }
33+ /// ```
34+ ///
35+ /// Use instead:
36+ /// ```rust
37+ /// # let opt = Some(1);
38+ /// # let res: Result<i32, std::io::Error> = Ok(1);
39+ /// if let Some(x) = opt {
40+ /// // ..
41+ /// }
42+ ///
43+ /// if let Ok(x) = res {
44+ /// // ..
45+ /// }
46+ /// ```
47+ pub FOR_LOOP_OVER_FALLIBLES ,
48+ Warn ,
49+ "for-looping over an `Option` or a `Result`, which is more clearly expressed as an `if let`"
50+ }
51+
52+ declare_lint_pass ! ( ForLoopOverFallibles => [ FOR_LOOP_OVER_FALLIBLES ] ) ;
53+
54+ impl < ' tcx > LateLintPass < ' tcx > for ForLoopOverFallibles {
55+ fn check_expr ( & mut self , cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' _ > ) {
56+ let Some ( ( pat, arg) ) = extract_for_loop ( expr) else { return } ;
57+
58+ let ty = cx. typeck_results ( ) . expr_ty ( arg) ;
59+
60+ let ty:: Adt ( adt, _) = ty. kind ( ) else { return } ;
61+
62+ let ( article, ty, var) = match adt. did ( ) {
63+ did if cx. tcx . is_diagnostic_item ( sym:: Option , did) => ( "an" , "Option" , "Some" ) ,
64+ did if cx. tcx . is_diagnostic_item ( sym:: Result , did) => ( "a" , "Result" , "Ok" ) ,
65+ _ => return ,
66+ } ;
67+
68+ let Ok ( pat_snip) = cx. sess ( ) . source_map ( ) . span_to_snippet ( pat. span ) else { return } ;
69+ let Ok ( arg_snip) = cx. sess ( ) . source_map ( ) . span_to_snippet ( arg. span ) else { return } ;
70+
71+ let help_string = format ! (
72+ "consider replacing `for {pat_snip} in {arg_snip}` with `if let {var}({pat_snip}) = {arg_snip}`"
73+ ) ;
74+ let msg = format ! (
75+ "for loop over `{arg_snip}`, which is {article} `{ty}`. This is more readably written as an `if let` statement" ,
76+ ) ;
77+
78+ cx. struct_span_lint ( FOR_LOOP_OVER_FALLIBLES , arg. span , |diag| {
79+ diag. build ( msg) . help ( help_string) . emit ( )
80+ } )
81+ }
82+ }
83+
84+ fn extract_for_loop < ' tcx > ( expr : & Expr < ' tcx > ) -> Option < ( & ' tcx Pat < ' tcx > , & ' tcx Expr < ' tcx > ) > {
85+ if let hir:: ExprKind :: DropTemps ( e) = expr. kind
86+ && let hir:: ExprKind :: Match ( iterexpr, [ arm] , hir:: MatchSource :: ForLoopDesugar ) = e. kind
87+ && let hir:: ExprKind :: Call ( _, [ arg] ) = iterexpr. kind
88+ && let hir:: ExprKind :: Loop ( block, ..) = arm. body . kind
89+ && let [ stmt] = block. stmts
90+ && let hir:: StmtKind :: Expr ( e) = stmt. kind
91+ && let hir:: ExprKind :: Match ( _, [ _, some_arm] , _) = e. kind
92+ && let hir:: PatKind :: Struct ( _, [ field] , _) = some_arm. pat . kind
93+ {
94+ Some ( ( field. pat , arg) )
95+ } else {
96+ None
97+ }
98+
99+ }
0 commit comments