Skip to content

Commit 9877dbd

Browse files
Auto merge of #147484 - sgasho:fix/141690_check_expr_if, r=<try>
Fix check_expr_if to point to a more accurate location of the compilation error in some cases
2 parents 044d68c + 5c38b51 commit 9877dbd

15 files changed

+428
-163
lines changed
Lines changed: 281 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,281 @@
1+
use rustc_errors::Diag;
2+
use rustc_hir::{self as hir, HirId};
3+
use rustc_infer::traits;
4+
use rustc_middle::ty::{Ty, TypeVisitableExt};
5+
use rustc_span::{ErrorGuaranteed, Span};
6+
use smallvec::SmallVec;
7+
8+
use crate::coercion::{CoerceMany, DynamicCoerceMany};
9+
use crate::{Diverges, Expectation, FnCtxt, bug};
10+
11+
#[derive(Clone, Debug)]
12+
struct BranchBody<'tcx> {
13+
expr: &'tcx hir::Expr<'tcx>,
14+
ty: Ty<'tcx>,
15+
diverges: Diverges,
16+
span: Span,
17+
}
18+
19+
#[derive(Clone, Debug)]
20+
struct IfGuardedBranch<'tcx> {
21+
if_expr: &'tcx hir::Expr<'tcx>,
22+
cond_diverges: Diverges,
23+
body: BranchBody<'tcx>,
24+
}
25+
26+
#[derive(Default, Debug)]
27+
struct IfGuardedBranches<'tcx> {
28+
branches: SmallVec<[IfGuardedBranch<'tcx>; 4]>,
29+
cond_error: Option<ErrorGuaranteed>,
30+
}
31+
32+
#[derive(Clone, Debug)]
33+
enum IfChainTail<'tcx> {
34+
FinalElse(BranchBody<'tcx>),
35+
Missing(&'tcx hir::Expr<'tcx>),
36+
}
37+
38+
impl<'tcx> IfChainTail<'tcx> {
39+
fn expr(&self) -> &'tcx hir::Expr<'tcx> {
40+
match &self {
41+
IfChainTail::FinalElse(else_branch) => else_branch.expr,
42+
IfChainTail::Missing(last_if_expr) => *last_if_expr,
43+
}
44+
}
45+
46+
fn diverges(&self) -> Diverges {
47+
match &self {
48+
IfChainTail::FinalElse(else_branch) => else_branch.diverges,
49+
IfChainTail::Missing(_) => Diverges::Maybe,
50+
}
51+
}
52+
}
53+
54+
impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
55+
pub(crate) fn check_expr_if(
56+
&self,
57+
expr_id: HirId,
58+
sp: Span,
59+
orig_expected: Expectation<'tcx>,
60+
) -> Ty<'tcx> {
61+
let root_if_expr = self.tcx.hir_expect_expr(expr_id);
62+
let expected = orig_expected.try_structurally_resolve_and_adjust_for_branches(self, sp);
63+
64+
let initial_diverges = self.diverges.get();
65+
66+
let (guarded, tail) = self.collect_if_chain(root_if_expr, expected);
67+
68+
let coerce_to_ty = expected.coercion_target_type(self, sp);
69+
let mut coerce: DynamicCoerceMany<'_> = CoerceMany::new(coerce_to_ty);
70+
71+
let tail_defines_return_position_impl_trait =
72+
self.return_position_impl_trait_from_match_expectation(orig_expected);
73+
74+
for (idx, branch) in guarded.branches.iter().enumerate() {
75+
if idx > 0 {
76+
let merged_ty = coerce.merged_ty();
77+
self.ensure_if_branch_type(branch.if_expr.hir_id, merged_ty);
78+
}
79+
80+
let branch_body = &branch.body;
81+
let next_else_expr =
82+
guarded.branches.get(idx + 1).map(|next| next.if_expr).or(tail.expr().into());
83+
let opt_prev_branch = if idx > 0 { guarded.branches.get(idx - 1) } else { None };
84+
let mut branch_cause = if let Some(next_else_expr) = next_else_expr {
85+
self.if_cause(
86+
opt_prev_branch.unwrap_or(branch).if_expr.hir_id,
87+
next_else_expr,
88+
tail_defines_return_position_impl_trait,
89+
)
90+
} else {
91+
self.misc(branch_body.span)
92+
};
93+
let cause_span =
94+
if idx == 0 { Some(root_if_expr.span) } else { Some(branch_body.span) };
95+
96+
self.coerce_if_arm(
97+
&mut coerce,
98+
&mut branch_cause,
99+
branch_body.expr,
100+
branch_body.ty,
101+
cause_span,
102+
opt_prev_branch.and_then(|b| b.body.span.into()),
103+
);
104+
}
105+
106+
match &tail {
107+
IfChainTail::FinalElse(else_branch) => {
108+
let mut else_cause = self.if_cause(
109+
expr_id,
110+
else_branch.expr,
111+
tail_defines_return_position_impl_trait,
112+
);
113+
self.coerce_if_arm(
114+
&mut coerce,
115+
&mut else_cause,
116+
else_branch.expr,
117+
else_branch.ty,
118+
None,
119+
guarded.branches.last().and_then(|b| b.body.span.into()),
120+
);
121+
}
122+
IfChainTail::Missing(last_if_expr) => {
123+
let hir::ExprKind::If(tail_cond, tail_then, _) = last_if_expr.kind else {
124+
bug!("expected `if` expression, found {:#?}", last_if_expr);
125+
};
126+
self.if_fallback_coercion(last_if_expr.span, tail_cond, tail_then, &mut coerce);
127+
}
128+
}
129+
130+
self.set_diverges(initial_diverges, &guarded, &tail);
131+
132+
let result_ty = coerce.complete(self);
133+
134+
let final_ty = if let Some(guar) = guarded.cond_error {
135+
Ty::new_error(self.tcx, guar)
136+
} else {
137+
result_ty
138+
};
139+
140+
for branch in guarded.branches.iter().skip(1) {
141+
self.overwrite_if_branch_type(branch.if_expr.hir_id, final_ty);
142+
}
143+
if let Err(guar) = final_ty.error_reported() {
144+
self.set_tainted_by_errors(guar);
145+
}
146+
147+
final_ty
148+
}
149+
150+
fn coerce_if_arm(
151+
&self,
152+
coerce: &mut DynamicCoerceMany<'tcx>,
153+
cause: &mut traits::ObligationCause<'tcx>,
154+
expr: &'tcx hir::Expr<'tcx>,
155+
ty: Ty<'tcx>,
156+
cause_span: Option<Span>,
157+
prev_branch_span: Option<Span>,
158+
) {
159+
if let Some(span) = cause_span {
160+
cause.span = span;
161+
}
162+
coerce.coerce_inner(
163+
self,
164+
cause,
165+
Some(expr),
166+
ty,
167+
move |err| self.explain_if_branch_mismatch(err, prev_branch_span),
168+
false,
169+
);
170+
}
171+
172+
fn check_if_condition(
173+
&self,
174+
cond_expr: &'tcx hir::Expr<'tcx>,
175+
then_span: Span,
176+
) -> (Ty<'tcx>, Diverges) {
177+
let cond_ty = self.check_expr_has_type_or_error(cond_expr, self.tcx.types.bool, |_| {});
178+
self.warn_if_unreachable(
179+
cond_expr.hir_id,
180+
then_span,
181+
"block in `if` or `while` expression",
182+
);
183+
let cond_diverges = self.take_diverges();
184+
(cond_ty, cond_diverges)
185+
}
186+
187+
fn collect_if_chain(
188+
&self,
189+
mut current_if: &'tcx hir::Expr<'tcx>,
190+
expected: Expectation<'tcx>,
191+
) -> (IfGuardedBranches<'tcx>, IfChainTail<'tcx>) {
192+
let mut chain: IfGuardedBranches<'tcx> = IfGuardedBranches::default();
193+
194+
loop {
195+
let Some(else_expr) = self.collect_if_branch(current_if, expected, &mut chain) else {
196+
return (chain, IfChainTail::Missing(current_if));
197+
};
198+
199+
if let hir::ExprKind::If(..) = else_expr.kind {
200+
current_if = else_expr;
201+
continue;
202+
}
203+
204+
return (chain, IfChainTail::FinalElse(self.check_branch_body(else_expr, expected)));
205+
}
206+
}
207+
208+
fn collect_if_branch(
209+
&self,
210+
if_expr: &'tcx hir::Expr<'tcx>,
211+
expected: Expectation<'tcx>,
212+
chain: &mut IfGuardedBranches<'tcx>,
213+
) -> Option<&'tcx hir::Expr<'tcx>> {
214+
let hir::ExprKind::If(cond_expr, then_expr, opt_else_expr) = if_expr.kind else {
215+
bug!("expected `if` expression, found {:#?}", if_expr);
216+
};
217+
218+
let (cond_ty, cond_diverges) = self.check_if_condition(cond_expr, then_expr.span);
219+
if let Err(guar) = cond_ty.error_reported() {
220+
chain.cond_error.get_or_insert(guar);
221+
}
222+
let branch_body = self.check_branch_body(then_expr, expected);
223+
224+
chain.branches.push(IfGuardedBranch { if_expr, cond_diverges, body: branch_body });
225+
226+
opt_else_expr
227+
}
228+
229+
fn reset_diverges_to_maybe(&self) {
230+
self.diverges.set(Diverges::Maybe);
231+
}
232+
233+
fn take_diverges(&self) -> Diverges {
234+
let diverges = self.diverges.get();
235+
self.reset_diverges_to_maybe();
236+
diverges
237+
}
238+
239+
fn ensure_if_branch_type(&self, hir_id: HirId, ty: Ty<'tcx>) {
240+
let mut typeck = self.typeck_results.borrow_mut();
241+
let mut node_ty = typeck.node_types_mut();
242+
node_ty.entry(hir_id).or_insert(ty);
243+
}
244+
245+
fn overwrite_if_branch_type(&self, hir_id: HirId, ty: Ty<'tcx>) {
246+
let mut typeck = self.typeck_results.borrow_mut();
247+
let mut node_ty = typeck.node_types_mut();
248+
node_ty.insert(hir_id, ty);
249+
}
250+
251+
fn check_branch_body(
252+
&self,
253+
expr: &'tcx hir::Expr<'tcx>,
254+
expected: Expectation<'tcx>,
255+
) -> BranchBody<'tcx> {
256+
self.reset_diverges_to_maybe();
257+
let ty = self.check_expr_with_expectation(expr, expected);
258+
let diverges = self.take_diverges();
259+
let span = self.find_block_span_from_hir_id(expr.hir_id);
260+
BranchBody { expr, ty, diverges, span }
261+
}
262+
263+
fn explain_if_branch_mismatch(&self, err: &mut Diag<'_>, prev_branch_span: Option<Span>) {
264+
if let Some(prev_branch_span) = prev_branch_span {
265+
err.span_label(prev_branch_span, "expected because of this");
266+
}
267+
}
268+
269+
fn set_diverges(
270+
&self,
271+
initial_diverges: Diverges,
272+
guarded: &IfGuardedBranches<'tcx>,
273+
tail: &IfChainTail<'tcx>,
274+
) {
275+
let mut tail_diverges = tail.diverges();
276+
for branch in guarded.branches.iter().rev() {
277+
tail_diverges = branch.cond_diverges | (branch.body.diverges & tail_diverges);
278+
}
279+
self.diverges.set(initial_diverges | tail_diverges);
280+
}
281+
}

