]> git.proxmox.com Git - rustc.git/blame - compiler/rustc_lint/src/for_loops_over_fallibles.rs
New upstream version 1.68.2+dfsg1
[rustc.git] / compiler / rustc_lint / src / for_loops_over_fallibles.rs
CommitLineData
f25598a0
FG
1use crate::{
2 lints::{
3 ForLoopsOverFalliblesDiag, ForLoopsOverFalliblesLoopSub, ForLoopsOverFalliblesQuestionMark,
4 ForLoopsOverFalliblesSuggestion,
5 },
6 LateContext, LateLintPass, LintContext,
7};
2b03887a
FG
8
9use hir::{Expr, Pat};
2b03887a 10use rustc_hir as hir;
2b03887a
FG
11use rustc_infer::{infer::TyCtxtInferExt, traits::ObligationCause};
12use rustc_middle::ty::{self, List};
13use rustc_span::{sym, Span};
2b03887a
FG
14
15declare_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
45declare_lint_pass!(ForLoopsOverFallibles => [FOR_LOOPS_OVER_FALLIBLES]);
46
47impl<'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
f25598a0 61 let sub = if let Some(recv) = extract_iterator_next_call(cx, arg)
2b03887a
FG
62 && let Ok(recv_snip) = cx.sess().source_map().span_to_snippet(recv.span)
63 {
f25598a0 64 ForLoopsOverFalliblesLoopSub::RemoveNext { suggestion: recv.span.between(arg.span.shrink_to_hi()), recv_snip }
2b03887a 65 } else {
f25598a0
FG
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 };
2b03887a 78
f25598a0
FG
79 cx.emit_spanned_lint(
80 FOR_LOOPS_OVER_FALLIBLES,
81 arg.span,
82 ForLoopsOverFalliblesDiag { article, ty, sub, question_mark, suggestion },
83 );
2b03887a
FG
84 }
85}
86
87fn 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
103fn 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
117fn 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();
2b03887a
FG
142 let cause = ObligationCause::new(
143 span,
144 body_id.hir_id,
145 rustc_infer::traits::ObligationCauseCode::MiscObligation,
146 );
487cf647 147 let errors = rustc_trait_selection::traits::fully_solve_bound(
2b03887a 148 &infcx,
487cf647 149 cause,
2b03887a
FG
150 ty::ParamEnv::empty(),
151 // Erase any region vids from the type, which may not be resolved
152 infcx.tcx.erase_regions(ty),
153 into_iterator_did,
2b03887a
FG
154 );
155
2b03887a
FG
156 errors.is_empty()
157}