2022-07-18 02:58:59 +04:00
|
|
|
use hir::{Expr, Pat};
|
2024-08-13 16:44:37 -04:00
|
|
|
use rustc_hir::{self as hir, LangItem};
|
2022-07-24 23:46:04 +04:00
|
|
|
use rustc_infer::infer::TyCtxtInferExt;
|
|
|
|
|
use rustc_infer::traits::ObligationCause;
|
2024-05-09 19:47:08 +00:00
|
|
|
use rustc_middle::ty;
|
2024-04-29 13:42:13 +10:00
|
|
|
use rustc_session::{declare_lint, declare_lint_pass};
|
2022-07-24 23:46:04 +04:00
|
|
|
use rustc_span::{Span, sym};
|
2023-03-14 14:19:06 +01:00
|
|
|
use rustc_trait_selection::traits::ObligationCtxt;
|
2022-07-18 02:58:59 +04:00
|
|
|
|
2022-11-09 19:34:49 -05:00
|
|
|
use crate::lints::{
|
|
|
|
|
ForLoopsOverFalliblesDiag, ForLoopsOverFalliblesLoopSub, ForLoopsOverFalliblesQuestionMark,
|
|
|
|
|
ForLoopsOverFalliblesSuggestion,
|
2024-07-29 08:13:50 +10:00
|
|
|
};
|
2022-11-09 19:34:49 -05:00
|
|
|
use crate::{LateContext, LateLintPass, LintContext};
|
2024-07-29 08:13:50 +10:00
|
|
|
|
2022-07-18 02:58:59 +04:00
|
|
|
declare_lint! {
|
2022-10-07 15:59:39 +00:00
|
|
|
/// The `for_loops_over_fallibles` lint checks for `for` loops over `Option` or `Result` values.
|
2022-08-18 11:43:10 +04:00
|
|
|
///
|
|
|
|
|
/// ### Example
|
|
|
|
|
///
|
|
|
|
|
/// ```rust
|
|
|
|
|
/// let opt = Some(1);
|
|
|
|
|
/// for x in opt { /* ... */}
|
|
|
|
|
/// ```
|
|
|
|
|
///
|
|
|
|
|
/// {{produces}}
|
2022-07-18 02:58:59 +04:00
|
|
|
///
|
2022-08-14 21:42:29 +04:00
|
|
|
/// ### Explanation
|
|
|
|
|
///
|
|
|
|
|
/// Both `Option` and `Result` implement `IntoIterator` trait, which allows using them in a `for` loop.
|
|
|
|
|
/// `for` loop over `Option` or `Result` will iterate either 0 (if the value is `None`/`Err(_)`)
|
|
|
|
|
/// or 1 time (if the value is `Some(_)`/`Ok(_)`). This is not very useful and is more clearly expressed
|
|
|
|
|
/// via `if let`.
|
|
|
|
|
///
|
|
|
|
|
/// `for` loop can also be accidentally written with the intention to call a function multiple times,
|
|
|
|
|
/// while the function returns `Some(_)`, in these cases `while let` loop should be used instead.
|
|
|
|
|
///
|
|
|
|
|
/// The "intended" use of `IntoIterator` implementations for `Option` and `Result` is passing them to
|
|
|
|
|
/// generic code that expects something implementing `IntoIterator`. For example using `.chain(option)`
|
|
|
|
|
/// to optionally add a value to an iterator.
|
2022-10-07 15:59:39 +00:00
|
|
|
pub FOR_LOOPS_OVER_FALLIBLES,
|
2022-07-18 02:58:59 +04:00
|
|
|
Warn,
|
|
|
|
|
"for-looping over an `Option` or a `Result`, which is more clearly expressed as an `if let`"
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-07 15:59:39 +00:00
|
|
|
declare_lint_pass!(ForLoopsOverFallibles => [FOR_LOOPS_OVER_FALLIBLES]);
|
2022-07-18 02:58:59 +04:00
|
|
|
|
2022-10-07 15:59:39 +00:00
|
|
|
impl<'tcx> LateLintPass<'tcx> for ForLoopsOverFallibles {
|
2022-07-18 02:58:59 +04:00
|
|
|
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
|
|
|
|
let Some((pat, arg)) = extract_for_loop(expr) else { return };
|
|
|
|
|
|
|
|
|
|
let ty = cx.typeck_results().expr_ty(arg);
|
|
|
|
|
|
2024-05-15 10:59:13 -05:00
|
|
|
let (adt, args, ref_mutability) = match ty.kind() {
|
2024-05-15 10:00:42 -05:00
|
|
|
&ty::Adt(adt, args) => (adt, args, None),
|
|
|
|
|
&ty::Ref(_, ty, mutability) => match ty.kind() {
|
|
|
|
|
&ty::Adt(adt, args) => (adt, args, Some(mutability)),
|
|
|
|
|
_ => return,
|
|
|
|
|
},
|
|
|
|
|
_ => return,
|
|
|
|
|
};
|
2022-07-18 02:58:59 +04:00
|
|
|
|
|
|
|
|
let (article, ty, var) = match adt.did() {
|
2024-05-24 15:08:18 +10:00
|
|
|
did if cx.tcx.is_diagnostic_item(sym::Option, did) && ref_mutability.is_some() => {
|
|
|
|
|
("a", "Option", "Some")
|
|
|
|
|
}
|
2022-07-18 02:58:59 +04:00
|
|
|
did if cx.tcx.is_diagnostic_item(sym::Option, did) => ("an", "Option", "Some"),
|
|
|
|
|
did if cx.tcx.is_diagnostic_item(sym::Result, did) => ("a", "Result", "Ok"),
|
|
|
|
|
_ => return,
|
|
|
|
|
};
|
|
|
|
|
|
2024-05-15 10:59:13 -05:00
|
|
|
let ref_prefix = match ref_mutability {
|
|
|
|
|
None => "",
|
|
|
|
|
Some(ref_mutability) => ref_mutability.ref_prefix_str(),
|
|
|
|
|
};
|
|
|
|
|
|
2022-11-09 19:34:49 -05:00
|
|
|
let sub = if let Some(recv) = extract_iterator_next_call(cx, arg)
|
2022-07-24 21:07:23 +04:00
|
|
|
&& let Ok(recv_snip) = cx.sess().source_map().span_to_snippet(recv.span)
|
|
|
|
|
{
|
2022-11-09 19:34:49 -05:00
|
|
|
ForLoopsOverFalliblesLoopSub::RemoveNext {
|
|
|
|
|
suggestion: recv.span.between(arg.span.shrink_to_hi()),
|
|
|
|
|
recv_snip,
|
|
|
|
|
}
|
2022-07-24 21:16:44 +04:00
|
|
|
} else {
|
2022-11-09 19:34:49 -05:00
|
|
|
ForLoopsOverFalliblesLoopSub::UseWhileLet {
|
|
|
|
|
start_span: expr.span.with_hi(pat.span.lo()),
|
|
|
|
|
end_span: pat.span.between(arg.span),
|
|
|
|
|
var,
|
|
|
|
|
}
|
|
|
|
|
};
|
2023-07-11 22:35:29 +01:00
|
|
|
let question_mark = suggest_question_mark(cx, adt, args, expr.span)
|
2023-02-15 11:43:41 +00:00
|
|
|
.then(|| ForLoopsOverFalliblesQuestionMark { suggestion: arg.span.shrink_to_hi() });
|
2022-11-09 19:34:49 -05:00
|
|
|
let suggestion = ForLoopsOverFalliblesSuggestion {
|
|
|
|
|
var,
|
|
|
|
|
start_span: expr.span.with_hi(pat.span.lo()),
|
|
|
|
|
end_span: pat.span.between(arg.span),
|
|
|
|
|
};
|
2022-07-24 23:46:04 +04:00
|
|
|
|
2024-05-15 10:59:13 -05:00
|
|
|
cx.emit_span_lint(FOR_LOOPS_OVER_FALLIBLES, arg.span, ForLoopsOverFalliblesDiag {
|
|
|
|
|
article,
|
|
|
|
|
ref_prefix,
|
|
|
|
|
ty,
|
|
|
|
|
sub,
|
|
|
|
|
question_mark,
|
|
|
|
|
suggestion,
|
2022-11-09 19:34:49 -05:00
|
|
|
});
|
2022-07-18 02:58:59 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn extract_for_loop<'tcx>(expr: &Expr<'tcx>) -> Option<(&'tcx Pat<'tcx>, &'tcx Expr<'tcx>)> {
|
|
|
|
|
if let hir::ExprKind::DropTemps(e) = expr.kind
|
|
|
|
|
&& let hir::ExprKind::Match(iterexpr, [arm], hir::MatchSource::ForLoopDesugar) = e.kind
|
|
|
|
|
&& let hir::ExprKind::Call(_, [arg]) = iterexpr.kind
|
|
|
|
|
&& let hir::ExprKind::Loop(block, ..) = arm.body.kind
|
|
|
|
|
&& let [stmt] = block.stmts
|
|
|
|
|
&& let hir::StmtKind::Expr(e) = stmt.kind
|
|
|
|
|
&& let hir::ExprKind::Match(_, [_, some_arm], _) = e.kind
|
2022-07-18 21:08:59 +04:00
|
|
|
&& let hir::PatKind::Struct(_, [field], _) = some_arm.pat.kind
|
2022-07-18 02:58:59 +04:00
|
|
|
{
|
|
|
|
|
Some((field.pat, arg))
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
}
|
2022-07-18 21:08:59 +04:00
|
|
|
}
|
2022-07-24 21:07:23 +04:00
|
|
|
|
|
|
|
|
fn extract_iterator_next_call<'tcx>(
|
|
|
|
|
cx: &LateContext<'_>,
|
|
|
|
|
expr: &Expr<'tcx>,
|
|
|
|
|
) -> Option<&'tcx Expr<'tcx>> {
|
|
|
|
|
// This won't work for `Iterator::next(iter)`, is this an issue?
|
2022-10-07 16:52:41 +00:00
|
|
|
if let hir::ExprKind::MethodCall(_, recv, _, _) = expr.kind
|
2024-08-13 16:44:37 -04:00
|
|
|
&& cx
|
|
|
|
|
.typeck_results()
|
|
|
|
|
.type_dependent_def_id(expr.hir_id)
|
|
|
|
|
.is_some_and(|def_id| cx.tcx.is_lang_item(def_id, LangItem::IteratorNext))
|
2022-07-24 21:07:23 +04:00
|
|
|
{
|
|
|
|
|
Some(recv)
|
|
|
|
|
} else {
|
2024-09-09 12:22:00 +02:00
|
|
|
None
|
2022-07-24 21:07:23 +04:00
|
|
|
}
|
|
|
|
|
}
|
2022-07-24 23:46:04 +04:00
|
|
|
|
|
|
|
|
fn suggest_question_mark<'tcx>(
|
|
|
|
|
cx: &LateContext<'tcx>,
|
|
|
|
|
adt: ty::AdtDef<'tcx>,
|
2024-05-09 19:47:08 +00:00
|
|
|
args: ty::GenericArgsRef<'tcx>,
|
2022-07-24 23:46:04 +04:00
|
|
|
span: Span,
|
|
|
|
|
) -> bool {
|
|
|
|
|
let Some(body_id) = cx.enclosing_body else { return false };
|
|
|
|
|
let Some(into_iterator_did) = cx.tcx.get_diagnostic_item(sym::IntoIterator) else {
|
|
|
|
|
return false;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if !cx.tcx.is_diagnostic_item(sym::Result, adt.did()) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check that the function/closure/constant we are in has a `Result` type.
|
|
|
|
|
// Otherwise suggesting using `?` may not be a good idea.
|
|
|
|
|
{
|
2023-11-21 20:07:32 +01:00
|
|
|
let ty = cx.typeck_results().expr_ty(cx.tcx.hir().body(body_id).value);
|
2022-07-24 23:46:04 +04:00
|
|
|
let ty::Adt(ret_adt, ..) = ty.kind() else { return false };
|
|
|
|
|
if !cx.tcx.is_diagnostic_item(sym::Result, ret_adt.did()) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-11 22:35:29 +01:00
|
|
|
let ty = args.type_at(0);
|
2024-10-18 00:28:43 +02:00
|
|
|
let infcx = cx.tcx.infer_ctxt().build(cx.typing_mode());
|
2023-03-14 14:19:06 +01:00
|
|
|
let ocx = ObligationCtxt::new(&infcx);
|
|
|
|
|
|
2023-01-15 12:58:46 +01:00
|
|
|
let body_def_id = cx.tcx.hir().body_owner_def_id(body_id);
|
2024-05-09 20:12:47 -04:00
|
|
|
let cause =
|
|
|
|
|
ObligationCause::new(span, body_def_id, rustc_infer::traits::ObligationCauseCode::Misc);
|
2023-03-14 14:19:06 +01:00
|
|
|
|
|
|
|
|
ocx.register_bound(
|
2022-11-02 04:09:01 +00:00
|
|
|
cause,
|
2023-03-15 14:36:10 +01:00
|
|
|
cx.param_env,
|
2022-10-07 18:21:32 +00:00
|
|
|
// Erase any region vids from the type, which may not be resolved
|
|
|
|
|
infcx.tcx.erase_regions(ty),
|
|
|
|
|
into_iterator_did,
|
|
|
|
|
);
|
|
|
|
|
|
2023-03-14 14:19:06 +01:00
|
|
|
ocx.select_all_or_error().is_empty()
|
2022-07-24 23:46:04 +04:00
|
|
|
}
|