From 62755d739568cb4c539d12719ff48a089aa8f8ff Mon Sep 17 00:00:00 2001 From: sgasho Date: Mon, 4 May 2026 18:45:22 +0900 Subject: [PATCH 1/3] old approach (squashed for revert) --- compiler/rustc_hir_typeck/src/_if.rs | 297 ++++++++++++++++++ compiler/rustc_hir_typeck/src/expr.rs | 72 +---- .../src/fn_ctxt/suggestions.rs | 6 + compiler/rustc_hir_typeck/src/lib.rs | 1 + .../src/error_reporting/infer/mod.rs | 38 ++- .../ui/expr/if/if-else-chain-missing-else.rs | 11 +- .../expr/if/if-else-chain-missing-else.stderr | 12 +- ...else-if-incompatible-types-issue-146190.rs | 15 + ...-if-incompatible-types-issue-146190.stderr | 22 ++ tests/ui/inference/deref-suggestion.rs | 2 +- tests/ui/inference/deref-suggestion.stderr | 28 +- .../ui/typeck/consider-borrowing-141810-1.rs | 3 +- .../typeck/consider-borrowing-141810-1.stderr | 26 +- .../typeck/consider-borrowing-141810-2.stderr | 5 +- .../typeck/consider-borrowing-141810-3.stderr | 7 +- 15 files changed, 413 insertions(+), 132 deletions(-) create mode 100644 compiler/rustc_hir_typeck/src/_if.rs create mode 100644 tests/ui/expr/if/if-multi-else-if-incompatible-types-issue-146190.rs create mode 100644 tests/ui/expr/if/if-multi-else-if-incompatible-types-issue-146190.stderr diff --git a/compiler/rustc_hir_typeck/src/_if.rs b/compiler/rustc_hir_typeck/src/_if.rs new file mode 100644 index 0000000000000..20c6fb0ce4da1 --- /dev/null +++ b/compiler/rustc_hir_typeck/src/_if.rs @@ -0,0 +1,297 @@ +use rustc_hir::{self as hir, HirId}; +use rustc_infer::traits; +use rustc_middle::ty::{Ty, TypeVisitableExt}; +use rustc_span::{ErrorGuaranteed, Span}; +use smallvec::SmallVec; + +use crate::coercion::{CoerceMany, DynamicCoerceMany}; +use crate::{Diverges, Expectation, FnCtxt}; + +#[derive(Clone, Copy, Debug)] +pub(crate) struct IfExprParts<'tcx> { + pub cond: &'tcx hir::Expr<'tcx>, + pub then: &'tcx hir::Expr<'tcx>, + pub else_branch: Option<&'tcx hir::Expr<'tcx>>, +} + +#[derive(Clone, Copy, Debug)] +struct IfExprWithParts<'tcx> { + expr: &'tcx hir::Expr<'tcx>, + parts: IfExprParts<'tcx>, +} + +#[derive(Clone, Debug, Default)] +struct IfChain<'tcx> { + guarded_branches: SmallVec<[IfGuardedBranch<'tcx>; 4]>, + tail: IfChainTail<'tcx>, + error: Option, +} + +impl<'tcx> IfChain<'tcx> { + fn last_expr(&self) -> Option<&'tcx hir::Expr<'tcx>> { + if let IfChainTail::FinalElse(final_else) = &self.tail { + final_else.expr.into() + } else { + self.guarded_branches.last().map(|l| l.expr_with_parts.expr) + } + } +} + +#[derive(Clone, Debug)] +struct IfGuardedBranch<'tcx> { + expr_with_parts: IfExprWithParts<'tcx>, + cond_diverges: Diverges, + body: BranchBody<'tcx>, +} + +#[derive(Clone, Debug)] +enum IfChainTail<'tcx> { + FinalElse(BranchBody<'tcx>), + Missing, +} + +#[derive(Clone, Debug)] +struct BranchBody<'tcx> { + expr: &'tcx hir::Expr<'tcx>, + ty: Ty<'tcx>, + diverges: Diverges, + span: Span, +} + +impl<'tcx> Default for IfChainTail<'tcx> { + fn default() -> Self { + IfChainTail::Missing + } +} + +impl<'tcx> IfChainTail<'tcx> { + fn diverges(&self) -> Diverges { + match &self { + IfChainTail::FinalElse(else_branch) => else_branch.diverges, + IfChainTail::Missing => Diverges::Maybe, + } + } +} + +impl<'a, 'tcx> FnCtxt<'a, 'tcx> { + pub(crate) fn check_expr_if( + &self, + expr_id: HirId, + sp: Span, + parts: &IfExprParts<'tcx>, + orig_expected: Expectation<'tcx>, + ) -> Ty<'tcx> { + let root_if_expr = self.tcx.hir_expect_expr(expr_id); + let expected = orig_expected.try_structurally_resolve_and_adjust_for_branches(self, sp); + + let initial_diverges = self.diverges.get(); + + let chain = + self.collect_if_chain(&IfExprWithParts { expr: root_if_expr, parts: *parts }, expected); + + let coerce_to_ty = expected.coercion_target_type(self, sp); + let mut coerce: DynamicCoerceMany<'_> = CoerceMany::new(coerce_to_ty); + + let tail_defines_return_position_impl_trait = + self.return_position_impl_trait_from_match_expectation(orig_expected); + + for (idx, branch) in chain.guarded_branches.iter().enumerate() { + if idx > 0 { + let merged_ty = coerce.merged_ty(); + self.ensure_if_branch_type(branch.expr_with_parts.expr.hir_id, merged_ty); + } + + let branch_body = &branch.body; + let next_else_expr = chain + .guarded_branches + .get(idx + 1) + .map(|next| next.expr_with_parts.expr) + .or(chain.last_expr().into()); + let opt_prev_branch = if idx > 0 { chain.guarded_branches.get(idx - 1) } else { None }; + let mut branch_cause = if let Some(next_else_expr) = next_else_expr { + self.if_cause( + opt_prev_branch.unwrap_or(branch).expr_with_parts.expr.hir_id, + next_else_expr, + tail_defines_return_position_impl_trait, + ) + } else { + self.misc(branch_body.span) + }; + let cause_span = + if idx == 0 { Some(root_if_expr.span) } else { Some(branch_body.span) }; + + self.coerce_if_arm( + &mut coerce, + &mut branch_cause, + branch_body.expr, + branch_body.ty, + cause_span, + opt_prev_branch.and_then(|b| b.body.span.into()), + ); + } + + match &chain.tail { + IfChainTail::FinalElse(else_branch) => { + let mut else_cause = self.if_cause( + expr_id, + else_branch.expr, + tail_defines_return_position_impl_trait, + ); + self.coerce_if_arm( + &mut coerce, + &mut else_cause, + else_branch.expr, + else_branch.ty, + None, + chain.guarded_branches.last().and_then(|b| b.body.span.into()), + ); + } + IfChainTail::Missing => { + let last_if = chain.guarded_branches.last().map(|l| l.expr_with_parts).unwrap(); + self.if_fallback_coercion( + last_if.expr.span, + last_if.parts.cond, + last_if.parts.then, + &mut coerce, + ); + } + } + + let mut tail_diverges = chain.tail.diverges(); + for branch in chain.guarded_branches.iter().rev() { + tail_diverges = branch.cond_diverges | (branch.body.diverges & tail_diverges); + } + self.diverges.set(initial_diverges | tail_diverges); + + let result_ty = coerce.complete(self); + + let final_ty = + if let Some(guar) = chain.error { Ty::new_error(self.tcx, guar) } else { result_ty }; + + for branch in chain.guarded_branches.iter().skip(1) { + self.overwrite_if_branch_type(branch.expr_with_parts.expr.hir_id, final_ty); + } + if let Err(guar) = final_ty.error_reported() { + self.set_tainted_by_errors(guar); + } + + final_ty + } + + fn coerce_if_arm( + &self, + coerce: &mut DynamicCoerceMany<'tcx>, + cause: &mut traits::ObligationCause<'tcx>, + expr: &'tcx hir::Expr<'tcx>, + ty: Ty<'tcx>, + cause_span: Option, + prev_branch_span: Option, + ) { + if let Some(span) = cause_span { + cause.span = span; + } + coerce.coerce_inner( + self, + cause, + Some(expr), + ty, + move |err| { + if let Some(prev_branch_span) = prev_branch_span { + err.span_label(prev_branch_span, "expected because of this"); + } + }, + false, + ); + } + + fn check_if_condition( + &self, + cond_expr: &'tcx hir::Expr<'tcx>, + then_span: Span, + ) -> (Ty<'tcx>, Diverges) { + let cond_ty = self.check_expr_has_type_or_error(cond_expr, self.tcx.types.bool, |_| {}); + self.warn_if_unreachable( + cond_expr.hir_id, + then_span, + "block in `if` or `while` expression", + ); + let cond_diverges = self.diverges.get(); + self.diverges.set(Diverges::Maybe); + (cond_ty, cond_diverges) + } + + fn collect_if_chain( + &self, + expr_with_parts: &IfExprWithParts<'tcx>, + expected: Expectation<'tcx>, + ) -> IfChain<'tcx> { + let mut chain: IfChain<'tcx> = IfChain::default(); + let mut current = *expr_with_parts; + + loop { + self.collect_if_branch(¤t, expected, &mut chain); + + match current.parts.else_branch { + Some(current_else_branch) => { + if let hir::ExprKind::If(cond, then, else_branch) = current_else_branch.kind { + current = IfExprWithParts { + expr: current_else_branch, + parts: IfExprParts { cond, then, else_branch }, + }; + } else { + chain.tail = IfChainTail::FinalElse( + self.check_branch_body(current_else_branch, expected), + ); + return chain; + } + } + None => return chain, + } + } + } + + fn collect_if_branch( + &self, + expr_with_parts: &IfExprWithParts<'tcx>, + expected: Expectation<'tcx>, + chain: &mut IfChain<'tcx>, + ) { + let (cond_ty, cond_diverges) = + self.check_if_condition(expr_with_parts.parts.cond, expr_with_parts.parts.then.span); + if let Err(guar) = cond_ty.error_reported() { + chain.error.get_or_insert(guar); + } + let branch_body = self.check_branch_body(expr_with_parts.parts.then, expected); + + chain.guarded_branches.push(IfGuardedBranch { + expr_with_parts: *expr_with_parts, + cond_diverges, + body: branch_body, + }); + } + + fn ensure_if_branch_type(&self, hir_id: HirId, ty: Ty<'tcx>) { + let mut typeck = self.typeck_results.borrow_mut(); + let mut node_ty = typeck.node_types_mut(); + node_ty.entry(hir_id).or_insert(ty); + } + + fn overwrite_if_branch_type(&self, hir_id: HirId, ty: Ty<'tcx>) { + let mut typeck = self.typeck_results.borrow_mut(); + let mut node_ty = typeck.node_types_mut(); + node_ty.insert(hir_id, ty); + } + + fn check_branch_body( + &self, + expr: &'tcx hir::Expr<'tcx>, + expected: Expectation<'tcx>, + ) -> BranchBody<'tcx> { + self.diverges.set(Diverges::Maybe); + let ty = self.check_expr_with_expectation(expr, expected); + let diverges = self.diverges.get(); + self.diverges.set(Diverges::Maybe); + let span = self.find_block_span_from_hir_id(expr.hir_id); + BranchBody { expr, ty, diverges, span } + } +} diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs index fd9c1bc8780ee..f370b2d0a3ee5 100644 --- a/compiler/rustc_hir_typeck/src/expr.rs +++ b/compiler/rustc_hir_typeck/src/expr.rs @@ -38,6 +38,7 @@ use rustc_trait_selection::infer::InferCtxtExt; use rustc_trait_selection::traits::{self, ObligationCauseCode, ObligationCtxt}; use tracing::{debug, instrument, trace}; +use crate::_if::IfExprParts; use crate::Expectation::{self, ExpectCastableToType, ExpectHasType, NoExpectation}; use crate::coercion::CoerceMany; use crate::errors::{ @@ -378,8 +379,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.demand_eqtype(e.span, ascribed_ty, ty); ascribed_ty } - ExprKind::If(cond, then_expr, opt_else_expr) => { - self.check_expr_if(expr.hir_id, cond, then_expr, opt_else_expr, expr.span, expected) + ExprKind::If(cond, then_expr, else_branch) => { + let parts = IfExprParts { cond, then: then_expr, else_branch }; + self.check_expr_if(expr.hir_id, expr.span, &parts, expected) } ExprKind::DropTemps(e) => self.check_expr_with_expectation(e, expected), ExprKind::Array(args) => self.check_expr_array(args, expected, expr), @@ -1165,72 +1167,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } - // A generic function for checking the 'then' and 'else' clauses in an 'if' - // or 'if-else' expression. - fn check_expr_if( - &self, - expr_id: HirId, - cond_expr: &'tcx hir::Expr<'tcx>, - then_expr: &'tcx hir::Expr<'tcx>, - opt_else_expr: Option<&'tcx hir::Expr<'tcx>>, - sp: Span, - orig_expected: Expectation<'tcx>, - ) -> Ty<'tcx> { - let cond_ty = self.check_expr_has_type_or_error(cond_expr, self.tcx.types.bool, |_| {}); - - self.warn_if_unreachable( - cond_expr.hir_id, - then_expr.span, - "block in `if` or `while` expression", - ); - - let cond_diverges = self.diverges.get(); - self.diverges.set(Diverges::Maybe); - - let expected = orig_expected.try_structurally_resolve_and_adjust_for_branches(self); - let then_ty = self.check_expr_with_expectation(then_expr, expected); - let then_diverges = self.diverges.get(); - self.diverges.set(Diverges::Maybe); - - // We've already taken the expected type's preferences - // into account when typing the `then` branch. To figure - // out the initial shot at a LUB, we thus only consider - // `expected` if it represents a *hard* constraint - // (`only_has_type`); otherwise, we just go with a - // fresh type variable. - let coerce_to_ty = expected.coercion_target_type(self, sp); - let mut coerce = CoerceMany::with_capacity(coerce_to_ty, 2); - - coerce.coerce(self, &self.misc(sp), then_expr, then_ty); - - if let Some(else_expr) = opt_else_expr { - let else_ty = self.check_expr_with_expectation(else_expr, expected); - let else_diverges = self.diverges.get(); - - let tail_defines_return_position_impl_trait = - self.return_position_impl_trait_from_match_expectation(orig_expected); - let if_cause = - self.if_cause(expr_id, else_expr, tail_defines_return_position_impl_trait); - - coerce.coerce(self, &if_cause, else_expr, else_ty); - - // We won't diverge unless both branches do (or the condition does). - self.diverges.set(cond_diverges | then_diverges & else_diverges); - } else { - self.if_fallback_coercion(sp, cond_expr, then_expr, &mut coerce); - - // If the condition is false we can't diverge. - self.diverges.set(cond_diverges); - } - - let result_ty = coerce.complete(self); - if let Err(guar) = cond_ty.error_reported() { - Ty::new_error(self.tcx, guar) - } else { - result_ty - } - } - /// Type check assignment expression `expr` of form `lhs = rhs`. /// The expected type is `()` and is passed to the function for the purposes of diagnostics. fn check_expr_assign( diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs index afd5356d5a1e7..ef15d6b964230 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs @@ -2865,6 +2865,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // `ExprKind::DropTemps` is semantically irrelevant for these suggestions. let expr = expr.peel_drop_temps(); + if let hir::ExprKind::Block(block, _) = &expr.kind + && block.expr.is_none() + { + return None; + } + match (&expr.kind, expected.kind(), checked_ty.kind()) { (_, &ty::Ref(_, exp, _), &ty::Ref(_, check, _)) => match (exp.kind(), check.kind()) { (&ty::Str, &ty::Array(arr, _) | &ty::Slice(arr)) if arr == self.tcx.types.u8 => { diff --git a/compiler/rustc_hir_typeck/src/lib.rs b/compiler/rustc_hir_typeck/src/lib.rs index 15729bc311e57..e7b3a24e2de9a 100644 --- a/compiler/rustc_hir_typeck/src/lib.rs +++ b/compiler/rustc_hir_typeck/src/lib.rs @@ -6,6 +6,7 @@ #![feature(trim_prefix_suffix)] // tidy-alphabetical-end +mod _if; mod _match; mod autoderef; mod callee; diff --git a/compiler/rustc_trait_selection/src/error_reporting/infer/mod.rs b/compiler/rustc_trait_selection/src/error_reporting/infer/mod.rs index 21951cee6d5ab..8ceed03d5befa 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/infer/mod.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/infer/mod.rs @@ -528,19 +528,29 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { .expect("if expression only expected inside FnCtxt") .expr_ty(then_expr); let else_span = self.find_block_span_from_hir_id(else_expr.hir_id); - let else_ty = self + let mut else_ty = self .typeck_results .as_ref() .expect("if expression only expected inside FnCtxt") - .expr_ty(else_expr); - if let hir::ExprKind::If(_cond, _then, None) = else_expr.kind + .expr_ty_opt(else_expr); + + if else_ty.is_none() + && let Some(exp_found) = exp_found + { + let exp_found = ty::error::ExpectedFound { + expected: self.resolve_vars_if_possible(exp_found.expected), + found: self.resolve_vars_if_possible(exp_found.found), + }; + else_ty.get_or_insert(exp_found.found); + } + + if let (Some(else_ty), hir::ExprKind::If(.., None)) = (else_ty, &else_expr.kind) && else_ty.is_unit() { // Account for `let x = if a { 1 } else if b { 2 };` err.note("`if` expressions without `else` evaluate to `()`"); err.note("consider adding an `else` block that evaluates to the expected type"); } - err.span_label(then_span, "expected because of this"); let outer_span = if self.tcx.sess.source_map().is_multiline(expr_span) { if then_span.hi() == expr_span.hi() || else_span.hi() == expr_span.hi() { @@ -567,15 +577,17 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { } else { else_expr.hir_id }; - if let Some(subdiag) = self.suggest_remove_semi_or_return_binding( - Some(then_id), - then_ty, - then_span, - Some(else_id), - else_ty, - else_span, - ) { - err.subdiagnostic(subdiag); + if let Some(else_ty) = else_ty { + if let Some(subdiag) = self.suggest_remove_semi_or_return_binding( + Some(then_id), + then_ty, + then_span, + Some(else_id), + else_ty, + else_span, + ) { + err.subdiagnostic(subdiag); + } } } ObligationCauseCode::LetElse => { diff --git a/tests/ui/expr/if/if-else-chain-missing-else.rs b/tests/ui/expr/if/if-else-chain-missing-else.rs index 995aac07f2f76..e45939ff7beac 100644 --- a/tests/ui/expr/if/if-else-chain-missing-else.rs +++ b/tests/ui/expr/if/if-else-chain-missing-else.rs @@ -1,5 +1,10 @@ -enum Cause { Cause1, Cause2 } -struct MyErr { x: Cause } +enum Cause { + Cause1, + Cause2, +} +struct MyErr { + x: Cause, +} fn main() { _ = f(); @@ -9,7 +14,7 @@ fn f() -> Result { let res = could_fail(); let x = if let Ok(x) = res { x - } else if let Err(e) = res { //~ ERROR `if` and `else` + } else if let Err(e) = res { //~ ERROR `if` may be missing an `else` clause return Err(e); }; Ok(x) diff --git a/tests/ui/expr/if/if-else-chain-missing-else.stderr b/tests/ui/expr/if/if-else-chain-missing-else.stderr index 6c437120d391d..a21b9e135272d 100644 --- a/tests/ui/expr/if/if-else-chain-missing-else.stderr +++ b/tests/ui/expr/if/if-else-chain-missing-else.stderr @@ -1,10 +1,6 @@ -error[E0308]: `if` and `else` have incompatible types - --> $DIR/if-else-chain-missing-else.rs:12:12 +error[E0317]: `if` may be missing an `else` clause + --> $DIR/if-else-chain-missing-else.rs:17:12 | -LL | let x = if let Ok(x) = res { - | ------------------ `if` and `else` have incompatible types -LL | x - | - expected because of this LL | } else if let Err(e) = res { | ____________^ LL | | return Err(e); @@ -12,8 +8,8 @@ LL | | }; | |_____^ expected `i32`, found `()` | = note: `if` expressions without `else` evaluate to `()` - = note: consider adding an `else` block that evaluates to the expected type + = help: consider adding an `else` block that evaluates to the expected type error: aborting due to 1 previous error -For more information about this error, try `rustc --explain E0308`. +For more information about this error, try `rustc --explain E0317`. diff --git a/tests/ui/expr/if/if-multi-else-if-incompatible-types-issue-146190.rs b/tests/ui/expr/if/if-multi-else-if-incompatible-types-issue-146190.rs new file mode 100644 index 0000000000000..488bb76a9605a --- /dev/null +++ b/tests/ui/expr/if/if-multi-else-if-incompatible-types-issue-146190.rs @@ -0,0 +1,15 @@ +fn main() { + let oa = Some(1); + let oa2 = Some(1); + let oa3 = Some(1); + let v = if let Some(a) = oa { + Some(&a) + } else if let Some(a) = oa2 { + &Some(a) //~ ERROR `if` and `else` have incompatible types [E0308] + } else if let Some(a) = oa3 { + &Some(a) + } else { + None + }; + println!("{v:?}"); +} diff --git a/tests/ui/expr/if/if-multi-else-if-incompatible-types-issue-146190.stderr b/tests/ui/expr/if/if-multi-else-if-incompatible-types-issue-146190.stderr new file mode 100644 index 0000000000000..a846ac521186b --- /dev/null +++ b/tests/ui/expr/if/if-multi-else-if-incompatible-types-issue-146190.stderr @@ -0,0 +1,22 @@ +error[E0308]: `if` and `else` have incompatible types + --> $DIR/if-multi-else-if-incompatible-types-issue-146190.rs:8:9 + | +LL | let v = if let Some(a) = oa { + | ------------------- `if` and `else` have incompatible types +LL | Some(&a) + | -------- expected because of this +LL | } else if let Some(a) = oa2 { +LL | &Some(a) + | ^^^^^^^^ expected `Option<&{integer}>`, found `&Option<{integer}>` + | + = note: expected enum `Option<&{integer}>` + found reference `&Option<{integer}>` +help: try using `.as_ref()` to convert `&Option<{integer}>` to `Option<&{integer}>` + | +LL - &Some(a) +LL + Some(a).as_ref() + | + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0308`. diff --git a/tests/ui/inference/deref-suggestion.rs b/tests/ui/inference/deref-suggestion.rs index dc39cc9dbffb0..bed8a9d9ef637 100644 --- a/tests/ui/inference/deref-suggestion.rs +++ b/tests/ui/inference/deref-suggestion.rs @@ -67,8 +67,8 @@ fn main() { let val = if true { *a } else if true { - //~^ ERROR incompatible types b + //~^ ERROR incompatible types } else { &0 }; diff --git a/tests/ui/inference/deref-suggestion.stderr b/tests/ui/inference/deref-suggestion.stderr index 027902a9f31e2..efa1b5af3e9ef 100644 --- a/tests/ui/inference/deref-suggestion.stderr +++ b/tests/ui/inference/deref-suggestion.stderr @@ -165,20 +165,20 @@ LL | *b | + error[E0308]: `if` and `else` have incompatible types - --> $DIR/deref-suggestion.rs:69:12 - | -LL | let val = if true { - | ------- `if` and `else` have incompatible types -LL | *a - | -- expected because of this -LL | } else if true { - | ____________^ -LL | | -LL | | b -LL | | } else { -LL | | &0 -LL | | }; - | |_____^ expected `i32`, found `&{integer}` + --> $DIR/deref-suggestion.rs:70:9 + | +LL | let val = if true { + | ------- `if` and `else` have incompatible types +LL | *a + | -- expected because of this +LL | } else if true { +LL | b + | ^ expected `i32`, found `&{integer}` + | +help: consider dereferencing the borrow + | +LL | *b + | + error[E0308]: mismatched types --> $DIR/deref-suggestion.rs:81:15 diff --git a/tests/ui/typeck/consider-borrowing-141810-1.rs b/tests/ui/typeck/consider-borrowing-141810-1.rs index 94c2d69091517..3a339717a9751 100644 --- a/tests/ui/typeck/consider-borrowing-141810-1.rs +++ b/tests/ui/typeck/consider-borrowing-141810-1.rs @@ -1,8 +1,9 @@ fn main() { let x = if true { &true - } else if false { //~ ERROR `if` and `else` have incompatible types [E0308] + } else if false { true //~ HELP consider borrowing here + //~^ ERROR `if` and `else` have incompatible types [E0308] } else { true }; diff --git a/tests/ui/typeck/consider-borrowing-141810-1.stderr b/tests/ui/typeck/consider-borrowing-141810-1.stderr index 35ca6793eee0d..d83f58e612669 100644 --- a/tests/ui/typeck/consider-borrowing-141810-1.stderr +++ b/tests/ui/typeck/consider-borrowing-141810-1.stderr @@ -1,24 +1,18 @@ error[E0308]: `if` and `else` have incompatible types - --> $DIR/consider-borrowing-141810-1.rs:4:12 + --> $DIR/consider-borrowing-141810-1.rs:5:9 | -LL | let x = if true { - | ------- `if` and `else` have incompatible types -LL | &true - | ----- expected because of this -LL | } else if false { - | ____________^ -LL | | true -LL | | } else { -LL | | true -LL | | }; - | |_____^ expected `&bool`, found `bool` +LL | let x = if true { + | ------- `if` and `else` have incompatible types +LL | &true + | ----- expected because of this +LL | } else if false { +LL | true + | ^^^^ expected `&bool`, found `bool` | help: consider borrowing here | -LL ~ &true -LL | } else { -LL ~ &true - | +LL | &true + | + error: aborting due to 1 previous error diff --git a/tests/ui/typeck/consider-borrowing-141810-2.stderr b/tests/ui/typeck/consider-borrowing-141810-2.stderr index 44ecb5a4a945a..101fc03ac4ccf 100644 --- a/tests/ui/typeck/consider-borrowing-141810-2.stderr +++ b/tests/ui/typeck/consider-borrowing-141810-2.stderr @@ -1,14 +1,13 @@ error[E0308]: `if` and `else` have incompatible types - --> $DIR/consider-borrowing-141810-2.rs:4:12 + --> $DIR/consider-borrowing-141810-2.rs:4:21 | LL | let x = if true { | ------- `if` and `else` have incompatible types LL | &() | --- expected because of this LL | } else if false { - | ____________^ + | _____________________^ LL | | } else { -LL | | }; | |_____^ expected `&()`, found `()` error: aborting due to 1 previous error diff --git a/tests/ui/typeck/consider-borrowing-141810-3.stderr b/tests/ui/typeck/consider-borrowing-141810-3.stderr index 3adf8ba1a8924..34a0d5a044934 100644 --- a/tests/ui/typeck/consider-borrowing-141810-3.stderr +++ b/tests/ui/typeck/consider-borrowing-141810-3.stderr @@ -1,18 +1,15 @@ error[E0308]: `if` and `else` have incompatible types - --> $DIR/consider-borrowing-141810-3.rs:4:12 + --> $DIR/consider-borrowing-141810-3.rs:4:21 | LL | let x = if true { | ------- `if` and `else` have incompatible types LL | &() | --- expected because of this LL | } else if false { - | ____________^ + | _____________________^ LL | | LL | | }; | |_____^ expected `&()`, found `()` - | - = note: `if` expressions without `else` evaluate to `()` - = note: consider adding an `else` block that evaluates to the expected type error: aborting due to 1 previous error From b7077d4f143275a390184569b0706ba3fb838976 Mon Sep 17 00:00:00 2001 From: sgasho Date: Mon, 4 May 2026 18:50:17 +0900 Subject: [PATCH 2/3] Revert "old approach (squashed for revert)" This reverts commit ac49139fe0a60e54b19bd74196609ed64480614a. --- compiler/rustc_hir_typeck/src/_if.rs | 297 ------------------ compiler/rustc_hir_typeck/src/expr.rs | 72 ++++- .../src/fn_ctxt/suggestions.rs | 6 - compiler/rustc_hir_typeck/src/lib.rs | 1 - .../src/error_reporting/infer/mod.rs | 38 +-- .../ui/expr/if/if-else-chain-missing-else.rs | 11 +- .../expr/if/if-else-chain-missing-else.stderr | 12 +- ...else-if-incompatible-types-issue-146190.rs | 15 - ...-if-incompatible-types-issue-146190.stderr | 22 -- tests/ui/inference/deref-suggestion.rs | 2 +- tests/ui/inference/deref-suggestion.stderr | 28 +- .../ui/typeck/consider-borrowing-141810-1.rs | 3 +- .../typeck/consider-borrowing-141810-1.stderr | 26 +- .../typeck/consider-borrowing-141810-2.stderr | 5 +- .../typeck/consider-borrowing-141810-3.stderr | 7 +- 15 files changed, 132 insertions(+), 413 deletions(-) delete mode 100644 compiler/rustc_hir_typeck/src/_if.rs delete mode 100644 tests/ui/expr/if/if-multi-else-if-incompatible-types-issue-146190.rs delete mode 100644 tests/ui/expr/if/if-multi-else-if-incompatible-types-issue-146190.stderr diff --git a/compiler/rustc_hir_typeck/src/_if.rs b/compiler/rustc_hir_typeck/src/_if.rs deleted file mode 100644 index 20c6fb0ce4da1..0000000000000 --- a/compiler/rustc_hir_typeck/src/_if.rs +++ /dev/null @@ -1,297 +0,0 @@ -use rustc_hir::{self as hir, HirId}; -use rustc_infer::traits; -use rustc_middle::ty::{Ty, TypeVisitableExt}; -use rustc_span::{ErrorGuaranteed, Span}; -use smallvec::SmallVec; - -use crate::coercion::{CoerceMany, DynamicCoerceMany}; -use crate::{Diverges, Expectation, FnCtxt}; - -#[derive(Clone, Copy, Debug)] -pub(crate) struct IfExprParts<'tcx> { - pub cond: &'tcx hir::Expr<'tcx>, - pub then: &'tcx hir::Expr<'tcx>, - pub else_branch: Option<&'tcx hir::Expr<'tcx>>, -} - -#[derive(Clone, Copy, Debug)] -struct IfExprWithParts<'tcx> { - expr: &'tcx hir::Expr<'tcx>, - parts: IfExprParts<'tcx>, -} - -#[derive(Clone, Debug, Default)] -struct IfChain<'tcx> { - guarded_branches: SmallVec<[IfGuardedBranch<'tcx>; 4]>, - tail: IfChainTail<'tcx>, - error: Option, -} - -impl<'tcx> IfChain<'tcx> { - fn last_expr(&self) -> Option<&'tcx hir::Expr<'tcx>> { - if let IfChainTail::FinalElse(final_else) = &self.tail { - final_else.expr.into() - } else { - self.guarded_branches.last().map(|l| l.expr_with_parts.expr) - } - } -} - -#[derive(Clone, Debug)] -struct IfGuardedBranch<'tcx> { - expr_with_parts: IfExprWithParts<'tcx>, - cond_diverges: Diverges, - body: BranchBody<'tcx>, -} - -#[derive(Clone, Debug)] -enum IfChainTail<'tcx> { - FinalElse(BranchBody<'tcx>), - Missing, -} - -#[derive(Clone, Debug)] -struct BranchBody<'tcx> { - expr: &'tcx hir::Expr<'tcx>, - ty: Ty<'tcx>, - diverges: Diverges, - span: Span, -} - -impl<'tcx> Default for IfChainTail<'tcx> { - fn default() -> Self { - IfChainTail::Missing - } -} - -impl<'tcx> IfChainTail<'tcx> { - fn diverges(&self) -> Diverges { - match &self { - IfChainTail::FinalElse(else_branch) => else_branch.diverges, - IfChainTail::Missing => Diverges::Maybe, - } - } -} - -impl<'a, 'tcx> FnCtxt<'a, 'tcx> { - pub(crate) fn check_expr_if( - &self, - expr_id: HirId, - sp: Span, - parts: &IfExprParts<'tcx>, - orig_expected: Expectation<'tcx>, - ) -> Ty<'tcx> { - let root_if_expr = self.tcx.hir_expect_expr(expr_id); - let expected = orig_expected.try_structurally_resolve_and_adjust_for_branches(self, sp); - - let initial_diverges = self.diverges.get(); - - let chain = - self.collect_if_chain(&IfExprWithParts { expr: root_if_expr, parts: *parts }, expected); - - let coerce_to_ty = expected.coercion_target_type(self, sp); - let mut coerce: DynamicCoerceMany<'_> = CoerceMany::new(coerce_to_ty); - - let tail_defines_return_position_impl_trait = - self.return_position_impl_trait_from_match_expectation(orig_expected); - - for (idx, branch) in chain.guarded_branches.iter().enumerate() { - if idx > 0 { - let merged_ty = coerce.merged_ty(); - self.ensure_if_branch_type(branch.expr_with_parts.expr.hir_id, merged_ty); - } - - let branch_body = &branch.body; - let next_else_expr = chain - .guarded_branches - .get(idx + 1) - .map(|next| next.expr_with_parts.expr) - .or(chain.last_expr().into()); - let opt_prev_branch = if idx > 0 { chain.guarded_branches.get(idx - 1) } else { None }; - let mut branch_cause = if let Some(next_else_expr) = next_else_expr { - self.if_cause( - opt_prev_branch.unwrap_or(branch).expr_with_parts.expr.hir_id, - next_else_expr, - tail_defines_return_position_impl_trait, - ) - } else { - self.misc(branch_body.span) - }; - let cause_span = - if idx == 0 { Some(root_if_expr.span) } else { Some(branch_body.span) }; - - self.coerce_if_arm( - &mut coerce, - &mut branch_cause, - branch_body.expr, - branch_body.ty, - cause_span, - opt_prev_branch.and_then(|b| b.body.span.into()), - ); - } - - match &chain.tail { - IfChainTail::FinalElse(else_branch) => { - let mut else_cause = self.if_cause( - expr_id, - else_branch.expr, - tail_defines_return_position_impl_trait, - ); - self.coerce_if_arm( - &mut coerce, - &mut else_cause, - else_branch.expr, - else_branch.ty, - None, - chain.guarded_branches.last().and_then(|b| b.body.span.into()), - ); - } - IfChainTail::Missing => { - let last_if = chain.guarded_branches.last().map(|l| l.expr_with_parts).unwrap(); - self.if_fallback_coercion( - last_if.expr.span, - last_if.parts.cond, - last_if.parts.then, - &mut coerce, - ); - } - } - - let mut tail_diverges = chain.tail.diverges(); - for branch in chain.guarded_branches.iter().rev() { - tail_diverges = branch.cond_diverges | (branch.body.diverges & tail_diverges); - } - self.diverges.set(initial_diverges | tail_diverges); - - let result_ty = coerce.complete(self); - - let final_ty = - if let Some(guar) = chain.error { Ty::new_error(self.tcx, guar) } else { result_ty }; - - for branch in chain.guarded_branches.iter().skip(1) { - self.overwrite_if_branch_type(branch.expr_with_parts.expr.hir_id, final_ty); - } - if let Err(guar) = final_ty.error_reported() { - self.set_tainted_by_errors(guar); - } - - final_ty - } - - fn coerce_if_arm( - &self, - coerce: &mut DynamicCoerceMany<'tcx>, - cause: &mut traits::ObligationCause<'tcx>, - expr: &'tcx hir::Expr<'tcx>, - ty: Ty<'tcx>, - cause_span: Option, - prev_branch_span: Option, - ) { - if let Some(span) = cause_span { - cause.span = span; - } - coerce.coerce_inner( - self, - cause, - Some(expr), - ty, - move |err| { - if let Some(prev_branch_span) = prev_branch_span { - err.span_label(prev_branch_span, "expected because of this"); - } - }, - false, - ); - } - - fn check_if_condition( - &self, - cond_expr: &'tcx hir::Expr<'tcx>, - then_span: Span, - ) -> (Ty<'tcx>, Diverges) { - let cond_ty = self.check_expr_has_type_or_error(cond_expr, self.tcx.types.bool, |_| {}); - self.warn_if_unreachable( - cond_expr.hir_id, - then_span, - "block in `if` or `while` expression", - ); - let cond_diverges = self.diverges.get(); - self.diverges.set(Diverges::Maybe); - (cond_ty, cond_diverges) - } - - fn collect_if_chain( - &self, - expr_with_parts: &IfExprWithParts<'tcx>, - expected: Expectation<'tcx>, - ) -> IfChain<'tcx> { - let mut chain: IfChain<'tcx> = IfChain::default(); - let mut current = *expr_with_parts; - - loop { - self.collect_if_branch(¤t, expected, &mut chain); - - match current.parts.else_branch { - Some(current_else_branch) => { - if let hir::ExprKind::If(cond, then, else_branch) = current_else_branch.kind { - current = IfExprWithParts { - expr: current_else_branch, - parts: IfExprParts { cond, then, else_branch }, - }; - } else { - chain.tail = IfChainTail::FinalElse( - self.check_branch_body(current_else_branch, expected), - ); - return chain; - } - } - None => return chain, - } - } - } - - fn collect_if_branch( - &self, - expr_with_parts: &IfExprWithParts<'tcx>, - expected: Expectation<'tcx>, - chain: &mut IfChain<'tcx>, - ) { - let (cond_ty, cond_diverges) = - self.check_if_condition(expr_with_parts.parts.cond, expr_with_parts.parts.then.span); - if let Err(guar) = cond_ty.error_reported() { - chain.error.get_or_insert(guar); - } - let branch_body = self.check_branch_body(expr_with_parts.parts.then, expected); - - chain.guarded_branches.push(IfGuardedBranch { - expr_with_parts: *expr_with_parts, - cond_diverges, - body: branch_body, - }); - } - - fn ensure_if_branch_type(&self, hir_id: HirId, ty: Ty<'tcx>) { - let mut typeck = self.typeck_results.borrow_mut(); - let mut node_ty = typeck.node_types_mut(); - node_ty.entry(hir_id).or_insert(ty); - } - - fn overwrite_if_branch_type(&self, hir_id: HirId, ty: Ty<'tcx>) { - let mut typeck = self.typeck_results.borrow_mut(); - let mut node_ty = typeck.node_types_mut(); - node_ty.insert(hir_id, ty); - } - - fn check_branch_body( - &self, - expr: &'tcx hir::Expr<'tcx>, - expected: Expectation<'tcx>, - ) -> BranchBody<'tcx> { - self.diverges.set(Diverges::Maybe); - let ty = self.check_expr_with_expectation(expr, expected); - let diverges = self.diverges.get(); - self.diverges.set(Diverges::Maybe); - let span = self.find_block_span_from_hir_id(expr.hir_id); - BranchBody { expr, ty, diverges, span } - } -} diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs index f370b2d0a3ee5..1b44b7fa77f4b 100644 --- a/compiler/rustc_hir_typeck/src/expr.rs +++ b/compiler/rustc_hir_typeck/src/expr.rs @@ -38,7 +38,6 @@ use rustc_trait_selection::infer::InferCtxtExt; use rustc_trait_selection::traits::{self, ObligationCauseCode, ObligationCtxt}; use tracing::{debug, instrument, trace}; -use crate::_if::IfExprParts; use crate::Expectation::{self, ExpectCastableToType, ExpectHasType, NoExpectation}; use crate::coercion::CoerceMany; use crate::errors::{ @@ -379,9 +378,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.demand_eqtype(e.span, ascribed_ty, ty); ascribed_ty } - ExprKind::If(cond, then_expr, else_branch) => { - let parts = IfExprParts { cond, then: then_expr, else_branch }; - self.check_expr_if(expr.hir_id, expr.span, &parts, expected) + ExprKind::If(cond, then_expr, opt_else_expr) => { + self.check_expr_if(expr.hir_id, cond, then_expr, opt_else_expr, expr.span, expected) } ExprKind::DropTemps(e) => self.check_expr_with_expectation(e, expected), ExprKind::Array(args) => self.check_expr_array(args, expected, expr), @@ -1167,6 +1165,72 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } + // A generic function for checking the 'then' and 'else' clauses in an 'if' + // or 'if-else' expression. + fn check_expr_if( + &self, + expr_id: HirId, + cond_expr: &'tcx hir::Expr<'tcx>, + then_expr: &'tcx hir::Expr<'tcx>, + opt_else_expr: Option<&'tcx hir::Expr<'tcx>>, + sp: Span, + orig_expected: Expectation<'tcx>, + ) -> Ty<'tcx> { + let cond_ty = self.check_expr_has_type_or_error(cond_expr, self.tcx.types.bool, |_| {}); + + self.warn_if_unreachable( + cond_expr.hir_id, + then_expr.span, + "block in `if` or `while` expression", + ); + + let cond_diverges = self.diverges.get(); + self.diverges.set(Diverges::Maybe); + + let expected = orig_expected.try_structurally_resolve_and_adjust_for_branches(self, sp); + let then_ty = self.check_expr_with_expectation(then_expr, expected); + let then_diverges = self.diverges.get(); + self.diverges.set(Diverges::Maybe); + + // We've already taken the expected type's preferences + // into account when typing the `then` branch. To figure + // out the initial shot at a LUB, we thus only consider + // `expected` if it represents a *hard* constraint + // (`only_has_type`); otherwise, we just go with a + // fresh type variable. + let coerce_to_ty = expected.coercion_target_type(self, sp); + let mut coerce = CoerceMany::with_capacity(coerce_to_ty, 2); + + coerce.coerce(self, &self.misc(sp), then_expr, then_ty); + + if let Some(else_expr) = opt_else_expr { + let else_ty = self.check_expr_with_expectation(else_expr, expected); + let else_diverges = self.diverges.get(); + + let tail_defines_return_position_impl_trait = + self.return_position_impl_trait_from_match_expectation(orig_expected); + let if_cause = + self.if_cause(expr_id, else_expr, tail_defines_return_position_impl_trait); + + coerce.coerce(self, &if_cause, else_expr, else_ty); + + // We won't diverge unless both branches do (or the condition does). + self.diverges.set(cond_diverges | then_diverges & else_diverges); + } else { + self.if_fallback_coercion(sp, cond_expr, then_expr, &mut coerce); + + // If the condition is false we can't diverge. + self.diverges.set(cond_diverges); + } + + let result_ty = coerce.complete(self); + if let Err(guar) = cond_ty.error_reported() { + Ty::new_error(self.tcx, guar) + } else { + result_ty + } + } + /// Type check assignment expression `expr` of form `lhs = rhs`. /// The expected type is `()` and is passed to the function for the purposes of diagnostics. fn check_expr_assign( diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs index ef15d6b964230..afd5356d5a1e7 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs @@ -2865,12 +2865,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // `ExprKind::DropTemps` is semantically irrelevant for these suggestions. let expr = expr.peel_drop_temps(); - if let hir::ExprKind::Block(block, _) = &expr.kind - && block.expr.is_none() - { - return None; - } - match (&expr.kind, expected.kind(), checked_ty.kind()) { (_, &ty::Ref(_, exp, _), &ty::Ref(_, check, _)) => match (exp.kind(), check.kind()) { (&ty::Str, &ty::Array(arr, _) | &ty::Slice(arr)) if arr == self.tcx.types.u8 => { diff --git a/compiler/rustc_hir_typeck/src/lib.rs b/compiler/rustc_hir_typeck/src/lib.rs index e7b3a24e2de9a..15729bc311e57 100644 --- a/compiler/rustc_hir_typeck/src/lib.rs +++ b/compiler/rustc_hir_typeck/src/lib.rs @@ -6,7 +6,6 @@ #![feature(trim_prefix_suffix)] // tidy-alphabetical-end -mod _if; mod _match; mod autoderef; mod callee; diff --git a/compiler/rustc_trait_selection/src/error_reporting/infer/mod.rs b/compiler/rustc_trait_selection/src/error_reporting/infer/mod.rs index 8ceed03d5befa..21951cee6d5ab 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/infer/mod.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/infer/mod.rs @@ -528,29 +528,19 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { .expect("if expression only expected inside FnCtxt") .expr_ty(then_expr); let else_span = self.find_block_span_from_hir_id(else_expr.hir_id); - let mut else_ty = self + let else_ty = self .typeck_results .as_ref() .expect("if expression only expected inside FnCtxt") - .expr_ty_opt(else_expr); - - if else_ty.is_none() - && let Some(exp_found) = exp_found - { - let exp_found = ty::error::ExpectedFound { - expected: self.resolve_vars_if_possible(exp_found.expected), - found: self.resolve_vars_if_possible(exp_found.found), - }; - else_ty.get_or_insert(exp_found.found); - } - - if let (Some(else_ty), hir::ExprKind::If(.., None)) = (else_ty, &else_expr.kind) + .expr_ty(else_expr); + if let hir::ExprKind::If(_cond, _then, None) = else_expr.kind && else_ty.is_unit() { // Account for `let x = if a { 1 } else if b { 2 };` err.note("`if` expressions without `else` evaluate to `()`"); err.note("consider adding an `else` block that evaluates to the expected type"); } + err.span_label(then_span, "expected because of this"); let outer_span = if self.tcx.sess.source_map().is_multiline(expr_span) { if then_span.hi() == expr_span.hi() || else_span.hi() == expr_span.hi() { @@ -577,17 +567,15 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { } else { else_expr.hir_id }; - if let Some(else_ty) = else_ty { - if let Some(subdiag) = self.suggest_remove_semi_or_return_binding( - Some(then_id), - then_ty, - then_span, - Some(else_id), - else_ty, - else_span, - ) { - err.subdiagnostic(subdiag); - } + if let Some(subdiag) = self.suggest_remove_semi_or_return_binding( + Some(then_id), + then_ty, + then_span, + Some(else_id), + else_ty, + else_span, + ) { + err.subdiagnostic(subdiag); } } ObligationCauseCode::LetElse => { diff --git a/tests/ui/expr/if/if-else-chain-missing-else.rs b/tests/ui/expr/if/if-else-chain-missing-else.rs index e45939ff7beac..995aac07f2f76 100644 --- a/tests/ui/expr/if/if-else-chain-missing-else.rs +++ b/tests/ui/expr/if/if-else-chain-missing-else.rs @@ -1,10 +1,5 @@ -enum Cause { - Cause1, - Cause2, -} -struct MyErr { - x: Cause, -} +enum Cause { Cause1, Cause2 } +struct MyErr { x: Cause } fn main() { _ = f(); @@ -14,7 +9,7 @@ fn f() -> Result { let res = could_fail(); let x = if let Ok(x) = res { x - } else if let Err(e) = res { //~ ERROR `if` may be missing an `else` clause + } else if let Err(e) = res { //~ ERROR `if` and `else` return Err(e); }; Ok(x) diff --git a/tests/ui/expr/if/if-else-chain-missing-else.stderr b/tests/ui/expr/if/if-else-chain-missing-else.stderr index a21b9e135272d..6c437120d391d 100644 --- a/tests/ui/expr/if/if-else-chain-missing-else.stderr +++ b/tests/ui/expr/if/if-else-chain-missing-else.stderr @@ -1,6 +1,10 @@ -error[E0317]: `if` may be missing an `else` clause - --> $DIR/if-else-chain-missing-else.rs:17:12 +error[E0308]: `if` and `else` have incompatible types + --> $DIR/if-else-chain-missing-else.rs:12:12 | +LL | let x = if let Ok(x) = res { + | ------------------ `if` and `else` have incompatible types +LL | x + | - expected because of this LL | } else if let Err(e) = res { | ____________^ LL | | return Err(e); @@ -8,8 +12,8 @@ LL | | }; | |_____^ expected `i32`, found `()` | = note: `if` expressions without `else` evaluate to `()` - = help: consider adding an `else` block that evaluates to the expected type + = note: consider adding an `else` block that evaluates to the expected type error: aborting due to 1 previous error -For more information about this error, try `rustc --explain E0317`. +For more information about this error, try `rustc --explain E0308`. diff --git a/tests/ui/expr/if/if-multi-else-if-incompatible-types-issue-146190.rs b/tests/ui/expr/if/if-multi-else-if-incompatible-types-issue-146190.rs deleted file mode 100644 index 488bb76a9605a..0000000000000 --- a/tests/ui/expr/if/if-multi-else-if-incompatible-types-issue-146190.rs +++ /dev/null @@ -1,15 +0,0 @@ -fn main() { - let oa = Some(1); - let oa2 = Some(1); - let oa3 = Some(1); - let v = if let Some(a) = oa { - Some(&a) - } else if let Some(a) = oa2 { - &Some(a) //~ ERROR `if` and `else` have incompatible types [E0308] - } else if let Some(a) = oa3 { - &Some(a) - } else { - None - }; - println!("{v:?}"); -} diff --git a/tests/ui/expr/if/if-multi-else-if-incompatible-types-issue-146190.stderr b/tests/ui/expr/if/if-multi-else-if-incompatible-types-issue-146190.stderr deleted file mode 100644 index a846ac521186b..0000000000000 --- a/tests/ui/expr/if/if-multi-else-if-incompatible-types-issue-146190.stderr +++ /dev/null @@ -1,22 +0,0 @@ -error[E0308]: `if` and `else` have incompatible types - --> $DIR/if-multi-else-if-incompatible-types-issue-146190.rs:8:9 - | -LL | let v = if let Some(a) = oa { - | ------------------- `if` and `else` have incompatible types -LL | Some(&a) - | -------- expected because of this -LL | } else if let Some(a) = oa2 { -LL | &Some(a) - | ^^^^^^^^ expected `Option<&{integer}>`, found `&Option<{integer}>` - | - = note: expected enum `Option<&{integer}>` - found reference `&Option<{integer}>` -help: try using `.as_ref()` to convert `&Option<{integer}>` to `Option<&{integer}>` - | -LL - &Some(a) -LL + Some(a).as_ref() - | - -error: aborting due to 1 previous error - -For more information about this error, try `rustc --explain E0308`. diff --git a/tests/ui/inference/deref-suggestion.rs b/tests/ui/inference/deref-suggestion.rs index bed8a9d9ef637..dc39cc9dbffb0 100644 --- a/tests/ui/inference/deref-suggestion.rs +++ b/tests/ui/inference/deref-suggestion.rs @@ -67,8 +67,8 @@ fn main() { let val = if true { *a } else if true { + //~^ ERROR incompatible types b - //~^ ERROR incompatible types } else { &0 }; diff --git a/tests/ui/inference/deref-suggestion.stderr b/tests/ui/inference/deref-suggestion.stderr index efa1b5af3e9ef..027902a9f31e2 100644 --- a/tests/ui/inference/deref-suggestion.stderr +++ b/tests/ui/inference/deref-suggestion.stderr @@ -165,20 +165,20 @@ LL | *b | + error[E0308]: `if` and `else` have incompatible types - --> $DIR/deref-suggestion.rs:70:9 - | -LL | let val = if true { - | ------- `if` and `else` have incompatible types -LL | *a - | -- expected because of this -LL | } else if true { -LL | b - | ^ expected `i32`, found `&{integer}` - | -help: consider dereferencing the borrow - | -LL | *b - | + + --> $DIR/deref-suggestion.rs:69:12 + | +LL | let val = if true { + | ------- `if` and `else` have incompatible types +LL | *a + | -- expected because of this +LL | } else if true { + | ____________^ +LL | | +LL | | b +LL | | } else { +LL | | &0 +LL | | }; + | |_____^ expected `i32`, found `&{integer}` error[E0308]: mismatched types --> $DIR/deref-suggestion.rs:81:15 diff --git a/tests/ui/typeck/consider-borrowing-141810-1.rs b/tests/ui/typeck/consider-borrowing-141810-1.rs index 3a339717a9751..94c2d69091517 100644 --- a/tests/ui/typeck/consider-borrowing-141810-1.rs +++ b/tests/ui/typeck/consider-borrowing-141810-1.rs @@ -1,9 +1,8 @@ fn main() { let x = if true { &true - } else if false { + } else if false { //~ ERROR `if` and `else` have incompatible types [E0308] true //~ HELP consider borrowing here - //~^ ERROR `if` and `else` have incompatible types [E0308] } else { true }; diff --git a/tests/ui/typeck/consider-borrowing-141810-1.stderr b/tests/ui/typeck/consider-borrowing-141810-1.stderr index d83f58e612669..35ca6793eee0d 100644 --- a/tests/ui/typeck/consider-borrowing-141810-1.stderr +++ b/tests/ui/typeck/consider-borrowing-141810-1.stderr @@ -1,18 +1,24 @@ error[E0308]: `if` and `else` have incompatible types - --> $DIR/consider-borrowing-141810-1.rs:5:9 + --> $DIR/consider-borrowing-141810-1.rs:4:12 | -LL | let x = if true { - | ------- `if` and `else` have incompatible types -LL | &true - | ----- expected because of this -LL | } else if false { -LL | true - | ^^^^ expected `&bool`, found `bool` +LL | let x = if true { + | ------- `if` and `else` have incompatible types +LL | &true + | ----- expected because of this +LL | } else if false { + | ____________^ +LL | | true +LL | | } else { +LL | | true +LL | | }; + | |_____^ expected `&bool`, found `bool` | help: consider borrowing here | -LL | &true - | + +LL ~ &true +LL | } else { +LL ~ &true + | error: aborting due to 1 previous error diff --git a/tests/ui/typeck/consider-borrowing-141810-2.stderr b/tests/ui/typeck/consider-borrowing-141810-2.stderr index 101fc03ac4ccf..44ecb5a4a945a 100644 --- a/tests/ui/typeck/consider-borrowing-141810-2.stderr +++ b/tests/ui/typeck/consider-borrowing-141810-2.stderr @@ -1,13 +1,14 @@ error[E0308]: `if` and `else` have incompatible types - --> $DIR/consider-borrowing-141810-2.rs:4:21 + --> $DIR/consider-borrowing-141810-2.rs:4:12 | LL | let x = if true { | ------- `if` and `else` have incompatible types LL | &() | --- expected because of this LL | } else if false { - | _____________________^ + | ____________^ LL | | } else { +LL | | }; | |_____^ expected `&()`, found `()` error: aborting due to 1 previous error diff --git a/tests/ui/typeck/consider-borrowing-141810-3.stderr b/tests/ui/typeck/consider-borrowing-141810-3.stderr index 34a0d5a044934..3adf8ba1a8924 100644 --- a/tests/ui/typeck/consider-borrowing-141810-3.stderr +++ b/tests/ui/typeck/consider-borrowing-141810-3.stderr @@ -1,15 +1,18 @@ error[E0308]: `if` and `else` have incompatible types - --> $DIR/consider-borrowing-141810-3.rs:4:21 + --> $DIR/consider-borrowing-141810-3.rs:4:12 | LL | let x = if true { | ------- `if` and `else` have incompatible types LL | &() | --- expected because of this LL | } else if false { - | _____________________^ + | ____________^ LL | | LL | | }; | |_____^ expected `&()`, found `()` + | + = note: `if` expressions without `else` evaluate to `()` + = note: consider adding an `else` block that evaluates to the expected type error: aborting due to 1 previous error From 7d6060c17895b70d9b663c1a57fa175ec9a5d259 Mon Sep 17 00:00:00 2001 From: sgasho Date: Mon, 4 May 2026 18:19:41 +0900 Subject: [PATCH 3/3] fix: diagnostics - check if - else chain types from top to bottom, not from depth first approach --- compiler/rustc_hir_typeck/src/_if.rs | 232 ++++++++++++++++++ compiler/rustc_hir_typeck/src/coercion.rs | 35 ++- compiler/rustc_hir_typeck/src/expr.rs | 130 +++------- compiler/rustc_hir_typeck/src/lib.rs | 1 + compiler/rustc_middle/src/traits/mod.rs | 16 ++ .../src/error_reporting/infer/mod.rs | 66 ++++- .../src/error_reporting/traits/suggestions.rs | 1 + tests/ui/inference/deref-suggestion.rs | 2 +- tests/ui/inference/deref-suggestion.stderr | 22 +- .../ui/typeck/consider-borrowing-141810-1.rs | 5 +- .../typeck/consider-borrowing-141810-1.stderr | 22 +- .../if-else-type-mismatch-chain-nested.rs | 21 ++ .../if-else-type-mismatch-chain-nested.stderr | 43 ++++ .../ui/typeck/if-else-type-mismatch-chain.rs | 11 + .../typeck/if-else-type-mismatch-chain.stderr | 26 ++ 15 files changed, 516 insertions(+), 117 deletions(-) create mode 100644 compiler/rustc_hir_typeck/src/_if.rs create mode 100644 tests/ui/typeck/if-else-type-mismatch-chain-nested.rs create mode 100644 tests/ui/typeck/if-else-type-mismatch-chain-nested.stderr create mode 100644 tests/ui/typeck/if-else-type-mismatch-chain.rs create mode 100644 tests/ui/typeck/if-else-type-mismatch-chain.stderr diff --git a/compiler/rustc_hir_typeck/src/_if.rs b/compiler/rustc_hir_typeck/src/_if.rs new file mode 100644 index 0000000000000..3a76041ec72f9 --- /dev/null +++ b/compiler/rustc_hir_typeck/src/_if.rs @@ -0,0 +1,232 @@ +use rustc_hir as hir; +use rustc_hir::{ExprKind, HirId}; +use rustc_infer::traits::{IfChainCoerceCause, ObligationCauseCode}; +use rustc_middle::bug; +use rustc_middle::ty::{Ty, TypeVisitableExt}; +use rustc_span::Span; +use rustc_span::def_id::LocalDefId; + +use crate::Expectation; +use crate::coercion::CoerceMany; +use crate::diverges::Diverges; +use crate::fn_ctxt::FnCtxt; + +/// State shared across all branches of an `else if` chain check. +struct ElseIfChainCx<'a, 'tcx> { + coerce: &'a mut CoerceMany<'tcx>, + outer_if_expr_id: HirId, + orig_expected: Expectation<'tcx>, + tail_defines_return_position_impl_trait: Option, +} + +#[derive(Copy, Clone)] +struct PrevBranch<'tcx> { + hir_id: HirId, + ty: Ty<'tcx>, +} + +fn chain_has_terminal_else_block(expr: &hir::Expr<'_>) -> bool { + let mut cur = expr; + loop { + match cur.kind { + ExprKind::If(_, _, Some(next)) => cur = next, + ExprKind::If(_, _, None) => return false, + _ => return true, + } + } +} + +fn has_empty_block_chains(expr: &hir::Expr<'_>) -> bool { + let mut cur = expr; + loop { + match cur.kind { + ExprKind::If(_, then, opt_else) => { + let ExprKind::Block(then_block, _) = then.kind else { + return true; + }; + if then_block.expr.is_none() { + return true; + } + match opt_else { + Some(next) => cur = next, + None => return false, + } + } + ExprKind::Block(block, _) => return block.expr.is_none(), + _ => return false, + } + } +} + +impl<'a, 'tcx> FnCtxt<'a, 'tcx> { + // A generic function for checking the 'then' and 'else' clauses in an 'if' + // or 'if-else' expression. + pub(crate) fn check_expr_if( + &self, + expr_id: HirId, + cond_expr: &'tcx hir::Expr<'tcx>, + then_expr: &'tcx hir::Expr<'tcx>, + opt_else_expr: Option<&'tcx hir::Expr<'tcx>>, + sp: Span, + orig_expected: Expectation<'tcx>, + ) -> Ty<'tcx> { + let cond_ty = self.check_expr_has_type_or_error(cond_expr, self.tcx.types.bool, |_| {}); + + let (expected, then_ty, cond_diverges, then_diverges) = + self.check_current_if_branch(cond_expr, then_expr, orig_expected); + + // We've already taken the expected type's preferences + // into account when typing the `then` branch. To figure + // out the initial shot at a LUB, we thus only consider + // `expected` if it represents a *hard* constraint + // (`only_has_type`); otherwise, we just go with a + // fresh type variable. + let coerce_to_ty = expected.coercion_target_type(self, sp); + let mut coerce = CoerceMany::with_capacity(coerce_to_ty, 2); + + coerce.coerce(self, &self.misc(sp), then_expr, then_ty); + + if let Some(else_expr) = opt_else_expr { + let else_diverges = if chain_has_terminal_else_block(else_expr) + && let ExprKind::If(..) = else_expr.kind + // Chains containing `{}` arms go to the path below. + // Otherwise for code like + // let x = if c1 { &() } else if c2 {} else {}; + // the "consider borrowing here" suggestion comes out as + // } else if c2 &{ + // which isn't valid syntax. Having an empty block in an if chain is an edge + // case in real code, so the fallback is harmless. + && !has_empty_block_chains(else_expr) + { + let mut cx = ElseIfChainCx { + coerce: &mut coerce, + outer_if_expr_id: expr_id, + orig_expected, + tail_defines_return_position_impl_trait: self + .return_position_impl_trait_from_match_expectation(orig_expected), + }; + self.check_expr_with_check_fn(else_expr, expected, |this| { + this.check_else_if_branch( + &mut cx, + PrevBranch { hir_id: then_expr.hir_id, ty: then_ty }, + else_expr, + ) + }); + self.diverges.get() + } else { + let else_ty = self.check_expr_with_expectation(else_expr, expected); + let else_diverges = self.diverges.get(); + let if_cause = self.if_cause( + expr_id, + else_expr, + self.return_position_impl_trait_from_match_expectation(orig_expected), + ); + coerce.coerce(self, &if_cause, else_expr, else_ty); + else_diverges + }; + + // We won't diverge unless both branches do (or the condition does). + self.diverges.set(cond_diverges | then_diverges & else_diverges); + } else { + self.if_fallback_coercion(sp, cond_expr, then_expr, &mut coerce); + + // If the condition is false we can't diverge. + self.diverges.set(cond_diverges); + } + + let result_ty = coerce.complete(self); + if let Err(guar) = cond_ty.error_reported() { + Ty::new_error(self.tcx, guar) + } else { + result_ty + } + } + + fn check_current_if_branch( + &self, + cond_expr: &'tcx hir::Expr<'tcx>, + then_expr: &'tcx hir::Expr<'tcx>, + orig_expected: Expectation<'tcx>, + ) -> (Expectation<'tcx>, Ty<'tcx>, Diverges, Diverges) { + self.warn_if_unreachable( + cond_expr.hir_id, + then_expr.span, + "block in `if` or `while` expression", + ); + + let cond_diverges = self.diverges.get(); + self.diverges.set(Diverges::Maybe); + + let expected = orig_expected.try_structurally_resolve_and_adjust_for_branches(self); + let then_ty = self.check_expr_with_expectation(then_expr, expected); + let then_diverges = self.diverges.get(); + self.diverges.set(Diverges::Maybe); + + (expected, then_ty, cond_diverges, then_diverges) + } + + fn check_else_if_branch( + &self, + cx: &mut ElseIfChainCx<'_, 'tcx>, + prev_branch: PrevBranch<'tcx>, + current_expr: &'tcx hir::Expr<'tcx>, + ) -> Ty<'tcx> { + let ExprKind::If(cond_expr, then_expr, opt_else_expr) = current_expr.kind else { + bug!("check_else_if_branch called on non-`if` expr"); + }; + self.check_expr_has_type_or_error(cond_expr, self.tcx.types.bool, |_| {}); + + let (expected, then_ty, cond_diverges, then_diverges) = + self.check_current_if_branch(cond_expr, then_expr, cx.orig_expected); + + self.coerce_branch_against_prev(cx, prev_branch, then_expr, then_ty); + + let next_prev = PrevBranch { hir_id: then_expr.hir_id, ty: then_ty }; + + let else_diverges = match opt_else_expr { + Some(else_expr) if let ExprKind::If(..) = else_expr.kind => { + self.check_expr_with_check_fn(else_expr, expected, |this| { + this.check_else_if_branch(cx, next_prev, else_expr) + }); + self.diverges.get() + } + Some(else_expr) => { + let else_ty = self.check_expr_with_expectation(else_expr, expected); + let else_diverges = self.diverges.get(); + self.coerce_branch_against_prev(cx, next_prev, else_expr, else_ty); + else_diverges + } + None => bug!("chain entered without a final else; broken `has_final_else_arm`"), + }; + + // We won't diverge unless cond does, or both then and else do. + self.diverges.set(cond_diverges | then_diverges & else_diverges); + + cx.coerce.merged_ty() + } + + fn coerce_branch_against_prev( + &self, + cx: &mut ElseIfChainCx<'_, 'tcx>, + prev: PrevBranch<'tcx>, + current_branch_expr: &'tcx hir::Expr<'tcx>, + current_branch_ty: Ty<'tcx>, + ) { + let prev_branch_span = self.find_block_span_from_hir_id(prev.hir_id); + let branch_span = self.find_block_span_from_hir_id(current_branch_expr.hir_id); + let cause = self.cause( + branch_span, + ObligationCauseCode::IfChainCoerce(Box::new(IfChainCoerceCause { + outer_if_expr_id: cx.outer_if_expr_id, + source_branch_expr_id: prev.hir_id, + source_branch_ty: prev.ty, + source_branch_span: prev_branch_span, + target_branch_expr_id: current_branch_expr.hir_id, + target_branch_ty: current_branch_ty, + target_branch_span: branch_span, + tail_defines_return_position_impl_trait: cx.tail_defines_return_position_impl_trait, + })), + ); + cx.coerce.coerce(self, &cause, current_branch_expr, current_branch_ty); + } +} diff --git a/compiler/rustc_hir_typeck/src/coercion.rs b/compiler/rustc_hir_typeck/src/coercion.rs index abd5f38f0ed08..817049a41b205 100644 --- a/compiler/rustc_hir_typeck/src/coercion.rs +++ b/compiler/rustc_hir_typeck/src/coercion.rs @@ -46,7 +46,8 @@ use rustc_hir_analysis::hir_ty_lowering::HirTyLowerer; use rustc_infer::infer::relate::RelateResult; use rustc_infer::infer::{DefineOpaqueTypes, InferOk, InferResult, RegionVariableOrigin}; use rustc_infer::traits::{ - MatchExpressionArmCause, Obligation, PredicateObligation, PredicateObligations, SelectionError, + IfChainCoerceCause, MatchExpressionArmCause, Obligation, PredicateObligation, + PredicateObligations, SelectionError, }; use rustc_middle::span_bug; use rustc_middle::ty::adjustment::{ @@ -1769,6 +1770,38 @@ impl<'tcx> CoerceMany<'tcx> { ); } } + ObligationCauseCode::IfChainCoerce(box IfChainCoerceCause { + source_branch_expr_id: prev_branch_expr_id, + source_branch_ty: prev_branch_ty, + source_branch_span: prev_branch_span, + target_branch_expr_id: branch_expr_id, + target_branch_ty: branch_ty, + target_branch_span: branch_span, + tail_defines_return_position_impl_trait: Some(rpit_def_id), + .. + }) => { + err = fcx.err_ctxt().report_mismatched_types( + cause, + fcx.param_env, + expected, + found, + coercion_error, + ); + let prev_expr_span = + fcx.tcx.hir_node(prev_branch_expr_id).expect_expr().span; + let branch_expr_span = fcx.tcx.hir_node(branch_expr_id).expect_expr().span; + // Don't suggest wrapping whole block in `Box::new`. + if prev_branch_span != prev_expr_span && branch_span != branch_expr_span { + self.suggest_boxing_tail_for_return_position_impl_trait( + fcx, + &mut err, + rpit_def_id, + prev_branch_ty, + branch_ty, + [prev_branch_span, branch_span].into_iter(), + ); + } + } _ => { err = fcx.err_ctxt().report_mismatched_types( cause, diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs index 1b44b7fa77f4b..7a9bda10a64f8 100644 --- a/compiler/rustc_hir_typeck/src/expr.rs +++ b/compiler/rustc_hir_typeck/src/expr.rs @@ -201,27 +201,17 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.check_expr_with_expectation_and_needs(expr, NoExpectation, needs) } - /// Check an expr with an expectation type which may be used to eagerly - /// guide inference when evaluating that expr. - #[instrument(skip(self, expr), level = "debug")] - pub(super) fn check_expr_with_expectation( - &self, - expr: &'tcx hir::Expr<'tcx>, - expected: Expectation<'tcx>, - ) -> Ty<'tcx> { - self.check_expr_with_expectation_and_args(expr, expected, None) - } - - /// Same as [`Self::check_expr_with_expectation`], but allows us to pass in - /// the arguments of a [`ExprKind::Call`] when evaluating its callee that - /// is an [`ExprKind::Path`]. We use this to refine the spans for certain - /// well-formedness guarantees for the path expr. - pub(super) fn check_expr_with_expectation_and_args( + /// Run the per-expression framing (`write_ty`, divergence handling, + /// unreachable warnings) around `check`. + pub(super) fn check_expr_with_check_fn( &self, expr: &'tcx hir::Expr<'tcx>, expected: Expectation<'tcx>, - call_expr_and_args: Option<(&'tcx hir::Expr<'tcx>, &'tcx [hir::Expr<'tcx>])>, - ) -> Ty<'tcx> { + check: F, + ) -> Ty<'tcx> + where + F: FnOnce(&Self) -> Ty<'tcx>, + { if self.tcx().sess.verbose_internals() { // make this code only run with -Zverbose-internals because it is probably slow if let Ok(lint_str) = self.tcx.sess.source_map().span_to_snippet(expr.span) { @@ -264,13 +254,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.diverges.set(self.function_diverges_because_of_empty_arguments.get()) }; - let ty = ensure_sufficient_stack(|| match &expr.kind { - // Intercept the callee path expr and give it better spans. - hir::ExprKind::Path( - qpath @ (hir::QPath::Resolved(..) | hir::QPath::TypeRelative(..)), - ) => self.check_expr_path(qpath, expr, call_expr_and_args), - _ => self.check_expr_kind(expr, expected), - }); + let ty = ensure_sufficient_stack(|| check(self)); let ty = self.resolve_vars_if_possible(ty); // Warn for non-block expressions with diverging children. @@ -320,6 +304,36 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ty } + /// Check an expr with an expectation type which may be used to eagerly + /// guide inference when evaluating that expr. + #[instrument(skip(self, expr), level = "debug")] + pub(super) fn check_expr_with_expectation( + &self, + expr: &'tcx hir::Expr<'tcx>, + expected: Expectation<'tcx>, + ) -> Ty<'tcx> { + self.check_expr_with_expectation_and_args(expr, expected, None) + } + + /// Same as [`Self::check_expr_with_expectation`], but allows us to pass in + /// the arguments of a [`ExprKind::Call`] when evaluating its callee that + /// is an [`ExprKind::Path`]. We use this to refine the spans for certain + /// well-formedness guarantees for the path expr. + pub(super) fn check_expr_with_expectation_and_args( + &self, + expr: &'tcx hir::Expr<'tcx>, + expected: Expectation<'tcx>, + call_expr_and_args: Option<(&'tcx hir::Expr<'tcx>, &'tcx [hir::Expr<'tcx>])>, + ) -> Ty<'tcx> { + self.check_expr_with_check_fn(expr, expected, |this| match &expr.kind { + // Intercept the callee path expr and give it better spans. + hir::ExprKind::Path( + qpath @ (hir::QPath::Resolved(..) | hir::QPath::TypeRelative(..)), + ) => this.check_expr_path(qpath, expr, call_expr_and_args), + _ => this.check_expr_kind(expr, expected), + }) + } + #[instrument(skip(self, expr), level = "debug")] fn check_expr_kind( &self, @@ -1165,72 +1179,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } - // A generic function for checking the 'then' and 'else' clauses in an 'if' - // or 'if-else' expression. - fn check_expr_if( - &self, - expr_id: HirId, - cond_expr: &'tcx hir::Expr<'tcx>, - then_expr: &'tcx hir::Expr<'tcx>, - opt_else_expr: Option<&'tcx hir::Expr<'tcx>>, - sp: Span, - orig_expected: Expectation<'tcx>, - ) -> Ty<'tcx> { - let cond_ty = self.check_expr_has_type_or_error(cond_expr, self.tcx.types.bool, |_| {}); - - self.warn_if_unreachable( - cond_expr.hir_id, - then_expr.span, - "block in `if` or `while` expression", - ); - - let cond_diverges = self.diverges.get(); - self.diverges.set(Diverges::Maybe); - - let expected = orig_expected.try_structurally_resolve_and_adjust_for_branches(self, sp); - let then_ty = self.check_expr_with_expectation(then_expr, expected); - let then_diverges = self.diverges.get(); - self.diverges.set(Diverges::Maybe); - - // We've already taken the expected type's preferences - // into account when typing the `then` branch. To figure - // out the initial shot at a LUB, we thus only consider - // `expected` if it represents a *hard* constraint - // (`only_has_type`); otherwise, we just go with a - // fresh type variable. - let coerce_to_ty = expected.coercion_target_type(self, sp); - let mut coerce = CoerceMany::with_capacity(coerce_to_ty, 2); - - coerce.coerce(self, &self.misc(sp), then_expr, then_ty); - - if let Some(else_expr) = opt_else_expr { - let else_ty = self.check_expr_with_expectation(else_expr, expected); - let else_diverges = self.diverges.get(); - - let tail_defines_return_position_impl_trait = - self.return_position_impl_trait_from_match_expectation(orig_expected); - let if_cause = - self.if_cause(expr_id, else_expr, tail_defines_return_position_impl_trait); - - coerce.coerce(self, &if_cause, else_expr, else_ty); - - // We won't diverge unless both branches do (or the condition does). - self.diverges.set(cond_diverges | then_diverges & else_diverges); - } else { - self.if_fallback_coercion(sp, cond_expr, then_expr, &mut coerce); - - // If the condition is false we can't diverge. - self.diverges.set(cond_diverges); - } - - let result_ty = coerce.complete(self); - if let Err(guar) = cond_ty.error_reported() { - Ty::new_error(self.tcx, guar) - } else { - result_ty - } - } - /// Type check assignment expression `expr` of form `lhs = rhs`. /// The expected type is `()` and is passed to the function for the purposes of diagnostics. fn check_expr_assign( diff --git a/compiler/rustc_hir_typeck/src/lib.rs b/compiler/rustc_hir_typeck/src/lib.rs index 15729bc311e57..e7b3a24e2de9a 100644 --- a/compiler/rustc_hir_typeck/src/lib.rs +++ b/compiler/rustc_hir_typeck/src/lib.rs @@ -6,6 +6,7 @@ #![feature(trim_prefix_suffix)] // tidy-alphabetical-end +mod _if; mod _match; mod autoderef; mod callee; diff --git a/compiler/rustc_middle/src/traits/mod.rs b/compiler/rustc_middle/src/traits/mod.rs index daa0df4013272..2db99df4458f5 100644 --- a/compiler/rustc_middle/src/traits/mod.rs +++ b/compiler/rustc_middle/src/traits/mod.rs @@ -339,6 +339,8 @@ pub enum ObligationCauseCode<'tcx> { tail_defines_return_position_impl_trait: Option, }, + IfChainCoerce(Box>), + /// Computing common supertype of an if expression with no else counter-part IfExpressionWithNoElse, @@ -551,6 +553,20 @@ pub struct MatchExpressionArmCause<'tcx> { pub tail_defines_return_position_impl_trait: Option, } +#[derive(Clone, Debug, PartialEq, Eq, StableHash, TyEncodable, TyDecodable)] +#[derive(TypeVisitable, TypeFoldable)] +pub struct IfChainCoerceCause<'tcx> { + /// Outermost `if` of the chain, for the outer "incompatible types" label. + pub outer_if_expr_id: HirId, + pub source_branch_expr_id: HirId, + pub source_branch_ty: Ty<'tcx>, + pub source_branch_span: Span, + pub target_branch_expr_id: HirId, + pub target_branch_ty: Ty<'tcx>, + pub target_branch_span: Span, + pub tail_defines_return_position_impl_trait: Option, +} + /// Information about the origin expression of a pattern, relevant to diagnostics. /// Fields here refer to the scrutinee of a pattern. /// If the scrutinee isn't given in the diagnostic, then this won't exist. diff --git a/compiler/rustc_trait_selection/src/error_reporting/infer/mod.rs b/compiler/rustc_trait_selection/src/error_reporting/infer/mod.rs index 21951cee6d5ab..09d3c61399b93 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/infer/mod.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/infer/mod.rs @@ -58,6 +58,7 @@ use rustc_hir::def_id::{CRATE_DEF_ID, DefId}; use rustc_hir::intravisit::Visitor; use rustc_hir::lang_items::LangItem; use rustc_infer::infer::DefineOpaqueTypes; +use rustc_infer::traits::IfChainCoerceCause; use rustc_macros::extension; use rustc_middle::bug; use rustc_middle::traits::PatternOriginExpr; @@ -512,6 +513,69 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { } } }, + ObligationCauseCode::IfChainCoerce(box IfChainCoerceCause { + outer_if_expr_id, + source_branch_expr_id: prev_branch_expr_id, + source_branch_ty: prev_branch_ty, + source_branch_span: prev_branch_span, + target_branch_expr_id: branch_expr_id, + target_branch_ty: branch_ty, + target_branch_span: branch_span, + .. + }) => { + err.span_label(prev_branch_span, "expected because of this"); + + if let hir::Node::Expr(&hir::Expr { span: expr_span, .. }) = + self.tcx.hir_node(outer_if_expr_id) + && let hir::Node::Expr(&hir::Expr { kind: hir::ExprKind::If(cond, ..), .. }) = + self.tcx.hir_node(outer_if_expr_id) + { + let outer_span = if self.tcx.sess.source_map().is_multiline(expr_span) { + if prev_branch_span.hi() == expr_span.hi() + || branch_span.hi() == expr_span.hi() + { + // Avoid overlapping arrows: shrink to `if `. + Some(expr_span.shrink_to_lo().to(cond.peel_drop_temps().span)) + } else { + Some(expr_span) + } + } else { + None + }; + if let Some(sp) = outer_span { + err.span_label(sp, "`if` and `else` have incompatible types"); + } + } + + let prev_id = if let hir::Node::Expr(hir::Expr { + kind: hir::ExprKind::Block(blk, _), + .. + }) = self.tcx.hir_node(prev_branch_expr_id) + { + blk.hir_id + } else { + prev_branch_expr_id + }; + let new_id = if let hir::Node::Expr(hir::Expr { + kind: hir::ExprKind::Block(blk, _), + .. + }) = self.tcx.hir_node(branch_expr_id) + { + blk.hir_id + } else { + branch_expr_id + }; + if let Some(subdiag) = self.suggest_remove_semi_or_return_binding( + Some(prev_id), + prev_branch_ty, + prev_branch_span, + Some(new_id), + branch_ty, + branch_span, + ) { + err.subdiagnostic(subdiag); + } + } ObligationCauseCode::IfExpression { expr_id, .. } => { let hir::Node::Expr(&hir::Expr { kind: hir::ExprKind::If(cond_expr, then_expr, Some(else_expr)), @@ -2305,7 +2369,7 @@ impl<'tcx> ObligationCause<'tcx> { } _ => ObligationCauseFailureCode::MatchCompat { span, subdiags }, }, - ObligationCauseCode::IfExpression { .. } => { + ObligationCauseCode::IfExpression { .. } | ObligationCauseCode::IfChainCoerce(_) => { ObligationCauseFailureCode::IfElseDifferent { span, subdiags } } ObligationCauseCode::IfExpressionWithNoElse => { diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs index 148f1471b1b66..fa3ea66334cf1 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs @@ -3476,6 +3476,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { ObligationCauseCode::ExprAssignable | ObligationCauseCode::MatchExpressionArm { .. } | ObligationCauseCode::Pattern { .. } + | ObligationCauseCode::IfChainCoerce(_) | ObligationCauseCode::IfExpression { .. } | ObligationCauseCode::IfExpressionWithNoElse | ObligationCauseCode::MainFunctionType diff --git a/tests/ui/inference/deref-suggestion.rs b/tests/ui/inference/deref-suggestion.rs index dc39cc9dbffb0..bed8a9d9ef637 100644 --- a/tests/ui/inference/deref-suggestion.rs +++ b/tests/ui/inference/deref-suggestion.rs @@ -67,8 +67,8 @@ fn main() { let val = if true { *a } else if true { - //~^ ERROR incompatible types b + //~^ ERROR incompatible types } else { &0 }; diff --git a/tests/ui/inference/deref-suggestion.stderr b/tests/ui/inference/deref-suggestion.stderr index 027902a9f31e2..48ad7d11975cf 100644 --- a/tests/ui/inference/deref-suggestion.stderr +++ b/tests/ui/inference/deref-suggestion.stderr @@ -165,20 +165,24 @@ LL | *b | + error[E0308]: `if` and `else` have incompatible types - --> $DIR/deref-suggestion.rs:69:12 + --> $DIR/deref-suggestion.rs:70:9 | LL | let val = if true { - | ------- `if` and `else` have incompatible types -LL | *a - | -- expected because of this -LL | } else if true { - | ____________^ -LL | | + | _______________- +LL | | *a + | | -- expected because of this +LL | | } else if true { LL | | b -LL | | } else { + | | ^ expected `i32`, found `&{integer}` +... | LL | | &0 LL | | }; - | |_____^ expected `i32`, found `&{integer}` + | |_____- `if` and `else` have incompatible types + | +help: consider dereferencing the borrow + | +LL | *b + | + error[E0308]: mismatched types --> $DIR/deref-suggestion.rs:81:15 diff --git a/tests/ui/typeck/consider-borrowing-141810-1.rs b/tests/ui/typeck/consider-borrowing-141810-1.rs index 94c2d69091517..1b1d4312b4e3c 100644 --- a/tests/ui/typeck/consider-borrowing-141810-1.rs +++ b/tests/ui/typeck/consider-borrowing-141810-1.rs @@ -1,8 +1,9 @@ fn main() { let x = if true { &true - } else if false { //~ ERROR `if` and `else` have incompatible types [E0308] - true //~ HELP consider borrowing here + } else if false { + true //~ ERROR `if` and `else` have incompatible types [E0308] + //~^ HELP consider borrowing here } else { true }; diff --git a/tests/ui/typeck/consider-borrowing-141810-1.stderr b/tests/ui/typeck/consider-borrowing-141810-1.stderr index 35ca6793eee0d..3de8ccea0bfb9 100644 --- a/tests/ui/typeck/consider-borrowing-141810-1.stderr +++ b/tests/ui/typeck/consider-borrowing-141810-1.stderr @@ -1,24 +1,22 @@ error[E0308]: `if` and `else` have incompatible types - --> $DIR/consider-borrowing-141810-1.rs:4:12 + --> $DIR/consider-borrowing-141810-1.rs:5:9 | LL | let x = if true { - | ------- `if` and `else` have incompatible types -LL | &true - | ----- expected because of this -LL | } else if false { - | ____________^ + | _____________- +LL | | &true + | | ----- expected because of this +LL | | } else if false { LL | | true -LL | | } else { + | | ^^^^ expected `&bool`, found `bool` +... | LL | | true LL | | }; - | |_____^ expected `&bool`, found `bool` + | |_____- `if` and `else` have incompatible types | help: consider borrowing here | -LL ~ &true -LL | } else { -LL ~ &true - | +LL | &true + | + error: aborting due to 1 previous error diff --git a/tests/ui/typeck/if-else-type-mismatch-chain-nested.rs b/tests/ui/typeck/if-else-type-mismatch-chain-nested.rs new file mode 100644 index 0000000000000..52bd4c12bd7a9 --- /dev/null +++ b/tests/ui/typeck/if-else-type-mismatch-chain-nested.rs @@ -0,0 +1,21 @@ +fn main() { + let oa = Some(1); + let oa2 = Some(1); + let _v = if let Some(a) = oa { + Some(&a) + } else if let Some(a) = oa2 { + let _f = 1; + + let _h = if true { + 1 + } else if true { + "2" //~ ERROR `if` and `else` have incompatible types [E0308] + } else { + 3 + }; + + &Some(a) //~ ERROR `if` and `else` have incompatible types [E0308] + } else { + None + }; +} diff --git a/tests/ui/typeck/if-else-type-mismatch-chain-nested.stderr b/tests/ui/typeck/if-else-type-mismatch-chain-nested.stderr new file mode 100644 index 0000000000000..c002a18699095 --- /dev/null +++ b/tests/ui/typeck/if-else-type-mismatch-chain-nested.stderr @@ -0,0 +1,43 @@ +error[E0308]: `if` and `else` have incompatible types + --> $DIR/if-else-type-mismatch-chain-nested.rs:12:13 + | +LL | let _h = if true { + | __________________- +LL | | 1 + | | - expected because of this +LL | | } else if true { +LL | | "2" + | | ^^^ expected integer, found `&str` +LL | | } else { +LL | | 3 +LL | | }; + | |_________- `if` and `else` have incompatible types + +error[E0308]: `if` and `else` have incompatible types + --> $DIR/if-else-type-mismatch-chain-nested.rs:17:9 + | +LL | let _v = if let Some(a) = oa { + | ______________- +LL | | Some(&a) + | | -------- expected because of this +LL | | } else if let Some(a) = oa2 { +LL | | let _f = 1; +... | +LL | | &Some(a) + | | ^^^^^^^^ expected `Option<&{integer}>`, found `&Option<{integer}>` +LL | | } else { +LL | | None +LL | | }; + | |_____- `if` and `else` have incompatible types + | + = note: expected enum `Option<&{integer}>` + found reference `&Option<{integer}>` +help: try using `.as_ref()` to convert `&Option<{integer}>` to `Option<&{integer}>` + | +LL - &Some(a) +LL + Some(a).as_ref() + | + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0308`. diff --git a/tests/ui/typeck/if-else-type-mismatch-chain.rs b/tests/ui/typeck/if-else-type-mismatch-chain.rs new file mode 100644 index 0000000000000..7a4c14f9acb76 --- /dev/null +++ b/tests/ui/typeck/if-else-type-mismatch-chain.rs @@ -0,0 +1,11 @@ +fn main() { + let oa = Some(1); + let oa2 = Some(1); + let _v = if let Some(a) = oa { + Some(&a) + } else if let Some(a) = oa2 { + &Some(a) //~ ERROR `if` and `else` have incompatible types [E0308] + } else { + None + }; +} diff --git a/tests/ui/typeck/if-else-type-mismatch-chain.stderr b/tests/ui/typeck/if-else-type-mismatch-chain.stderr new file mode 100644 index 0000000000000..890a7186e0c37 --- /dev/null +++ b/tests/ui/typeck/if-else-type-mismatch-chain.stderr @@ -0,0 +1,26 @@ +error[E0308]: `if` and `else` have incompatible types + --> $DIR/if-else-type-mismatch-chain.rs:7:9 + | +LL | let _v = if let Some(a) = oa { + | ______________- +LL | | Some(&a) + | | -------- expected because of this +LL | | } else if let Some(a) = oa2 { +LL | | &Some(a) + | | ^^^^^^^^ expected `Option<&{integer}>`, found `&Option<{integer}>` +LL | | } else { +LL | | None +LL | | }; + | |_____- `if` and `else` have incompatible types + | + = note: expected enum `Option<&{integer}>` + found reference `&Option<{integer}>` +help: try using `.as_ref()` to convert `&Option<{integer}>` to `Option<&{integer}>` + | +LL - &Some(a) +LL + Some(a).as_ref() + | + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0308`.