Skip to content

Commit b522404

Browse files
committed
fix: check_expr_if to point to a more accurate location of the compilation error in some cases
1 parent 9642c0e commit b522404

File tree

14 files changed

+435
-170
lines changed

14 files changed

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

0 commit comments

Comments
 (0)