diff --git a/compiler/rustc_hir_typeck/src/_if.rs b/compiler/rustc_hir_typeck/src/_if.rs new file mode 100644 index 0000000000000..e1517caae272a --- /dev/null +++ b/compiler/rustc_hir_typeck/src/_if.rs @@ -0,0 +1,141 @@ +use rustc_hir as hir; +use rustc_hir::{ExprKind, HirId}; +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 `if`/`else if` chain check. +struct IfChainCx<'a, 'tcx> { + coerce: &'a mut CoerceMany<'tcx>, + outer_if_expr_id: HirId, + orig_expected: Expectation<'tcx>, + tail_defines_return_position_impl_trait: Option, +} + +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 expected = orig_expected.try_structurally_resolve_and_adjust_for_branches(self); + + let coerce_to_ty = expected.coercion_target_type(self, sp); + let mut coerce = CoerceMany::with_capacity(coerce_to_ty, 2); + + let mut cx = IfChainCx { + 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), + }; + let branch_ty = + self.check_if_branch(&mut cx, None, cond_expr, then_expr, opt_else_expr, sp); + + let result_ty = coerce.complete(self); + if let Err(guar) = branch_ty.error_reported() { + Ty::new_error(self.tcx, guar) + } else { + result_ty + } + } + + /// Checks one `if` of an `if`/`else if` chain and recurses into the next `else if`. + /// + /// `prev_branch` is `None` for the outermost `if`. + /// Every later branch is coerced against the preceding one (`prev_branch`), + /// so a mismatch is reported at that branch rather than at the whole `else` chain. + fn check_if_branch( + &self, + cx: &mut IfChainCx<'_, 'tcx>, + prev_branch: Option, + cond_expr: &'tcx hir::Expr<'tcx>, + then_expr: &'tcx hir::Expr<'tcx>, + opt_else_expr: Option<&'tcx hir::Expr<'tcx>>, + sp: Span, + ) -> 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 = cx.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); + + if let Some(prev_hir_id) = prev_branch { + let cause = self.if_cause( + cx.outer_if_expr_id, + prev_hir_id, + then_expr.hir_id, + cx.tail_defines_return_position_impl_trait, + ); + cx.coerce.coerce(self, &cause, then_expr, then_ty); + } else { + cx.coerce.coerce(self, &self.misc(sp), then_expr, then_ty); + } + + match opt_else_expr { + Some(else_expr) + if let ExprKind::If(next_cond_expr, next_then_expr, next_opt_else_expr) = + else_expr.kind => + { + self.check_expr_with_check_fn(else_expr, expected, |this| { + this.check_if_branch( + cx, + Some(then_expr.hir_id), + next_cond_expr, + next_then_expr, + next_opt_else_expr, + else_expr.span, + ) + }); + + let else_diverges = self.diverges.get(); + self.diverges.set(cond_diverges | then_diverges & else_diverges); + } + Some(else_expr) => { + let else_ty = self.check_expr_with_expectation(else_expr, expected); + let else_diverges = self.diverges.get(); + let cause = self.if_cause( + cx.outer_if_expr_id, + then_expr.hir_id, + else_expr.hir_id, + cx.tail_defines_return_position_impl_trait, + ); + cx.coerce.coerce(self, &cause, else_expr, else_ty); + self.diverges.set(cond_diverges | then_diverges & else_diverges); + } + None => { + self.if_fallback_coercion(sp, cond_expr, then_expr, cx.coerce); + + self.diverges.set(cond_diverges); + } + }; + + if let Err(guar) = cond_ty.error_reported() { + Ty::new_error(self.tcx, guar) + } else { + cx.coerce.merged_ty() + } + } +} diff --git a/compiler/rustc_hir_typeck/src/_match.rs b/compiler/rustc_hir_typeck/src/_match.rs index a1ff036574fdc..3b6c6339e0f39 100644 --- a/compiler/rustc_hir_typeck/src/_match.rs +++ b/compiler/rustc_hir_typeck/src/_match.rs @@ -437,15 +437,21 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { pub(crate) fn if_cause( &self, expr_id: HirId, - else_expr: &'tcx hir::Expr<'tcx>, + expected_branch_id: HirId, + found_branch_id: HirId, tail_defines_return_position_impl_trait: Option, ) -> ObligationCause<'tcx> { - let error_sp = self.find_block_span_from_hir_id(else_expr.hir_id); + let error_sp = self.find_block_span_from_hir_id(found_branch_id); // Finally construct the cause: self.cause( error_sp, - ObligationCauseCode::IfExpression { expr_id, tail_defines_return_position_impl_trait }, + ObligationCauseCode::IfExpression { + outermost_if_expr_id: expr_id, + expected_branch_id, + found_branch_id, + tail_defines_return_position_impl_trait, + }, ) } diff --git a/compiler/rustc_hir_typeck/src/coercion.rs b/compiler/rustc_hir_typeck/src/coercion.rs index af00194a95bf0..fb2b0d43c076c 100644 --- a/compiler/rustc_hir_typeck/src/coercion.rs +++ b/compiler/rustc_hir_typeck/src/coercion.rs @@ -1832,16 +1832,11 @@ impl<'tcx> CoerceMany<'tcx> { } } ObligationCauseCode::IfExpression { - expr_id, + expected_branch_id, + found_branch_id, tail_defines_return_position_impl_trait: Some(rpit_def_id), + .. } => { - let hir::Node::Expr(hir::Expr { - kind: hir::ExprKind::If(_, then_expr, Some(else_expr)), - .. - }) = fcx.tcx.hir_node(expr_id) - else { - unreachable!(); - }; err = fcx.err_ctxt().report_mismatched_types( cause, fcx.param_env, @@ -1849,19 +1844,21 @@ impl<'tcx> CoerceMany<'tcx> { found, coercion_error, ); - let then_span = fcx.find_block_span_from_hir_id(then_expr.hir_id); - let else_span = fcx.find_block_span_from_hir_id(else_expr.hir_id); + let expected_expr = fcx.tcx.hir_node(expected_branch_id).expect_expr(); + let found_expr = fcx.tcx.hir_node(found_branch_id).expect_expr(); + let expected_span = fcx.find_block_span_from_hir_id(expected_branch_id); + let found_span = fcx.find_block_span_from_hir_id(found_branch_id); // Don't suggest wrapping whole block in `Box::new`. - if then_span != then_expr.span && else_span != else_expr.span { - let then_ty = fcx.typeck_results.borrow().expr_ty(then_expr); - let else_ty = fcx.typeck_results.borrow().expr_ty(else_expr); + if expected_span != expected_expr.span && found_span != found_expr.span { + let expected_ty = fcx.typeck_results.borrow().expr_ty(expected_expr); + let found_ty = fcx.typeck_results.borrow().expr_ty(found_expr); self.suggest_boxing_tail_for_return_position_impl_trait( fcx, &mut err, rpit_def_id, - then_ty, - else_ty, - [then_span, else_span].into_iter(), + expected_ty, + found_ty, + [expected_span, found_span].into_iter(), ); } } diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs index 9f0d00d5274fa..79b574a23ba9d 100644 --- a/compiler/rustc_hir_typeck/src/expr.rs +++ b/compiler/rustc_hir_typeck/src/expr.rs @@ -205,27 +205,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) { @@ -268,13 +258,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.diverges.set(self.function_diverges_because_of_empty_arguments.get()) }; - let ty = 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 = check(self); let ty = self.deeply_resolve_ignoring_regions(ty); // Warn for non-block expressions with diverging children. @@ -324,6 +308,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, @@ -1192,72 +1206,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 116bbf6280642..60e0fa780674e 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs @@ -3135,6 +3135,16 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { return Some((suggs, help, app, mutref)); } + // Don't suggest borrowing a tail-less block that is an arm of + // an `if`: `else &{ ... }` is not valid syntax, and the block has type `()`. + if let hir::ExprKind::Block(block, _) = expr.kind + && block.expr.is_none() + && let hir::Node::Expr(hir::Expr { kind: hir::ExprKind::If(..), .. }) = + self.tcx.parent_hir_node(expr.hir_id) + { + return None; + } + if let Some((sugg, msg)) = self.can_use_as_ref_or_mut(expr, mutability) { return Some(( sugg, diff --git a/compiler/rustc_hir_typeck/src/lib.rs b/compiler/rustc_hir_typeck/src/lib.rs index 5efb2775c77fe..db205c7d33358 100644 --- a/compiler/rustc_hir_typeck/src/lib.rs +++ b/compiler/rustc_hir_typeck/src/lib.rs @@ -6,6 +6,7 @@ #![feature(option_reference_flattening)] // 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 493f6989f342c..19e112a1d7746 100644 --- a/compiler/rustc_middle/src/traits/mod.rs +++ b/compiler/rustc_middle/src/traits/mod.rs @@ -334,7 +334,13 @@ pub enum ObligationCauseCode<'tcx> { /// Computing common supertype in an if expression IfExpression { - expr_id: HirId, + outermost_if_expr_id: HirId, + /// The branch that established the expected type: the `then` block of a plain + /// `if`/`else`, or the preceding branch of an `else if` chain. + expected_branch_id: HirId, + /// The branch whose type did not match: the `else` block, or the current + /// `else if` branch. + found_branch_id: HirId, // Is the expectation of this match expression an RPIT? tail_defines_return_position_impl_trait: Option, }, 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 b2b57e018a7cf..cec185b34ecbe 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/infer/mod.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/infer/mod.rs @@ -506,38 +506,45 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { } } }, - ObligationCauseCode::IfExpression { expr_id, .. } => { - let hir::Node::Expr(&hir::Expr { - kind: hir::ExprKind::If(cond_expr, then_expr, Some(else_expr)), - span: expr_span, - .. - }) = self.tcx.hir_node(expr_id) + ObligationCauseCode::IfExpression { + outermost_if_expr_id: expr_id, + expected_branch_id: expected_branch, + found_branch_id: found_branch, + .. + } => { + let hir::Node::Expr( + outer_if @ &hir::Expr { + kind: hir::ExprKind::If(cond_expr, ..), + span: expr_span, + .. + }, + ) = self.tcx.hir_node(expr_id) else { return; }; - let then_span = self.find_block_span_from_hir_id(then_expr.hir_id); - let then_ty = self + let expected_expr = self.tcx.hir_node(expected_branch).expect_expr(); + let found_expr = self.tcx.hir_node(found_branch).expect_expr(); + let expected_span = self.find_block_span_from_hir_id(expected_branch); + let expected_ty = self .typeck_results .as_ref() .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 + .expr_ty(expected_expr); + let found_span = self.find_block_span_from_hir_id(found_branch); + let found_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 - && else_ty.is_unit() - { - // Account for `let x = if a { 1 } else if b { 2 };` + .expr_ty(found_expr); + if !expected_ty.is_unit() && if_chain_lacks_final_else(outer_if) { + // Account for `if a { &() } else if b { }` 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"); + err.span_label(expected_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() { + if expected_span.hi() == expr_span.hi() || found_span.hi() == expr_span.hi() { // Point at condition only if either block has the same end point as // the whole expression, since that'll cause awkward overlapping spans. Some(expr_span.shrink_to_lo().to(cond_expr.peel_drop_temps().span)) @@ -551,23 +558,23 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { err.span_label(sp, "`if` and `else` have incompatible types"); } - let then_id = if let hir::ExprKind::Block(then_blk, _) = then_expr.kind { + let expected_id = if let hir::ExprKind::Block(then_blk, _) = expected_expr.kind { then_blk.hir_id } else { - then_expr.hir_id + expected_expr.hir_id }; - let else_id = if let hir::ExprKind::Block(else_blk, _) = else_expr.kind { + let found_id = if let hir::ExprKind::Block(else_blk, _) = found_expr.kind { else_blk.hir_id } else { - else_expr.hir_id + found_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, + Some(expected_id), + expected_ty, + expected_span, + Some(found_id), + found_ty, + found_span, ) { err.subdiagnostic(subdiag); } @@ -2751,3 +2758,14 @@ impl TyCategory { } } } + +fn if_chain_lacks_final_else(expr: &hir::Expr<'_>) -> bool { + let mut cur = expr; + loop { + match cur.kind { + hir::ExprKind::If(_, _, Some(next)) => cur = next, + hir::ExprKind::If(_, _, None) => return true, + _ => return false, + } + } +} diff --git a/compiler/rustc_trait_selection/src/error_reporting/infer/note_and_explain.rs b/compiler/rustc_trait_selection/src/error_reporting/infer/note_and_explain.rs index 7b5bd350fe8f9..b55129d568201 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/infer/note_and_explain.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/infer/note_and_explain.rs @@ -485,7 +485,10 @@ impl Trait for X { } // If two if arms can be coerced to a trait object, provide a structured // suggestion. - let ObligationCauseCode::IfExpression { expr_id, .. } = cause.code() else { + let ObligationCauseCode::IfExpression { + outermost_if_expr_id: expr_id, .. + } = cause.code() + else { return; }; let hir::Node::Expr(&hir::Expr { @@ -565,7 +568,10 @@ impl Trait for X { } } (ty::Adt(_, _), ty::Adt(def, args)) - if let ObligationCauseCode::IfExpression { expr_id, .. } = cause.code() + if let ObligationCauseCode::IfExpression { + outermost_if_expr_id: expr_id, + .. + } = cause.code() && let hir::Node::Expr(if_expr) = self.tcx.hir_node(*expr_id) && let hir::ExprKind::If(_, then_expr, _) = if_expr.kind && let hir::ExprKind::Block(blk, _) = then_expr.kind diff --git a/compiler/rustc_trait_selection/src/error_reporting/infer/suggest.rs b/compiler/rustc_trait_selection/src/error_reporting/infer/suggest.rs index 4ccc7ab986f1e..b1e548b1dd411 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/infer/suggest.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/infer/suggest.rs @@ -197,37 +197,31 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { self.tcx.get_impl_future_output_ty(exp_found.expected), self.tcx.get_impl_future_output_ty(exp_found.found), ) { - (Some(exp), Some(found)) if self.same_type_modulo_infer(exp, found) => match cause - .code() - { - ObligationCauseCode::IfExpression { expr_id, .. } => { - let hir::Node::Expr(hir::Expr { - kind: hir::ExprKind::If(_, then_expr, _), .. - }) = self.tcx.hir_node(*expr_id) - else { - return; - }; - let then_span = self.find_block_span_from_hir_id(then_expr.hir_id); - Some(ConsiderAddingAwait::BothFuturesSugg { - first: then_span.shrink_to_hi(), - second: exp_span.shrink_to_hi(), - }) - } - ObligationCauseCode::MatchExpressionArm(MatchExpressionArmCause { - prior_non_diverging_arms, - .. - }) => { - if let [.., arm_span] = &prior_non_diverging_arms[..] { + (Some(exp), Some(found)) if self.same_type_modulo_infer(exp, found) => { + match cause.code() { + ObligationCauseCode::IfExpression { expected_branch_id, .. } => { + let expected_span = self.find_block_span_from_hir_id(*expected_branch_id); Some(ConsiderAddingAwait::BothFuturesSugg { - first: arm_span.shrink_to_hi(), + first: expected_span.shrink_to_hi(), second: exp_span.shrink_to_hi(), }) - } else { - Some(ConsiderAddingAwait::BothFuturesHelp) } + ObligationCauseCode::MatchExpressionArm(MatchExpressionArmCause { + prior_non_diverging_arms, + .. + }) => { + if let [.., arm_span] = &prior_non_diverging_arms[..] { + Some(ConsiderAddingAwait::BothFuturesSugg { + first: arm_span.shrink_to_hi(), + second: exp_span.shrink_to_hi(), + }) + } else { + Some(ConsiderAddingAwait::BothFuturesHelp) + } + } + _ => Some(ConsiderAddingAwait::BothFuturesHelp), } - _ => Some(ConsiderAddingAwait::BothFuturesHelp), - }, + } (_, Some(ty)) if self.same_type_modulo_infer(exp_found.expected, ty) => { // FIXME: Seems like we can't have a suggestion and a note with different spans in a single subdiagnostic diag.subdiagnostic(ConsiderAddingAwait::FutureSugg { @@ -242,15 +236,9 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { span: then_span.shrink_to_hi(), }) } - ObligationCauseCode::IfExpression { expr_id, .. } => { - let hir::Node::Expr(hir::Expr { - kind: hir::ExprKind::If(_, then_expr, _), .. - }) = self.tcx.hir_node(*expr_id) - else { - return; - }; - let then_span = self.find_block_span_from_hir_id(then_expr.hir_id); - Some(ConsiderAddingAwait::FutureSugg { span: then_span.shrink_to_hi() }) + ObligationCauseCode::IfExpression { expected_branch_id, .. } => { + let expected_span = self.find_block_span_from_hir_id(*expected_branch_id); + Some(ConsiderAddingAwait::FutureSugg { span: expected_span.shrink_to_hi() }) } ObligationCauseCode::MatchExpressionArm(MatchExpressionArmCause { prior_non_diverging_arms, 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..f78701383601f 100644 --- a/tests/ui/expr/if/if-else-chain-missing-else.rs +++ b/tests/ui/expr/if/if-else-chain-missing-else.rs @@ -9,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` 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..f6bcbe2c1094f 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 +error[E0317]: `if` may be missing an `else` clause --> $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); @@ -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/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/consider-borrowing-141810-2.stderr b/tests/ui/typeck/consider-borrowing-141810-2.stderr index 44ecb5a4a945a..ff3da9e04c2fe 100644 --- a/tests/ui/typeck/consider-borrowing-141810-2.stderr +++ b/tests/ui/typeck/consider-borrowing-141810-2.stderr @@ -1,15 +1,16 @@ 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 `()` +LL | let x = if true { + | ______________- +LL | | &() + | | --- expected because of this +LL | | } else if false { + | | _____________________^ +LL | || } else { + | ||_____^ expected `&()`, found `()` +LL | | }; + | |______- `if` and `else` have incompatible types 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..4c79b8499f279 100644 --- a/tests/ui/typeck/consider-borrowing-141810-3.stderr +++ b/tests/ui/typeck/consider-borrowing-141810-3.stderr @@ -1,12 +1,12 @@ 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 `()` 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`.