]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_lint/src/for_loops_over_fallibles.rs
Rollup merge of #107656 - jonhoo:bump-rust-installer, r=Mark-Simulacrum
[rust.git] / compiler / rustc_lint / src / for_loops_over_fallibles.rs
1 use crate::{
2     lints::{
3         ForLoopsOverFalliblesDiag, ForLoopsOverFalliblesLoopSub, ForLoopsOverFalliblesQuestionMark,
4         ForLoopsOverFalliblesSuggestion,
5     },
6     LateContext, LateLintPass, LintContext,
7 };
8
9 use hir::{Expr, Pat};
10 use rustc_hir as hir;
11 use rustc_infer::{infer::TyCtxtInferExt, traits::ObligationCause};
12 use rustc_middle::ty::{self, List};
13 use rustc_span::{sym, Span};
14
15 declare_lint! {
16     /// The `for_loops_over_fallibles` lint checks for `for` loops over `Option` or `Result` values.
17     ///
18     /// ### Example
19     ///
20     /// ```rust
21     /// let opt = Some(1);
22     /// for x in opt { /* ... */}
23     /// ```
24     ///
25     /// {{produces}}
26     ///
27     /// ### Explanation
28     ///
29     /// Both `Option` and `Result` implement `IntoIterator` trait, which allows using them in a `for` loop.
30     /// `for` loop over `Option` or `Result` will iterate either 0 (if the value is `None`/`Err(_)`)
31     /// or 1 time (if the value is `Some(_)`/`Ok(_)`). This is not very useful and is more clearly expressed
32     /// via `if let`.
33     ///
34     /// `for` loop can also be accidentally written with the intention to call a function multiple times,
35     /// while the function returns `Some(_)`, in these cases `while let` loop should be used instead.
36     ///
37     /// The "intended" use of `IntoIterator` implementations for `Option` and `Result` is passing them to
38     /// generic code that expects something implementing `IntoIterator`. For example using `.chain(option)`
39     /// to optionally add a value to an iterator.
40     pub FOR_LOOPS_OVER_FALLIBLES,
41     Warn,
42     "for-looping over an `Option` or a `Result`, which is more clearly expressed as an `if let`"
43 }
44
45 declare_lint_pass!(ForLoopsOverFallibles => [FOR_LOOPS_OVER_FALLIBLES]);
46
47 impl<'tcx> LateLintPass<'tcx> for ForLoopsOverFallibles {
48     fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
49         let Some((pat, arg)) = extract_for_loop(expr) else { return };
50
51         let ty = cx.typeck_results().expr_ty(arg);
52
53         let &ty::Adt(adt, substs) = ty.kind() else { return };
54
55         let (article, ty, var) = match adt.did() {
56             did if cx.tcx.is_diagnostic_item(sym::Option, did) => ("an", "Option", "Some"),
57             did if cx.tcx.is_diagnostic_item(sym::Result, did) => ("a", "Result", "Ok"),
58             _ => return,
59         };
60
61         let sub =  if let Some(recv) = extract_iterator_next_call(cx, arg)
62             && let Ok(recv_snip) = cx.sess().source_map().span_to_snippet(recv.span)
63             {
64                 ForLoopsOverFalliblesLoopSub::RemoveNext { suggestion: recv.span.between(arg.span.shrink_to_hi()), recv_snip }
65             } else {
66                 ForLoopsOverFalliblesLoopSub::UseWhileLet { start_span: expr.span.with_hi(pat.span.lo()), end_span: pat.span.between(arg.span), var }
67             } ;
68         let question_mark = if suggest_question_mark(cx, adt, substs, expr.span) {
69             Some(ForLoopsOverFalliblesQuestionMark { suggestion: arg.span.shrink_to_hi() })
70         } else {
71             None
72         };
73         let suggestion = ForLoopsOverFalliblesSuggestion {
74             var,
75             start_span: expr.span.with_hi(pat.span.lo()),
76             end_span: pat.span.between(arg.span),
77         };
78
79         cx.emit_spanned_lint(
80             FOR_LOOPS_OVER_FALLIBLES,
81             arg.span,
82             ForLoopsOverFalliblesDiag { article, ty, sub, question_mark, suggestion },
83         );
84     }
85 }
86
87 fn extract_for_loop<'tcx>(expr: &Expr<'tcx>) -> Option<(&'tcx Pat<'tcx>, &'tcx Expr<'tcx>)> {
88     if let hir::ExprKind::DropTemps(e) = expr.kind
89     && let hir::ExprKind::Match(iterexpr, [arm], hir::MatchSource::ForLoopDesugar) = e.kind
90     && let hir::ExprKind::Call(_, [arg]) = iterexpr.kind
91     && let hir::ExprKind::Loop(block, ..) = arm.body.kind
92     && let [stmt] = block.stmts
93     && let hir::StmtKind::Expr(e) = stmt.kind
94     && let hir::ExprKind::Match(_, [_, some_arm], _) = e.kind
95     && let hir::PatKind::Struct(_, [field], _) = some_arm.pat.kind
96     {
97         Some((field.pat, arg))
98     } else {
99         None
100     }
101 }
102
103 fn extract_iterator_next_call<'tcx>(
104     cx: &LateContext<'_>,
105     expr: &Expr<'tcx>,
106 ) -> Option<&'tcx Expr<'tcx>> {
107     // This won't work for `Iterator::next(iter)`, is this an issue?
108     if let hir::ExprKind::MethodCall(_, recv, _, _) = expr.kind
109     && cx.typeck_results().type_dependent_def_id(expr.hir_id) == cx.tcx.lang_items().next_fn()
110     {
111         Some(recv)
112     } else {
113         return None
114     }
115 }
116
117 fn suggest_question_mark<'tcx>(
118     cx: &LateContext<'tcx>,
119     adt: ty::AdtDef<'tcx>,
120     substs: &List<ty::GenericArg<'tcx>>,
121     span: Span,
122 ) -> bool {
123     let Some(body_id) = cx.enclosing_body else { return false };
124     let Some(into_iterator_did) = cx.tcx.get_diagnostic_item(sym::IntoIterator) else { return false };
125
126     if !cx.tcx.is_diagnostic_item(sym::Result, adt.did()) {
127         return false;
128     }
129
130     // Check that the function/closure/constant we are in has a `Result` type.
131     // Otherwise suggesting using `?` may not be a good idea.
132     {
133         let ty = cx.typeck_results().expr_ty(&cx.tcx.hir().body(body_id).value);
134         let ty::Adt(ret_adt, ..) = ty.kind() else { return false };
135         if !cx.tcx.is_diagnostic_item(sym::Result, ret_adt.did()) {
136             return false;
137         }
138     }
139
140     let ty = substs.type_at(0);
141     let infcx = cx.tcx.infer_ctxt().build();
142     let body_def_id = cx.tcx.hir().body_owner_def_id(body_id);
143     let cause = ObligationCause::new(
144         span,
145         body_def_id,
146         rustc_infer::traits::ObligationCauseCode::MiscObligation,
147     );
148     let errors = rustc_trait_selection::traits::fully_solve_bound(
149         &infcx,
150         cause,
151         ty::ParamEnv::empty(),
152         // Erase any region vids from the type, which may not be resolved
153         infcx.tcx.erase_regions(ty),
154         into_iterator_did,
155     );
156
157     errors.is_empty()
158 }