From 99c32570bbe20645fa953b0618cec9970c9750fd Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Thu, 21 Jul 2022 00:03:02 +0000 Subject: [PATCH] Do if-expression obligation stuff less eagerly --- compiler/rustc_hir/src/hir.rs | 10 + .../src/infer/error_reporting/mod.rs | 335 ++++++++++++++++-- compiler/rustc_middle/src/traits/mod.rs | 16 +- .../src/traits/structural_impls.rs | 1 - compiler/rustc_typeck/src/check/_match.rs | 124 +++---- .../rustc_typeck/src/check/fn_ctxt/_impl.rs | 84 +---- .../src/check/fn_ctxt/suggestions.rs | 116 +----- ...block-control-flow-static-semantics.stderr | 18 +- src/test/ui/suggestions/return-bindings.fixed | 23 -- src/test/ui/suggestions/return-bindings.rs | 10 +- .../ui/suggestions/return-bindings.stderr | 24 +- 11 files changed, 411 insertions(+), 350 deletions(-) delete mode 100644 src/test/ui/suggestions/return-bindings.fixed diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs index 48a41c8bd245a..d6e183dd5a3bb 100644 --- a/compiler/rustc_hir/src/hir.rs +++ b/compiler/rustc_hir/src/hir.rs @@ -954,6 +954,16 @@ pub struct Block<'hir> { pub targeted_by_break: bool, } +impl<'hir> Block<'hir> { + pub fn peel_blocks(&self) -> &Block<'hir> { + let mut block = self; + while let Some(Expr { kind: ExprKind::Block(inner_block, _), .. }) = block.expr { + block = inner_block; + } + block + } +} + #[derive(Debug, HashStable_Generic)] pub struct Pat<'hir> { #[stable_hasher(ignore)] diff --git a/compiler/rustc_infer/src/infer/error_reporting/mod.rs b/compiler/rustc_infer/src/infer/error_reporting/mod.rs index 6c57e7f4347f2..a8fc306b28677 100644 --- a/compiler/rustc_infer/src/infer/error_reporting/mod.rs +++ b/compiler/rustc_infer/src/infer/error_reporting/mod.rs @@ -721,25 +721,39 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> { } }, ObligationCauseCode::IfExpression(box IfExpressionCause { - then, - else_sp, - outer, - semicolon, + then_id, + else_id, + then_ty, + else_ty, + outer_span, opt_suggest_box_span, }) => { - err.span_label(then, "expected because of this"); - if let Some(sp) = outer { + let then_span = self.find_block_span_from_hir_id(then_id); + let else_span = self.find_block_span_from_hir_id(then_id); + err.span_label(then_span, "expected because of this"); + if let Some(sp) = outer_span { err.span_label(sp, "`if` and `else` have incompatible types"); } + let semicolon = if let hir::Node::Block(blk) = self.tcx.hir().get(then_id) + && let Some(remove_semicolon) = self.could_remove_semicolon(blk, else_ty) + { + Some(remove_semicolon) + } else if let hir::Node::Block(blk) = self.tcx.hir().get(else_id) + && let Some(remove_semicolon) = self.could_remove_semicolon(blk, then_ty) + { + Some(remove_semicolon) + } else { + None + }; if let Some((sp, boxed)) = semicolon { if matches!(boxed, StatementAsExpression::NeedsBoxing) { err.multipart_suggestion( "consider removing this semicolon and boxing the expression", vec![ - (then.shrink_to_lo(), "Box::new(".to_string()), - (then.shrink_to_hi(), ")".to_string()), - (else_sp.shrink_to_lo(), "Box::new(".to_string()), - (else_sp.shrink_to_hi(), ")".to_string()), + (then_span.shrink_to_lo(), "Box::new(".to_string()), + (then_span.shrink_to_hi(), ")".to_string()), + (else_span.shrink_to_lo(), "Box::new(".to_string()), + (else_span.shrink_to_hi(), ")".to_string()), (sp, String::new()), ], Applicability::MachineApplicable, @@ -752,12 +766,21 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> { Applicability::MachineApplicable, ); } + } else { + let suggested = if let hir::Node::Block(blk) = self.tcx.hir().get(then_id) { + self.consider_returning_binding(blk, else_ty, err) + } else { + false + }; + if !suggested && let hir::Node::Block(blk) = self.tcx.hir().get(else_id) { + self.consider_returning_binding(blk, then_ty, err); + } } if let Some(ret_sp) = opt_suggest_box_span { self.suggest_boxing_for_return_impl_trait( err, ret_sp, - [then, else_sp].into_iter(), + [then_span, else_span].into_iter(), ); } } @@ -1870,40 +1893,41 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> { self.get_impl_future_output_ty(exp_found.expected).map(Binder::skip_binder), self.get_impl_future_output_ty(exp_found.found).map(Binder::skip_binder), ) { - (Some(exp), Some(found)) if self.same_type_modulo_infer(exp, found) => { - match cause.code() { - ObligationCauseCode::IfExpression(box IfExpressionCause { then, .. }) => { + (Some(exp), Some(found)) if self.same_type_modulo_infer(exp, found) => match cause + .code() + { + ObligationCauseCode::IfExpression(box IfExpressionCause { then_id, .. }) => { + let then_span = self.find_block_span_from_hir_id(*then_id); + diag.multipart_suggestion( + "consider `await`ing on both `Future`s", + vec![ + (then_span.shrink_to_hi(), ".await".to_string()), + (exp_span.shrink_to_hi(), ".await".to_string()), + ], + Applicability::MaybeIncorrect, + ); + } + ObligationCauseCode::MatchExpressionArm(box MatchExpressionArmCause { + prior_arms, + .. + }) => { + if let [.., arm_span] = &prior_arms[..] { diag.multipart_suggestion( "consider `await`ing on both `Future`s", vec![ - (then.shrink_to_hi(), ".await".to_string()), + (arm_span.shrink_to_hi(), ".await".to_string()), (exp_span.shrink_to_hi(), ".await".to_string()), ], Applicability::MaybeIncorrect, ); - } - ObligationCauseCode::MatchExpressionArm(box MatchExpressionArmCause { - prior_arms, - .. - }) => { - if let [.., arm_span] = &prior_arms[..] { - diag.multipart_suggestion( - "consider `await`ing on both `Future`s", - vec![ - (arm_span.shrink_to_hi(), ".await".to_string()), - (exp_span.shrink_to_hi(), ".await".to_string()), - ], - Applicability::MaybeIncorrect, - ); - } else { - diag.help("consider `await`ing on both `Future`s"); - } - } - _ => { + } else { diag.help("consider `await`ing on both `Future`s"); } } - } + _ => { + diag.help("consider `await`ing on both `Future`s"); + } + }, (_, Some(ty)) if self.same_type_modulo_infer(exp_found.expected, ty) => { diag.span_suggestion_verbose( exp_span.shrink_to_hi(), @@ -1914,10 +1938,18 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> { } (Some(ty), _) if self.same_type_modulo_infer(ty, exp_found.found) => match cause.code() { - ObligationCauseCode::Pattern { span: Some(span), .. } - | ObligationCauseCode::IfExpression(box IfExpressionCause { then: span, .. }) => { + ObligationCauseCode::Pattern { span: Some(then_span), .. } => { + diag.span_suggestion_verbose( + then_span.shrink_to_hi(), + "consider `await`ing on the `Future`", + ".await", + Applicability::MaybeIncorrect, + ); + } + ObligationCauseCode::IfExpression(box IfExpressionCause { then_id, .. }) => { + let then_span = self.find_block_span_from_hir_id(*then_id); diag.span_suggestion_verbose( - span.shrink_to_hi(), + then_span.shrink_to_hi(), "consider `await`ing on the `Future`", ".await", Applicability::MaybeIncorrect, @@ -2808,3 +2840,230 @@ impl TyCategory { } } } + +impl<'tcx> InferCtxt<'_, 'tcx> { + pub fn find_block_span(&self, block: &'tcx hir::Block<'tcx>) -> Span { + let block = block.peel_blocks(); + if let Some(expr) = &block.expr { + expr.span + } else if let Some(stmt) = block.stmts.last() { + // possibly incorrect trailing `;` in the else arm + stmt.span + } else { + // empty block; point at its entirety + block.span + } + } + + pub fn find_block_span_from_hir_id(&self, hir_id: hir::HirId) -> Span { + match self.tcx.hir().get(hir_id) { + hir::Node::Block(blk) => self.find_block_span(blk), + // The parser was in a weird state if either of these happen... + hir::Node::Expr(e) => e.span, + _ => rustc_span::DUMMY_SP, + } + } + + pub fn could_remove_semicolon( + &self, + blk: &'tcx hir::Block<'tcx>, + expected_ty: Ty<'tcx>, + ) -> Option<(Span, StatementAsExpression)> { + let blk = blk.peel_blocks(); + // Do not suggest if we have a tail expr. + if blk.expr.is_some() { + return None; + } + // Be helpful when the user wrote `{... expr;}` and + // taking the `;` off is enough to fix the error. + let last_stmt = blk.stmts.last()?; + let hir::StmtKind::Semi(ref last_expr) = last_stmt.kind else { + return None; + }; + let last_expr_ty = self.in_progress_typeck_results?.borrow().expr_ty_opt(*last_expr)?; + let needs_box = match (last_expr_ty.kind(), expected_ty.kind()) { + _ if last_expr_ty.references_error() => return None, + _ if self.same_type_modulo_infer(last_expr_ty, expected_ty) => { + StatementAsExpression::CorrectType + } + (ty::Opaque(last_def_id, _), ty::Opaque(exp_def_id, _)) + if last_def_id == exp_def_id => + { + StatementAsExpression::CorrectType + } + (ty::Opaque(last_def_id, last_bounds), ty::Opaque(exp_def_id, exp_bounds)) => { + debug!( + "both opaque, likely future {:?} {:?} {:?} {:?}", + last_def_id, last_bounds, exp_def_id, exp_bounds + ); + + let last_local_id = last_def_id.as_local()?; + let exp_local_id = exp_def_id.as_local()?; + + match ( + &self.tcx.hir().expect_item(last_local_id).kind, + &self.tcx.hir().expect_item(exp_local_id).kind, + ) { + ( + hir::ItemKind::OpaqueTy(hir::OpaqueTy { bounds: last_bounds, .. }), + hir::ItemKind::OpaqueTy(hir::OpaqueTy { bounds: exp_bounds, .. }), + ) if iter::zip(*last_bounds, *exp_bounds).all(|(left, right)| { + match (left, right) { + ( + hir::GenericBound::Trait(tl, ml), + hir::GenericBound::Trait(tr, mr), + ) if tl.trait_ref.trait_def_id() == tr.trait_ref.trait_def_id() + && ml == mr => + { + true + } + ( + hir::GenericBound::LangItemTrait(langl, _, _, argsl), + hir::GenericBound::LangItemTrait(langr, _, _, argsr), + ) if langl == langr => { + // FIXME: consider the bounds! + debug!("{:?} {:?}", argsl, argsr); + true + } + _ => false, + } + }) => + { + StatementAsExpression::NeedsBoxing + } + _ => StatementAsExpression::CorrectType, + } + } + _ => return None, + }; + let span = if last_stmt.span.from_expansion() { + let mac_call = rustc_span::source_map::original_sp(last_stmt.span, blk.span); + self.tcx.sess.source_map().mac_call_stmt_semi_span(mac_call)? + } else { + last_stmt.span.with_lo(last_stmt.span.hi() - BytePos(1)) + }; + Some((span, needs_box)) + } + + pub fn consider_returning_binding( + &self, + blk: &'tcx hir::Block<'tcx>, + expected_ty: Ty<'tcx>, + err: &mut Diagnostic, + ) -> bool { + let blk = blk.peel_blocks(); + // Do not suggest if we have a tail expr. + if blk.expr.is_some() { + return false; + } + let mut shadowed = FxHashSet::default(); + let mut candidate_idents = vec![]; + let mut find_compatible_candidates = |pat: &hir::Pat<'_>| { + if let hir::PatKind::Binding(_, hir_id, ident, _) = &pat.kind + && let Some(pat_ty) = self + .in_progress_typeck_results + .and_then(|typeck_results| typeck_results.borrow().node_type_opt(*hir_id)) + { + let pat_ty = self.resolve_vars_if_possible(pat_ty); + if self.same_type_modulo_infer(pat_ty, expected_ty) + && !(pat_ty, expected_ty).references_error() + && shadowed.insert(ident.name) + { + candidate_idents.push((*ident, pat_ty)); + } + } + true + }; + + let hir = self.tcx.hir(); + for stmt in blk.stmts.iter().rev() { + let hir::StmtKind::Local(local) = &stmt.kind else { continue; }; + local.pat.walk(&mut find_compatible_candidates); + } + match hir.find(hir.get_parent_node(blk.hir_id)) { + Some(hir::Node::Expr(hir::Expr { hir_id, .. })) => { + match hir.find(hir.get_parent_node(*hir_id)) { + Some(hir::Node::Arm(hir::Arm { pat, .. })) => { + pat.walk(&mut find_compatible_candidates); + } + Some( + hir::Node::Item(hir::Item { kind: hir::ItemKind::Fn(_, _, body), .. }) + | hir::Node::ImplItem(hir::ImplItem { + kind: hir::ImplItemKind::Fn(_, body), + .. + }) + | hir::Node::TraitItem(hir::TraitItem { + kind: hir::TraitItemKind::Fn(_, hir::TraitFn::Provided(body)), + .. + }) + | hir::Node::Expr(hir::Expr { + kind: hir::ExprKind::Closure(hir::Closure { body, .. }), + .. + }), + ) => { + for param in hir.body(*body).params { + param.pat.walk(&mut find_compatible_candidates); + } + } + Some(hir::Node::Expr(hir::Expr { + kind: + hir::ExprKind::If( + hir::Expr { kind: hir::ExprKind::Let(let_), .. }, + then_block, + _, + ), + .. + })) if then_block.hir_id == *hir_id => { + let_.pat.walk(&mut find_compatible_candidates); + } + _ => {} + } + } + _ => {} + } + + match &candidate_idents[..] { + [(ident, _ty)] => { + let sm = self.tcx.sess.source_map(); + if let Some(stmt) = blk.stmts.last() { + let stmt_span = sm.stmt_span(stmt.span, blk.span); + let sugg = if sm.is_multiline(blk.span) + && let Some(spacing) = sm.indentation_before(stmt_span) + { + format!("\n{spacing}{ident}") + } else { + format!(" {ident}") + }; + err.span_suggestion_verbose( + stmt_span.shrink_to_hi(), + format!("consider returning the local binding `{ident}`"), + sugg, + Applicability::MaybeIncorrect, + ); + } else { + let sugg = if sm.is_multiline(blk.span) + && let Some(spacing) = sm.indentation_before(blk.span.shrink_to_lo()) + { + format!("\n{spacing} {ident}\n{spacing}") + } else { + format!(" {ident} ") + }; + let left_span = sm.span_through_char(blk.span, '{').shrink_to_hi(); + err.span_suggestion_verbose( + sm.span_extend_while(left_span, |c| c.is_whitespace()).unwrap_or(left_span), + format!("consider returning the local binding `{ident}`"), + sugg, + Applicability::MaybeIncorrect, + ); + } + true + } + values if (1..3).contains(&values.len()) => { + let spans = values.iter().map(|(ident, _)| ident.span).collect::>(); + err.span_note(spans, "consider returning one of these bindings"); + true + } + _ => false, + } + } +} diff --git a/compiler/rustc_middle/src/traits/mod.rs b/compiler/rustc_middle/src/traits/mod.rs index 75559d4f8b843..7a38c800ccf5f 100644 --- a/compiler/rustc_middle/src/traits/mod.rs +++ b/compiler/rustc_middle/src/traits/mod.rs @@ -351,7 +351,7 @@ pub enum ObligationCauseCode<'tcx> { ConstPatternStructural, /// Computing common supertype in an if expression - IfExpression(Box), + IfExpression(Box>), /// Computing common supertype of an if expression with no else counter-part IfExpressionWithNoElse, @@ -498,12 +498,14 @@ pub struct MatchExpressionArmCause<'tcx> { pub opt_suggest_box_span: Option, } -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub struct IfExpressionCause { - pub then: Span, - pub else_sp: Span, - pub outer: Option, - pub semicolon: Option<(Span, StatementAsExpression)>, +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +#[derive(Lift, TypeFoldable, TypeVisitable)] +pub struct IfExpressionCause<'tcx> { + pub then_id: hir::HirId, + pub else_id: hir::HirId, + pub then_ty: Ty<'tcx>, + pub else_ty: Ty<'tcx>, + pub outer_span: Option, pub opt_suggest_box_span: Option, } diff --git a/compiler/rustc_middle/src/traits/structural_impls.rs b/compiler/rustc_middle/src/traits/structural_impls.rs index 8f1a1564fc8e8..7fbd57ac7354a 100644 --- a/compiler/rustc_middle/src/traits/structural_impls.rs +++ b/compiler/rustc_middle/src/traits/structural_impls.rs @@ -130,7 +130,6 @@ impl fmt::Debug for traits::ImplSourceConstDestructData { // Lift implementations TrivialTypeTraversalAndLiftImpls! { - super::IfExpressionCause, super::ImplSourceDiscriminantKindData, super::ImplSourcePointeeData, } diff --git a/compiler/rustc_typeck/src/check/_match.rs b/compiler/rustc_typeck/src/check/_match.rs index c733f0d3c86d0..00547a6d8278f 100644 --- a/compiler/rustc_typeck/src/check/_match.rs +++ b/compiler/rustc_typeck/src/check/_match.rs @@ -216,13 +216,17 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ) -> (Span, Option<(Span, StatementAsExpression)>) { let arm = &arms[i]; let (arm_span, mut semi_span) = if let hir::ExprKind::Block(blk, _) = &arm.body.kind { - self.find_block_span(blk, prior_arm_ty) + ( + self.find_block_span(blk), + prior_arm_ty + .and_then(|prior_arm_ty| self.could_remove_semicolon(blk, prior_arm_ty)), + ) } else { (arm.body.span, None) }; if semi_span.is_none() && i > 0 { if let hir::ExprKind::Block(blk, _) = &arms[i - 1].body.kind { - let (_, semi_span_prev) = self.find_block_span(blk, Some(arm_ty)); + let semi_span_prev = self.could_remove_semicolon(blk, arm_ty); semi_span = semi_span_prev; } } @@ -313,7 +317,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { else_ty: Ty<'tcx>, opt_suggest_box_span: Option, ) -> ObligationCause<'tcx> { - let mut outer_sp = if self.tcx.sess.source_map().is_multiline(span) { + let mut outer_span = if self.tcx.sess.source_map().is_multiline(span) { // The `if`/`else` isn't in one line in the output, include some context to make it // clear it is an if/else expression: // ``` @@ -339,69 +343,67 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { None }; - let mut remove_semicolon = None; - let error_sp = if let ExprKind::Block(block, _) = &else_expr.kind { - let (error_sp, semi_sp) = self.find_block_span(block, Some(then_ty)); - remove_semicolon = semi_sp; - if block.expr.is_none() && block.stmts.is_empty() { - // Avoid overlapping spans that aren't as readable: - // ``` - // 2 | let x = if true { - // | _____________- - // 3 | | 3 - // | | - expected because of this - // 4 | | } else { - // | |____________^ - // 5 | || - // 6 | || }; - // | || ^ - // | ||_____| - // | |______if and else have incompatible types - // | expected integer, found `()` - // ``` - // by not pointing at the entire expression: - // ``` - // 2 | let x = if true { - // | ------- `if` and `else` have incompatible types - // 3 | 3 - // | - expected because of this - // 4 | } else { - // | ____________^ - // 5 | | - // 6 | | }; - // | |_____^ expected integer, found `()` - // ``` - if outer_sp.is_some() { - outer_sp = Some(self.tcx.sess.source_map().guess_head_span(span)); - } + let (error_sp, else_id) = if let ExprKind::Block(block, _) = &else_expr.kind { + let block = block.peel_blocks(); + + // Avoid overlapping spans that aren't as readable: + // ``` + // 2 | let x = if true { + // | _____________- + // 3 | | 3 + // | | - expected because of this + // 4 | | } else { + // | |____________^ + // 5 | || + // 6 | || }; + // | || ^ + // | ||_____| + // | |______if and else have incompatible types + // | expected integer, found `()` + // ``` + // by not pointing at the entire expression: + // ``` + // 2 | let x = if true { + // | ------- `if` and `else` have incompatible types + // 3 | 3 + // | - expected because of this + // 4 | } else { + // | ____________^ + // 5 | | + // 6 | | }; + // | |_____^ expected integer, found `()` + // ``` + if block.expr.is_none() && block.stmts.is_empty() + && let Some(outer_span) = &mut outer_span + { + *outer_span = self.tcx.sess.source_map().guess_head_span(*outer_span); } - error_sp + + (self.find_block_span(block), block.hir_id) } else { - // shouldn't happen unless the parser has done something weird - else_expr.span + (else_expr.span, else_expr.hir_id) }; - // Compute `Span` of `then` part of `if`-expression. - let then_sp = if let ExprKind::Block(block, _) = &then_expr.kind { - let (then_sp, semi_sp) = self.find_block_span(block, Some(else_ty)); - remove_semicolon = remove_semicolon.or(semi_sp); + let then_id = if let ExprKind::Block(block, _) = &then_expr.kind { + let block = block.peel_blocks(); + // Exclude overlapping spans if block.expr.is_none() && block.stmts.is_empty() { - outer_sp = None; // same as in `error_sp`; cleanup output + outer_span = None; } - then_sp + block.hir_id } else { - // shouldn't happen unless the parser has done something weird - then_expr.span + then_expr.hir_id }; // Finally construct the cause: self.cause( error_sp, ObligationCauseCode::IfExpression(Box::new(IfExpressionCause { - then: then_sp, - else_sp: error_sp, - outer: outer_sp, - semicolon: remove_semicolon, + else_id, + then_id, + then_ty, + else_ty, + outer_span, opt_suggest_box_span, })), ) @@ -482,22 +484,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } - fn find_block_span( - &self, - block: &'tcx hir::Block<'tcx>, - expected_ty: Option>, - ) -> (Span, Option<(Span, StatementAsExpression)>) { - if let Some(expr) = &block.expr { - (expr.span, None) - } else if let Some(stmt) = block.stmts.last() { - // possibly incorrect trailing `;` in the else arm - (stmt.span, expected_ty.and_then(|ty| self.could_remove_semicolon(block, ty))) - } else { - // empty block; point at its entirety - (block.span, None) - } - } - // When we have a `match` as a tail expression in a `fn` with a returned `impl Trait` // we check if the different arms would work with boxed trait objects instead and // provide a structured suggestion in that case. diff --git a/compiler/rustc_typeck/src/check/fn_ctxt/_impl.rs b/compiler/rustc_typeck/src/check/fn_ctxt/_impl.rs index d079aeb4801ca..21b3c9063a78a 100644 --- a/compiler/rustc_typeck/src/check/fn_ctxt/_impl.rs +++ b/compiler/rustc_typeck/src/check/fn_ctxt/_impl.rs @@ -30,17 +30,15 @@ use rustc_middle::ty::{ }; use rustc_session::lint; use rustc_span::hygiene::DesugaringKind; -use rustc_span::source_map::{original_sp, DUMMY_SP}; use rustc_span::symbol::{kw, sym, Ident}; -use rustc_span::{self, BytePos, Span}; +use rustc_span::{Span, DUMMY_SP}; use rustc_trait_selection::infer::InferCtxtExt as _; use rustc_trait_selection::traits::error_reporting::InferCtxtExt as _; use rustc_trait_selection::traits::{ - self, ObligationCause, ObligationCauseCode, StatementAsExpression, TraitEngine, TraitEngineExt, + self, ObligationCause, ObligationCauseCode, TraitEngine, TraitEngineExt, }; use std::collections::hash_map::Entry; -use std::iter; use std::slice; impl<'a, 'tcx> FnCtxt<'a, 'tcx> { @@ -1059,84 +1057,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { )); } - pub(in super::super) fn could_remove_semicolon( - &self, - blk: &'tcx hir::Block<'tcx>, - expected_ty: Ty<'tcx>, - ) -> Option<(Span, StatementAsExpression)> { - // Be helpful when the user wrote `{... expr;}` and - // taking the `;` off is enough to fix the error. - let last_stmt = blk.stmts.last()?; - let hir::StmtKind::Semi(ref last_expr) = last_stmt.kind else { - return None; - }; - let last_expr_ty = self.node_ty(last_expr.hir_id); - let needs_box = match (last_expr_ty.kind(), expected_ty.kind()) { - (ty::Opaque(last_def_id, _), ty::Opaque(exp_def_id, _)) - if last_def_id == exp_def_id => - { - StatementAsExpression::CorrectType - } - (ty::Opaque(last_def_id, last_bounds), ty::Opaque(exp_def_id, exp_bounds)) => { - debug!( - "both opaque, likely future {:?} {:?} {:?} {:?}", - last_def_id, last_bounds, exp_def_id, exp_bounds - ); - - let last_local_id = last_def_id.as_local()?; - let exp_local_id = exp_def_id.as_local()?; - - match ( - &self.tcx.hir().expect_item(last_local_id).kind, - &self.tcx.hir().expect_item(exp_local_id).kind, - ) { - ( - hir::ItemKind::OpaqueTy(hir::OpaqueTy { bounds: last_bounds, .. }), - hir::ItemKind::OpaqueTy(hir::OpaqueTy { bounds: exp_bounds, .. }), - ) if iter::zip(*last_bounds, *exp_bounds).all(|(left, right)| { - match (left, right) { - ( - hir::GenericBound::Trait(tl, ml), - hir::GenericBound::Trait(tr, mr), - ) if tl.trait_ref.trait_def_id() == tr.trait_ref.trait_def_id() - && ml == mr => - { - true - } - ( - hir::GenericBound::LangItemTrait(langl, _, _, argsl), - hir::GenericBound::LangItemTrait(langr, _, _, argsr), - ) if langl == langr => { - // FIXME: consider the bounds! - debug!("{:?} {:?}", argsl, argsr); - true - } - _ => false, - } - }) => - { - StatementAsExpression::NeedsBoxing - } - _ => StatementAsExpression::CorrectType, - } - } - _ => StatementAsExpression::CorrectType, - }; - if (matches!(last_expr_ty.kind(), ty::Error(_)) - || self.can_sub(self.param_env, last_expr_ty, expected_ty).is_err()) - && matches!(needs_box, StatementAsExpression::CorrectType) - { - return None; - } - let span = if last_stmt.span.from_expansion() { - let mac_call = original_sp(last_stmt.span, blk.span); - self.tcx.sess.source_map().mac_call_stmt_semi_span(mac_call)? - } else { - last_stmt.span.with_lo(last_stmt.span.hi() - BytePos(1)) - }; - Some((span, needs_box)) - } - // Instantiates the given path, which must refer to an item with the given // number of type parameters and type. #[instrument(skip(self, span), level = "debug")] diff --git a/compiler/rustc_typeck/src/check/fn_ctxt/suggestions.rs b/compiler/rustc_typeck/src/check/fn_ctxt/suggestions.rs index 871fc4a21f2a7..3e6ff72204f4c 100644 --- a/compiler/rustc_typeck/src/check/fn_ctxt/suggestions.rs +++ b/compiler/rustc_typeck/src/check/fn_ctxt/suggestions.rs @@ -3,7 +3,6 @@ use crate::astconv::AstConv; use crate::errors::{AddReturnTypeSuggestion, ExpectedReturnTypeLabel}; use rustc_ast::util::parser::ExprPrecedence; -use rustc_data_structures::fx::FxHashSet; use rustc_errors::{Applicability, Diagnostic, MultiSpan}; use rustc_hir as hir; use rustc_hir::def::{CtorOf, DefKind}; @@ -14,7 +13,7 @@ use rustc_hir::{ use rustc_infer::infer::{self, TyCtxtInferExt}; use rustc_infer::traits::{self, StatementAsExpression}; use rustc_middle::lint::in_external_macro; -use rustc_middle::ty::{self, Binder, IsSuggestable, Subst, ToPredicate, Ty, TypeVisitable}; +use rustc_middle::ty::{self, Binder, IsSuggestable, Subst, ToPredicate, Ty}; use rustc_span::symbol::sym; use rustc_span::Span; use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt as _; @@ -904,117 +903,4 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { false } } - - pub(crate) fn consider_returning_binding( - &self, - blk: &'tcx hir::Block<'tcx>, - expected_ty: Ty<'tcx>, - err: &mut Diagnostic, - ) { - let mut shadowed = FxHashSet::default(); - let mut candidate_idents = vec![]; - let mut find_compatible_candidates = |pat: &hir::Pat<'_>| { - if let hir::PatKind::Binding(_, hir_id, ident, _) = &pat.kind - && let Some(pat_ty) = self.typeck_results.borrow().node_type_opt(*hir_id) - { - let pat_ty = self.resolve_vars_if_possible(pat_ty); - if self.can_coerce(pat_ty, expected_ty) - && !(pat_ty, expected_ty).references_error() - && shadowed.insert(ident.name) - { - candidate_idents.push((*ident, pat_ty)); - } - } - true - }; - - let hir = self.tcx.hir(); - for stmt in blk.stmts.iter().rev() { - let StmtKind::Local(local) = &stmt.kind else { continue; }; - local.pat.walk(&mut find_compatible_candidates); - } - match hir.find(hir.get_parent_node(blk.hir_id)) { - Some(hir::Node::Expr(hir::Expr { hir_id, .. })) => { - match hir.find(hir.get_parent_node(*hir_id)) { - Some(hir::Node::Arm(hir::Arm { pat, .. })) => { - pat.walk(&mut find_compatible_candidates); - } - Some( - hir::Node::Item(hir::Item { kind: hir::ItemKind::Fn(_, _, body), .. }) - | hir::Node::ImplItem(hir::ImplItem { - kind: hir::ImplItemKind::Fn(_, body), - .. - }) - | hir::Node::TraitItem(hir::TraitItem { - kind: hir::TraitItemKind::Fn(_, hir::TraitFn::Provided(body)), - .. - }) - | hir::Node::Expr(hir::Expr { - kind: hir::ExprKind::Closure(hir::Closure { body, .. }), - .. - }), - ) => { - for param in hir.body(*body).params { - param.pat.walk(&mut find_compatible_candidates); - } - } - Some(hir::Node::Expr(hir::Expr { - kind: - hir::ExprKind::If( - hir::Expr { kind: hir::ExprKind::Let(let_), .. }, - then_block, - _, - ), - .. - })) if then_block.hir_id == *hir_id => { - let_.pat.walk(&mut find_compatible_candidates); - } - _ => {} - } - } - _ => {} - } - - match &candidate_idents[..] { - [(ident, _ty)] => { - let sm = self.tcx.sess.source_map(); - if let Some(stmt) = blk.stmts.last() { - let stmt_span = sm.stmt_span(stmt.span, blk.span); - let sugg = if sm.is_multiline(blk.span) - && let Some(spacing) = sm.indentation_before(stmt_span) - { - format!("\n{spacing}{ident}") - } else { - format!(" {ident}") - }; - err.span_suggestion_verbose( - stmt_span.shrink_to_hi(), - format!("consider returning the local binding `{ident}`"), - sugg, - Applicability::MachineApplicable, - ); - } else { - let sugg = if sm.is_multiline(blk.span) - && let Some(spacing) = sm.indentation_before(blk.span.shrink_to_lo()) - { - format!("\n{spacing} {ident}\n{spacing}") - } else { - format!(" {ident} ") - }; - let left_span = sm.span_through_char(blk.span, '{').shrink_to_hi(); - err.span_suggestion_verbose( - sm.span_extend_while(left_span, |c| c.is_whitespace()).unwrap_or(left_span), - format!("consider returning the local binding `{ident}`"), - sugg, - Applicability::MachineApplicable, - ); - } - } - values if (1..3).contains(&values.len()) => { - let spans = values.iter().map(|(ident, _)| ident.span).collect::>(); - err.span_note(spans, "consider returning one of these bindings"); - } - _ => {} - } - } } diff --git a/src/test/ui/async-await/async-block-control-flow-static-semantics.stderr b/src/test/ui/async-await/async-block-control-flow-static-semantics.stderr index ada6e357aea5e..e5887689690e7 100644 --- a/src/test/ui/async-await/async-block-control-flow-static-semantics.stderr +++ b/src/test/ui/async-await/async-block-control-flow-static-semantics.stderr @@ -18,14 +18,6 @@ LL | | break 0u8; LL | | }; | |_________- enclosing `async` block -error[E0271]: type mismatch resolving ` as Future>::Output == ()` - --> $DIR/async-block-control-flow-static-semantics.rs:26:39 - | -LL | let _: &dyn Future = █ - | ^^^^^^ expected `()`, found `u8` - | - = note: required for the cast from `impl Future` to the object type `dyn Future` - error[E0308]: mismatched types --> $DIR/async-block-control-flow-static-semantics.rs:21:58 | @@ -40,7 +32,7 @@ LL | | } | |_^ expected `u8`, found `()` error[E0271]: type mismatch resolving ` as Future>::Output == ()` - --> $DIR/async-block-control-flow-static-semantics.rs:17:39 + --> $DIR/async-block-control-flow-static-semantics.rs:26:39 | LL | let _: &dyn Future = █ | ^^^^^^ expected `()`, found `u8` @@ -55,6 +47,14 @@ LL | fn return_targets_async_block_not_fn() -> u8 { | | | implicitly returns `()` as its body has no tail or `return` expression +error[E0271]: type mismatch resolving ` as Future>::Output == ()` + --> $DIR/async-block-control-flow-static-semantics.rs:17:39 + | +LL | let _: &dyn Future = █ + | ^^^^^^ expected `()`, found `u8` + | + = note: required for the cast from `impl Future` to the object type `dyn Future` + error[E0308]: mismatched types --> $DIR/async-block-control-flow-static-semantics.rs:47:44 | diff --git a/src/test/ui/suggestions/return-bindings.fixed b/src/test/ui/suggestions/return-bindings.fixed deleted file mode 100644 index 4fabc411abcbe..0000000000000 --- a/src/test/ui/suggestions/return-bindings.fixed +++ /dev/null @@ -1,23 +0,0 @@ -// run-rustfix - -#![allow(unused)] - -fn a(i: i32) -> i32 { i } -//~^ ERROR mismatched types - -fn b(opt_str: Option) { - let s: String = if let Some(s) = opt_str { - s - //~^ ERROR mismatched types - } else { - String::new() - }; -} - -fn c() -> Option { - //~^ ERROR mismatched types - let x = Some(1); - x -} - -fn main() {} diff --git a/src/test/ui/suggestions/return-bindings.rs b/src/test/ui/suggestions/return-bindings.rs index d05b4ba27d6e8..80c83a70d50c1 100644 --- a/src/test/ui/suggestions/return-bindings.rs +++ b/src/test/ui/suggestions/return-bindings.rs @@ -1,5 +1,3 @@ -// run-rustfix - #![allow(unused)] fn a(i: i32) -> i32 {} @@ -18,4 +16,12 @@ fn c() -> Option { let x = Some(1); } +fn d(opt_str: Option) { + let s: String = if let Some(s) = opt_str { + //~^ ERROR mismatched types + } else { + String::new() + }; +} + fn main() {} diff --git a/src/test/ui/suggestions/return-bindings.stderr b/src/test/ui/suggestions/return-bindings.stderr index e5d4925500556..53ef7106fa808 100644 --- a/src/test/ui/suggestions/return-bindings.stderr +++ b/src/test/ui/suggestions/return-bindings.stderr @@ -1,5 +1,5 @@ error[E0308]: mismatched types - --> $DIR/return-bindings.rs:5:17 + --> $DIR/return-bindings.rs:3:17 | LL | fn a(i: i32) -> i32 {} | - ^^^ expected `i32`, found `()` @@ -12,7 +12,7 @@ LL | fn a(i: i32) -> i32 { i } | + error[E0308]: mismatched types - --> $DIR/return-bindings.rs:9:46 + --> $DIR/return-bindings.rs:7:46 | LL | let s: String = if let Some(s) = opt_str { | ______________________________________________^ @@ -28,7 +28,7 @@ LL ~ | error[E0308]: mismatched types - --> $DIR/return-bindings.rs:16:11 + --> $DIR/return-bindings.rs:14:11 | LL | fn c() -> Option { | - ^^^^^^^^^^^ expected enum `Option`, found `()` @@ -43,6 +43,22 @@ LL ~ let x = Some(1); LL + x | -error: aborting due to 3 previous errors +error[E0308]: mismatched types + --> $DIR/return-bindings.rs:20:46 + | +LL | let s: String = if let Some(s) = opt_str { + | ______________________________________________^ +LL | | +LL | | } else { + | |_____^ expected struct `String`, found `()` + | +help: consider returning the local binding `s` + | +LL ~ let s: String = if let Some(s) = opt_str { +LL + s +LL ~ + | + +error: aborting due to 4 previous errors For more information about this error, try `rustc --explain E0308`.