Skip to content

Commit 98bc078

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 98bc078

File tree

14 files changed

+431
-175
lines changed

14 files changed

+431
-175
lines changed
Lines changed: 319 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,319 @@
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 skip_coercion = matches!(tail, IfChainTail::Missing(_))
95+
&& chain.branches.len() > 1
96+
&& idx == chain.branches.len() - 1;
97+
if skip_coercion {
98+
recent_branch_types.record(branch_body.ty, branch_body.span);
99+
continue;
100+
}
101+
let next_else_expr =
102+
chain.branches.get(idx + 1).map(|next| next.if_expr).or(terminal_else_expr);
103+
let mut branch_cause = if let Some(next_else_expr) = next_else_expr {
104+
let prev_branch = chain.branches.get(idx - 1).unwrap_or(branch);
105+
self.if_cause(
106+
prev_branch.if_expr.hir_id,
107+
next_else_expr,
108+
tail_defines_return_position_impl_trait,
109+
)
110+
} else {
111+
self.misc(branch_body.span)
112+
};
113+
let diag_info = recent_branch_types.diagnostic_snapshot();
114+
let cause_span =
115+
if idx == 0 { Some(root_if_expr.span) } else { Some(branch_body.span) };
116+
117+
self.coerce_if_arm(
118+
&mut coerce,
119+
&mut branch_cause,
120+
branch_body.expr,
121+
branch_body.ty,
122+
cause_span,
123+
branch_body.span,
124+
diag_info,
125+
);
126+
127+
recent_branch_types.record(branch_body.ty, branch_body.span);
128+
}
129+
130+
let mut tail_diverges = match tail {
131+
IfChainTail::FinalElse(else_branch) => {
132+
let mut else_cause = self.if_cause(
133+
expr_id,
134+
else_branch.expr,
135+
tail_defines_return_position_impl_trait,
136+
);
137+
let diag_info = recent_branch_types.diagnostic_snapshot();
138+
self.coerce_if_arm(
139+
&mut coerce,
140+
&mut else_cause,
141+
else_branch.expr,
142+
else_branch.ty,
143+
None,
144+
else_branch.span,
145+
diag_info,
146+
);
147+
recent_branch_types.record(else_branch.ty, else_branch.span);
148+
149+
else_branch.diverges
150+
}
151+
IfChainTail::Missing(last_if_expr) => {
152+
let hir::ExprKind::If(tail_cond, tail_then, _) = last_if_expr.kind else {
153+
bug!("expected `if` expression, found {:#?}", last_if_expr);
154+
};
155+
self.if_fallback_coercion(last_if_expr.span, tail_cond, tail_then, &mut coerce);
156+
Diverges::Maybe
157+
}
158+
};
159+
160+
for branch in chain.branches.iter().rev() {
161+
tail_diverges = branch.cond_diverges | (branch.body.diverges & tail_diverges);
162+
}
163+
self.diverges.set(initial_diverges | tail_diverges);
164+
165+
let result_ty = coerce.complete(self);
166+
167+
let final_ty = if let Some(guar) = chain.cond_error {
168+
Ty::new_error(self.tcx, guar)
169+
} else {
170+
result_ty
171+
};
172+
173+
for branch in chain.branches.iter().skip(1) {
174+
self.overwrite_if_branch_type(branch.if_expr.hir_id, final_ty);
175+
}
176+
if let Err(guar) = final_ty.error_reported() {
177+
self.set_tainted_by_errors(guar);
178+
}
179+
180+
final_ty
181+
}
182+
183+
fn coerce_if_arm(
184+
&self,
185+
coerce: &mut DynamicCoerceMany<'tcx>,
186+
cause: &mut traits::ObligationCause<'tcx>,
187+
expr: &'tcx hir::Expr<'tcx>,
188+
ty: Ty<'tcx>,
189+
cause_span: Option<Span>,
190+
body_span: Span,
191+
prior_branches: SmallVec<[(Ty<'tcx>, Span); 4]>,
192+
) {
193+
if let Some(span) = cause_span {
194+
cause.span = span;
195+
}
196+
coerce.coerce_inner(
197+
self,
198+
cause,
199+
Some(expr),
200+
ty,
201+
move |err| self.explain_if_branch_mismatch(err, body_span, &prior_branches),
202+
false,
203+
);
204+
}
205+
206+
fn check_if_condition(
207+
&self,
208+
cond_expr: &'tcx hir::Expr<'tcx>,
209+
then_span: Span,
210+
) -> (Ty<'tcx>, Diverges) {
211+
let cond_ty = self.check_expr_has_type_or_error(cond_expr, self.tcx.types.bool, |_| {});
212+
self.warn_if_unreachable(
213+
cond_expr.hir_id,
214+
then_span,
215+
"block in `if` or `while` expression",
216+
);
217+
let cond_diverges = self.take_diverges();
218+
(cond_ty, cond_diverges)
219+
}
220+
221+
fn collect_if_chain(
222+
&self,
223+
mut current_if: &'tcx hir::Expr<'tcx>,
224+
expected: Expectation<'tcx>,
225+
) -> (IfChain<'tcx>, IfChainTail<'tcx>) {
226+
let mut chain: IfChain<'tcx> = IfChain::default();
227+
228+
loop {
229+
let Some(else_expr) = self.collect_if_branch(current_if, expected, &mut chain) else {
230+
return (chain, IfChainTail::Missing(current_if));
231+
};
232+
233+
if let hir::ExprKind::If(..) = else_expr.kind {
234+
current_if = else_expr;
235+
continue;
236+
}
237+
238+
return (chain, IfChainTail::FinalElse(self.collect_final_else(else_expr, expected)));
239+
}
240+
}
241+
242+
fn collect_if_branch(
243+
&self,
244+
if_expr: &'tcx hir::Expr<'tcx>,
245+
expected: Expectation<'tcx>,
246+
chain: &mut IfChain<'tcx>,
247+
) -> Option<&'tcx hir::Expr<'tcx>> {
248+
let hir::ExprKind::If(cond_expr, then_expr, opt_else_expr) = if_expr.kind else {
249+
bug!("expected `if` expression, found {:#?}", if_expr);
250+
};
251+
252+
let (cond_ty, cond_diverges) = self.check_if_condition(cond_expr, then_expr.span);
253+
if let Err(guar) = cond_ty.error_reported() {
254+
chain.cond_error.get_or_insert(guar);
255+
}
256+
let branch_body = self.check_branch_body(then_expr, expected);
257+
258+
chain.branches.push(IfBranch { if_expr, cond_diverges, body: branch_body });
259+
260+
opt_else_expr
261+
}
262+
263+
fn collect_final_else(
264+
&self,
265+
else_expr: &'tcx hir::Expr<'tcx>,
266+
expected: Expectation<'tcx>,
267+
) -> BranchBody<'tcx> {
268+
self.check_branch_body(else_expr, expected)
269+
}
270+
271+
fn reset_diverges_to_maybe(&self) {
272+
self.diverges.set(Diverges::Maybe);
273+
}
274+
275+
fn take_diverges(&self) -> Diverges {
276+
let diverges = self.diverges.get();
277+
self.reset_diverges_to_maybe();
278+
diverges
279+
}
280+
281+
fn ensure_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.entry(hir_id).or_insert(ty);
285+
}
286+
287+
fn overwrite_if_branch_type(&self, hir_id: HirId, ty: Ty<'tcx>) {
288+
let mut typeck = self.typeck_results.borrow_mut();
289+
let mut node_ty = typeck.node_types_mut();
290+
node_ty.insert(hir_id, ty);
291+
}
292+
293+
fn check_branch_body(
294+
&self,
295+
expr: &'tcx hir::Expr<'tcx>,
296+
expected: Expectation<'tcx>,
297+
) -> BranchBody<'tcx> {
298+
self.reset_diverges_to_maybe();
299+
let ty = self.check_expr_with_expectation(expr, expected);
300+
let diverges = self.take_diverges();
301+
let span = self.find_block_span_from_hir_id(expr.hir_id);
302+
BranchBody { expr, ty, diverges, span }
303+
}
304+
305+
fn explain_if_branch_mismatch(
306+
&self,
307+
err: &mut Diag<'_>,
308+
branch_span: Span,
309+
prior_branches: &[(Ty<'tcx>, Span)],
310+
) {
311+
let Some(&(.., prior_span)) =
312+
prior_branches.iter().rev().find(|&&(_, span)| span != branch_span)
313+
else {
314+
return;
315+
};
316+
317+
err.span_label(prior_span, "expected because of this");
318+
}
319+
}

0 commit comments

Comments
 (0)