compiler/rustc_hir_typeck/src/expr.rs

Lines changed: 2 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ use tracing::{debug, instrument, trace};
4040
use {rustc_ast as ast, rustc_hir as hir};
4141

4242
use crate::Expectation::{self, ExpectCastableToType, ExpectHasType, NoExpectation};
43-
use crate::coercion::{CoerceMany, DynamicCoerceMany};
43+
use crate::coercion::CoerceMany;
4444
use crate::errors::{
4545
AddressOfTemporaryTaken, BaseExpressionDoubleDot, BaseExpressionDoubleDotAddExpr,
4646
BaseExpressionDoubleDotRemove, CantDereference, FieldMultiplySpecifiedInInitializer,
@@ -584,9 +584,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
584584
self.demand_eqtype(e.span, ascribed_ty, ty);
585585
ascribed_ty
586586
}
587-
ExprKind::If(cond, then_expr, opt_else_expr) => {
588-
self.check_expr_if(expr.hir_id, cond, then_expr, opt_else_expr, expr.span, expected)
589-
}
587+
ExprKind::If(..) => self.check_expr_if(expr.hir_id, expr.span, expected),
590588
ExprKind::DropTemps(e) => self.check_expr_with_expectation(e, expected),
591589
ExprKind::Array(args) => self.check_expr_array(args, expected, expr),
592590
ExprKind::ConstBlock(ref block) => self.check_expr_const_block(block, expected),
@@ -1343,72 +1341,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
13431341
}
13441342
}
13451343

