diff --git a/compiler/rustc_hir_typeck/src/coercion.rs b/compiler/rustc_hir_typeck/src/coercion.rs index 3328177634b07..e11d721d223b9 100644 --- a/compiler/rustc_hir_typeck/src/coercion.rs +++ b/compiler/rustc_hir_typeck/src/coercion.rs @@ -36,11 +36,12 @@ //! ``` use crate::FnCtxt; +use rustc_ast::{LitKind, UnOp}; use rustc_errors::{codes::*, struct_span_code_err, Applicability, Diag, MultiSpan}; use rustc_hir as hir; use rustc_hir::def_id::DefId; use rustc_hir::intravisit::{self, Visitor}; -use rustc_hir::Expr; +use rustc_hir::{Expr, ExprField, ExprKind, LangItem, QPath}; use rustc_hir_analysis::hir_ty_lowering::HirTyLowerer; use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind}; use rustc_infer::infer::{Coercion, DefineOpaqueTypes, InferOk, InferResult}; @@ -57,6 +58,7 @@ use rustc_middle::ty::relate::RelateResult; use rustc_middle::ty::visit::TypeVisitableExt; use rustc_middle::ty::{self, GenericArgsRef, Ty, TyCtxt}; use rustc_session::parse::feature_err; +use rustc_span::source_map::Spanned; use rustc_span::symbol::sym; use rustc_span::DesugaringKind; use rustc_target::spec::abi::Abi; @@ -1688,6 +1690,23 @@ impl<'tcx, 'exprs, E: AsCoercionSite> CoerceMany<'tcx, 'exprs, E> { let hir::ExprKind::Loop(_, _, _, loop_span) = expr.kind else { return; }; + + let hir = tcx.hir(); + let parent_node = tcx.hir_node(hir.get_parent_item(expr.hir_id).into()); + let parent_block = if let Some(body_id) = parent_node.body_id() + && let hir::ExprKind::Block(block, _) = hir.body(body_id).value.kind + { + Some(block) + } else { + None + }; + + if let Some(block) = parent_block + && Self::loop_iterates_atleast_once(block) + { + return; + } + let mut span: MultiSpan = vec![loop_span].into(); span.push_span_label(loop_span, "this might have zero elements to iterate on"); const MAXITER: usize = 3; @@ -1708,15 +1727,11 @@ impl<'tcx, 'exprs, E: AsCoercionSite> CoerceMany<'tcx, 'exprs, E> { ret_exprs.len() - MAXITER )); } - let hir = tcx.hir(); - let item = hir.get_parent_item(expr.hir_id); let ret_msg = "return a value for the case when the loop has zero elements to iterate on"; let ret_ty_msg = "otherwise consider changing the return type to account for that possibility"; - let node = tcx.hir_node(item.into()); - if let Some(body_id) = node.body_id() - && let Some(sig) = node.fn_sig() - && let hir::ExprKind::Block(block, _) = hir.body(body_id).value.kind + if let Some(block) = parent_block + && let Some(sig) = parent_node.fn_sig() && !ty.is_never() { let indentation = if let None = block.expr @@ -1781,6 +1796,97 @@ impl<'tcx, 'exprs, E: AsCoercionSite> CoerceMany<'tcx, 'exprs, E> { } } + /// Returns `true` if the given `block` is a loop + /// and it will definitely iterate at least once + fn loop_iterates_atleast_once(block: &hir::Block<'tcx>) -> bool { + // Check if `block` is a for loop and extract + // the expr it iterate over as `iter_target` + let Some(Expr { + kind: + ExprKind::DropTemps(Expr { + kind: + ExprKind::Match( + Expr { + kind: + ExprKind::Call( + Expr { + kind: + ExprKind::Path( + QPath::LangItem(LangItem::IntoIterIntoIter, ..), + .., + ), + .. + }, + [Expr { kind: iter_target, .. }], + ), + .. + }, + .., + ), + .. + }), + .. + }) = block.expr + else { + return false; // Block is not a for loop + }; + + // Peel away any ref if present + let iter_target = match iter_target { + ExprKind::AddrOf(.., deref) => deref.kind, + _ => *iter_target, + }; + + // Computes value of a literal expr + // Takes into account any enclosing neg unary expr if present + fn get_literal<'a>(expr_kind: &ExprKind<'a>) -> Option { + match expr_kind { + ExprKind::Lit(Spanned { node: LitKind::Int(lit, ..), .. }) => { + i128::try_from(lit.get()).ok() + } + ExprKind::Unary( + UnOp::Neg, + Expr { + kind: ExprKind::Lit(Spanned { node: LitKind::Int(lit, ..), .. }), .. + }, + ) => i128::try_from(lit.get()).map(|v| -1 * v).ok(), + _ => None, + } + } + + // Check if `iter_target` will be iterated over at least once. + // We support only exclusive, inclusive and infinite range + // literals and array literals + match iter_target { + // Exclusive range + ExprKind::Struct( + QPath::LangItem(LangItem::Range, ..), + [ + ExprField { expr: Expr { kind: start, .. }, .. }, + ExprField { expr: Expr { kind: end, .. }, .. }, + ], + .., + ) => match (get_literal(start), get_literal(end)) { + (Some(start), Some(end)) => end > start, + _ => false, + }, + // Inclusive range + ExprKind::Call( + Expr { + kind: ExprKind::Path(QPath::LangItem(LangItem::RangeInclusiveNew, ..)), .. + }, + [Expr { kind: start, .. }, Expr { kind: end, .. }], + ) => match (get_literal(start), get_literal(end)) { + (Some(start), Some(end)) => end >= start, + _ => false, + }, + // Infinite range + ExprKind::Struct(QPath::LangItem(LangItem::RangeFrom, ..), ..) => true, + ExprKind::Array(items) => items.len() > 0, + _ => false, + } + } + fn report_return_mismatched_types<'a>( &self, cause: &ObligationCause<'tcx>, diff --git a/tests/ui/coercion/coerce-for-loop-issue-122561.rs b/tests/ui/coercion/coerce-for-loop-issue-122561.rs new file mode 100644 index 0000000000000..0f3f3463f2240 --- /dev/null +++ b/tests/ui/coercion/coerce-for-loop-issue-122561.rs @@ -0,0 +1,126 @@ +// Regression test for #122561 +// Checks that we do our best not to emit the note +// saying the loop might run zero times if it is +// clear it will certainly run at least once + + +// ----- Should not emit note for these ----- +fn atleast_once_iter_range() -> bool { + for i in 0..3 { + //~^ ERROR mismatched types + return false; + } +} + +fn atleast_once_iter_range_neg() -> bool { + for i in -3..-1 { + //~^ ERROR mismatched types + return false; + } +} + +fn atleast_once_iter_range_inclusive() -> bool { + for i in 0..=3 { + //~^ ERROR mismatched types + return false; + } +} + +fn atleast_once_iter_range_inclusive_neg() -> bool { + for i in -3..=-1 { + //~^ ERROR mismatched types + return false; + } +} + +fn atleast_once_iter_infinite_range() -> bool { + for i in 0.. { + //~^ ERROR mismatched types + return false; + } +} + +fn atleast_once_iter_infinite_range_neg() -> bool { + for i in -3.. { + //~^ ERROR mismatched types + return false; + } +} + +fn atleast_once_iter_array() -> bool { + for i in [1, 2, 3] { + //~^ ERROR mismatched types + return false; + } +} + +fn atleast_once_iter_array_ref() -> bool { + for i in &[1, 2, 3] { + //~^ ERROR mismatched types + return false; + } +} + + +// ----- Should emit note for these ----- +fn zero_iter_range() -> bool { + for i in 0..0 { + //~^ ERROR mismatched types + return false; + } +} + +fn zero_iter_array() -> bool { + for i in [] { + //~^ ERROR mismatched types + return false; + } +} + +fn zero_iter_array_ref() -> bool { + for i in &[] { + //~^ ERROR mismatched types + return false; + } +} + +// For the following cases the loop does iterate at +// least once but we aren't currently smart enough +// to not emit the note for them. We might add such +// smarts in the future +fn atlast_once_iter_array_var() -> bool { + let x = [1, 2, 3]; + for i in x { + //~^ ERROR mismatched types + return false; + } +} + +fn atleast_once_iter_vec() -> bool { + for i in vec![1, 2, 3] { + //~^ ERROR mismatched types + return false; + } +} + +fn atleast_once_iter_array_iter() -> bool { + for i in [1, 2, 3].iter() { + //~^ ERROR mismatched types + return false; + } +} + +fn atleast_once_iter_func_result() -> bool { + for i in get_iter() { + //~^ ERROR mismatched types + return false; + } +} + + +// Helper function +fn get_iter() -> impl Iterator { + 1.. +} + +fn main() {} diff --git a/tests/ui/coercion/coerce-for-loop-issue-122561.stderr b/tests/ui/coercion/coerce-for-loop-issue-122561.stderr new file mode 100644 index 0000000000000..927aa6b8e9b53 --- /dev/null +++ b/tests/ui/coercion/coerce-for-loop-issue-122561.stderr @@ -0,0 +1,333 @@ +error[E0308]: mismatched types + --> $DIR/coerce-for-loop-issue-122561.rs:9:5 + | +LL | fn atleast_once_iter_range() -> bool { + | ---- expected `bool` because of return type +LL | / for i in 0..3 { +LL | | +LL | | return false; +LL | | } + | |_____^ expected `bool`, found `()` + +error[E0308]: mismatched types + --> $DIR/coerce-for-loop-issue-122561.rs:16:5 + | +LL | fn atleast_once_iter_range_neg() -> bool { + | ---- expected `bool` because of return type +LL | / for i in -3..-1 { +LL | | +LL | | return false; +LL | | } + | |_____^ expected `bool`, found `()` + +error[E0308]: mismatched types + --> $DIR/coerce-for-loop-issue-122561.rs:23:5 + | +LL | fn atleast_once_iter_range_inclusive() -> bool { + | ---- expected `bool` because of return type +LL | / for i in 0..=3 { +LL | | +LL | | return false; +LL | | } + | |_____^ expected `bool`, found `()` + +error[E0308]: mismatched types + --> $DIR/coerce-for-loop-issue-122561.rs:30:5 + | +LL | fn atleast_once_iter_range_inclusive_neg() -> bool { + | ---- expected `bool` because of return type +LL | / for i in -3..=-1 { +LL | | +LL | | return false; +LL | | } + | |_____^ expected `bool`, found `()` + +error[E0308]: mismatched types + --> $DIR/coerce-for-loop-issue-122561.rs:37:5 + | +LL | fn atleast_once_iter_infinite_range() -> bool { + | ---- expected `bool` because of return type +LL | / for i in 0.. { +LL | | +LL | | return false; +LL | | } + | |_____^ expected `bool`, found `()` + +error[E0308]: mismatched types + --> $DIR/coerce-for-loop-issue-122561.rs:44:5 + | +LL | fn atleast_once_iter_infinite_range_neg() -> bool { + | ---- expected `bool` because of return type +LL | / for i in -3.. { +LL | | +LL | | return false; +LL | | } + | |_____^ expected `bool`, found `()` + +error[E0308]: mismatched types + --> $DIR/coerce-for-loop-issue-122561.rs:51:5 + | +LL | fn atleast_once_iter_array() -> bool { + | ---- expected `bool` because of return type +LL | / for i in [1, 2, 3] { +LL | | +LL | | return false; +LL | | } + | |_____^ expected `bool`, found `()` + +error[E0308]: mismatched types + --> $DIR/coerce-for-loop-issue-122561.rs:58:5 + | +LL | fn atleast_once_iter_array_ref() -> bool { + | ---- expected `bool` because of return type +LL | / for i in &[1, 2, 3] { +LL | | +LL | | return false; +LL | | } + | |_____^ expected `bool`, found `()` + +error[E0308]: mismatched types + --> $DIR/coerce-for-loop-issue-122561.rs:67:5 + | +LL | fn zero_iter_range() -> bool { + | ---- expected `bool` because of return type +LL | / for i in 0..0 { +LL | | +LL | | return false; +LL | | } + | |_____^ expected `bool`, found `()` + | +note: the function expects a value to always be returned, but loops might run zero times + --> $DIR/coerce-for-loop-issue-122561.rs:67:5 + | +LL | for i in 0..0 { + | ^^^^^^^^^^^^^ this might have zero elements to iterate on +LL | +LL | return false; + | ------------ if the loop doesn't execute, this value would never get returned +help: return a value for the case when the loop has zero elements to iterate on + | +LL ~ } +LL + /* `bool` value */ + | +help: otherwise consider changing the return type to account for that possibility + | +LL ~ fn zero_iter_range() -> Option { +LL | for i in 0..0 { +LL | +LL ~ return Some(false); +LL ~ } +LL + None + | + +error[E0308]: mismatched types + --> $DIR/coerce-for-loop-issue-122561.rs:74:5 + | +LL | fn zero_iter_array() -> bool { + | ---- expected `bool` because of return type +LL | / for i in [] { +LL | | +LL | | return false; +LL | | } + | |_____^ expected `bool`, found `()` + | +note: the function expects a value to always be returned, but loops might run zero times + --> $DIR/coerce-for-loop-issue-122561.rs:74:5 + | +LL | for i in [] { + | ^^^^^^^^^^^ this might have zero elements to iterate on +LL | +LL | return false; + | ------------ if the loop doesn't execute, this value would never get returned +help: return a value for the case when the loop has zero elements to iterate on + | +LL ~ } +LL + /* `bool` value */ + | +help: otherwise consider changing the return type to account for that possibility + | +LL ~ fn zero_iter_array() -> Option { +LL | for i in [] { +LL | +LL ~ return Some(false); +LL ~ } +LL + None + | + +error[E0308]: mismatched types + --> $DIR/coerce-for-loop-issue-122561.rs:81:5 + | +LL | fn zero_iter_array_ref() -> bool { + | ---- expected `bool` because of return type +LL | / for i in &[] { +LL | | +LL | | return false; +LL | | } + | |_____^ expected `bool`, found `()` + | +note: the function expects a value to always be returned, but loops might run zero times + --> $DIR/coerce-for-loop-issue-122561.rs:81:5 + | +LL | for i in &[] { + | ^^^^^^^^^^^^ this might have zero elements to iterate on +LL | +LL | return false; + | ------------ if the loop doesn't execute, this value would never get returned +help: return a value for the case when the loop has zero elements to iterate on + | +LL ~ } +LL + /* `bool` value */ + | +help: otherwise consider changing the return type to account for that possibility + | +LL ~ fn zero_iter_array_ref() -> Option { +LL | for i in &[] { +LL | +LL ~ return Some(false); +LL ~ } +LL + None + | + +error[E0308]: mismatched types + --> $DIR/coerce-for-loop-issue-122561.rs:93:5 + | +LL | fn atlast_once_iter_array_var() -> bool { + | ---- expected `bool` because of return type +LL | let x = [1, 2, 3]; +LL | / for i in x { +LL | | +LL | | return false; +LL | | } + | |_____^ expected `bool`, found `()` + | +note: the function expects a value to always be returned, but loops might run zero times + --> $DIR/coerce-for-loop-issue-122561.rs:93:5 + | +LL | for i in x { + | ^^^^^^^^^^ this might have zero elements to iterate on +LL | +LL | return false; + | ------------ if the loop doesn't execute, this value would never get returned +help: return a value for the case when the loop has zero elements to iterate on + | +LL ~ } +LL + /* `bool` value */ + | +help: otherwise consider changing the return type to account for that possibility + | +LL ~ fn atlast_once_iter_array_var() -> Option { +LL | let x = [1, 2, 3]; +LL | for i in x { +LL | +LL ~ return Some(false); +LL ~ } +LL + None + | + +error[E0308]: mismatched types + --> $DIR/coerce-for-loop-issue-122561.rs:100:5 + | +LL | fn atleast_once_iter_vec() -> bool { + | ---- expected `bool` because of return type +LL | / for i in vec![1, 2, 3] { +LL | | +LL | | return false; +LL | | } + | |_____^ expected `bool`, found `()` + | +note: the function expects a value to always be returned, but loops might run zero times + --> $DIR/coerce-for-loop-issue-122561.rs:100:5 + | +LL | | } + | |_________^ this might have zero elements to iterate on +... +LL | / for i in vec![1, 2, 3] { +LL | +LL | return false; + | ------------ if the loop doesn't execute, this value would never get returned +help: return a value for the case when the loop has zero elements to iterate on + | +LL ~ } +LL + /* `bool` value */ + | +help: otherwise consider changing the return type to account for that possibility + | +LL ~ fn atleast_once_iter_vec() -> Option { +LL | for i in vec![1, 2, 3] { +LL | +LL ~ return Some(false); +LL ~ } +LL + None + | + +error[E0308]: mismatched types + --> $DIR/coerce-for-loop-issue-122561.rs:107:5 + | +LL | fn atleast_once_iter_array_iter() -> bool { + | ---- expected `bool` because of return type +LL | / for i in [1, 2, 3].iter() { +LL | | +LL | | return false; +LL | | } + | |_____^ expected `bool`, found `()` + | +note: the function expects a value to always be returned, but loops might run zero times + --> $DIR/coerce-for-loop-issue-122561.rs:107:5 + | +LL | for i in [1, 2, 3].iter() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^ this might have zero elements to iterate on +LL | +LL | return false; + | ------------ if the loop doesn't execute, this value would never get returned +help: return a value for the case when the loop has zero elements to iterate on + | +LL ~ } +LL + /* `bool` value */ + | +help: otherwise consider changing the return type to account for that possibility + | +LL ~ fn atleast_once_iter_array_iter() -> Option { +LL | for i in [1, 2, 3].iter() { +LL | +LL ~ return Some(false); +LL ~ } +LL + None + | + +error[E0308]: mismatched types + --> $DIR/coerce-for-loop-issue-122561.rs:114:5 + | +LL | fn atleast_once_iter_func_result() -> bool { + | ---- expected `bool` because of return type +LL | / for i in get_iter() { +LL | | +LL | | return false; +LL | | } + | |_____^ expected `bool`, found `()` + | +note: the function expects a value to always be returned, but loops might run zero times + --> $DIR/coerce-for-loop-issue-122561.rs:114:5 + | +LL | for i in get_iter() { + | ^^^^^^^^^^^^^^^^^^^ this might have zero elements to iterate on +LL | +LL | return false; + | ------------ if the loop doesn't execute, this value would never get returned +help: return a value for the case when the loop has zero elements to iterate on + | +LL ~ } +LL + /* `bool` value */ + | +help: otherwise consider changing the return type to account for that possibility + | +LL ~ fn atleast_once_iter_func_result() -> Option { +LL | for i in get_iter() { +LL | +LL ~ return Some(false); +LL ~ } +LL + None + | + +error: aborting due to 15 previous errors + +For more information about this error, try `rustc --explain E0308`.