Skip to content

Commit c50157b

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 c50157b

File tree

14 files changed

+432
-175
lines changed

14 files changed

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

0 commit comments

Comments
 (0)