1346-
// A generic function for checking the 'then' and 'else' clauses in an 'if'
1347-
// or 'if-else' expression.
1348-
fn check_expr_if(
1349-
&self,
1350-
expr_id: HirId,
1351-
cond_expr: &'tcx hir::Expr<'tcx>,
1352-
then_expr: &'tcx hir::Expr<'tcx>,
1353-
opt_else_expr: Option<&'tcx hir::Expr<'tcx>>,
1354-
sp: Span,
1355-
orig_expected: Expectation<'tcx>,
1356-
) -> Ty<'tcx> {
1357-
let cond_ty = self.check_expr_has_type_or_error(cond_expr, self.tcx.types.bool, |_| {});
1358-
1359-
self.warn_if_unreachable(
1360-
cond_expr.hir_id,
1361-
then_expr.span,
1362-
"block in `if` or `while` expression",
1363-
);
1364-
1365-
let cond_diverges = self.diverges.get();
1366-
self.diverges.set(Diverges::Maybe);
1367-
1368-
let expected = orig_expected.try_structurally_resolve_and_adjust_for_branches(self, sp);
1369-
let then_ty = self.check_expr_with_expectation(then_expr, expected);
1370-
let then_diverges = self.diverges.get();
1371-
self.diverges.set(Diverges::Maybe);
1372-
1373-
// We've already taken the expected type's preferences
1374-
// into account when typing the `then` branch. To figure
1375-
// out the initial shot at a LUB, we thus only consider
1376-
// `expected` if it represents a *hard* constraint
1377-
// (`only_has_type`); otherwise, we just go with a
1378-
// fresh type variable.
1379-
let coerce_to_ty = expected.coercion_target_type(self, sp);
1380-
let mut coerce: DynamicCoerceMany<'_> = CoerceMany::new(coerce_to_ty);
1381-
1382-
coerce.coerce(self, &self.misc(sp), then_expr, then_ty);
1383-
1384-
if let Some(else_expr) = opt_else_expr {
1385-
let else_ty = self.check_expr_with_expectation(else_expr, expected);
1386-
let else_diverges = self.diverges.get();
1387-
1388-
let tail_defines_return_position_impl_trait =
1389-
self.return_position_impl_trait_from_match_expectation(orig_expected);
1390-
let if_cause =
1391-
self.if_cause(expr_id, else_expr, tail_defines_return_position_impl_trait);
1392-
1393-
coerce.coerce(self, &if_cause, else_expr, else_ty);
1394-
1395-
// We won't diverge unless both branches do (or the condition does).
1396-
self.diverges.set(cond_diverges | then_diverges & else_diverges);
1397-
} else {
1398-
self.if_fallback_coercion(sp, cond_expr, then_expr, &mut coerce);
1399-
1400-
// If the condition is false we can't diverge.
1401-
self.diverges.set(cond_diverges);
1402-
}
1403-
1404-
let result_ty = coerce.complete(self);
1405-
if let Err(guar) = cond_ty.error_reported() {
1406-
Ty::new_error(self.tcx, guar)
1407-
} else {
1408-
result_ty
1409-
}
1410-
}
1411-
14121344
/// Type check assignment expression `expr` of form `lhs = rhs`.
14131345
/// The expected type is `()` and is passed to the function for the purposes of diagnostics.
14141346
fn check_expr_assign(

compiler/rustc_hir_typeck/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#![feature(never_type)]
1010
// tidy-alphabetical-end
1111

12+
mod _if;
1213
mod _match;
1314
mod autoderef;
1415
mod callee;

0 commit comments

Comments
 (0)