Skip to content

Commit 14b8f24

Browse files
committed
for_loop_over_fallibles: suggest using ? in some cases
1 parent 7cf94ad commit 14b8f24

File tree

1 file changed

+65
-3
lines changed

1 file changed

+65
-3
lines changed

compiler/rustc_lint/src/for_loop_over_fallibles.rs

Lines changed: 65 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@ use crate::{LateContext, LateLintPass, LintContext};
33
use hir::{Expr, Pat};
44
use rustc_errors::Applicability;
55
use rustc_hir as hir;
6-
use rustc_middle::ty;
7-
use rustc_span::sym;
6+
use rustc_infer::traits::TraitEngine;
7+
use rustc_infer::{infer::TyCtxtInferExt, traits::ObligationCause};
8+
use rustc_middle::ty::{self, List};
9+
use rustc_span::{sym, Span};
10+
use rustc_trait_selection::traits::TraitEngineExt;
811

912
declare_lint! {
1013
/// ### What it does
@@ -58,7 +61,7 @@ impl<'tcx> LateLintPass<'tcx> for ForLoopOverFallibles {
5861

5962
let ty = cx.typeck_results().expr_ty(arg);
6063

61-
let ty::Adt(adt, _) = ty.kind() else { return };
64+
let &ty::Adt(adt, substs) = ty.kind() else { return };
6265

6366
let (article, ty, var) = match adt.did() {
6467
did if cx.tcx.is_diagnostic_item(sym::Option, did) => ("an", "Option", "Some"),
@@ -96,6 +99,15 @@ impl<'tcx> LateLintPass<'tcx> for ForLoopOverFallibles {
9699
);
97100
}
98101

102+
if suggest_question_mark(cx, adt, substs, expr.span) {
103+
warn.span_suggestion(
104+
arg.span.shrink_to_hi(),
105+
"consider unwrapping the `Result` with `?` to iterate over its contents",
106+
"?",
107+
Applicability::MaybeIncorrect,
108+
);
109+
}
110+
99111
warn.multipart_suggestion_verbose(
100112
"consider using `if let` to clear intent",
101113
vec![
@@ -140,3 +152,53 @@ fn extract_iterator_next_call<'tcx>(
140152
return None
141153
}
142154
}
155+
156+
fn suggest_question_mark<'tcx>(
157+
cx: &LateContext<'tcx>,
158+
adt: ty::AdtDef<'tcx>,
159+
substs: &List<ty::GenericArg<'tcx>>,
160+
span: Span,
161+
) -> bool {
162+
let Some(body_id) = cx.enclosing_body else { return false };
163+
let Some(into_iterator_did) = cx.tcx.get_diagnostic_item(sym::IntoIterator) else { return false };
164+
165+
if !cx.tcx.is_diagnostic_item(sym::Result, adt.did()) {
166+
return false;
167+
}
168+
169+
// Check that the function/closure/constant we are in has a `Result` type.
170+
// Otherwise suggesting using `?` may not be a good idea.
171+
{
172+
let ty = cx.typeck_results().expr_ty(&cx.tcx.hir().body(body_id).value);
173+
let ty::Adt(ret_adt, ..) = ty.kind() else { return false };
174+
if !cx.tcx.is_diagnostic_item(sym::Result, ret_adt.did()) {
175+
return false;
176+
}
177+
}
178+
179+
let ty = substs.type_at(0);
180+
let is_iterator = cx.tcx.infer_ctxt().enter(|infcx| {
181+
let mut fulfill_cx = <dyn TraitEngine<'_>>::new(infcx.tcx);
182+
183+
let cause = ObligationCause::new(
184+
span,
185+
body_id.hir_id,
186+
rustc_infer::traits::ObligationCauseCode::MiscObligation,
187+
);
188+
fulfill_cx.register_bound(
189+
&infcx,
190+
ty::ParamEnv::empty(),
191+
// Erase any region vids from the type, which may not be resolved
192+
infcx.tcx.erase_regions(ty),
193+
into_iterator_did,
194+
cause,
195+
);
196+
197+
// Select all, including ambiguous predicates
198+
let errors = fulfill_cx.select_all_or_error(&infcx);
199+
200+
errors.is_empty()
201+
});
202+
203+
is_iterator
204+
}

0 commit comments

Comments
 (0)