Rework error handling when lowering range endpoints

This commit is contained in:
Nadrieril 2023-10-10 20:28:16 +02:00
parent c5e17021cc
commit 1e1174b034

View file

@ -27,7 +27,7 @@ use rustc_middle::ty::CanonicalUserTypeAnnotation;
use rustc_middle::ty::TypeVisitableExt; use rustc_middle::ty::TypeVisitableExt;
use rustc_middle::ty::{self, AdtDef, Region, Ty, TyCtxt, UserType}; use rustc_middle::ty::{self, AdtDef, Region, Ty, TyCtxt, UserType};
use rustc_middle::ty::{GenericArg, GenericArgsRef}; use rustc_middle::ty::{GenericArg, GenericArgsRef};
use rustc_span::{Span, Symbol}; use rustc_span::{ErrorGuaranteed, Span, Symbol};
use rustc_target::abi::FieldIdx; use rustc_target::abi::FieldIdx;
use std::cmp::Ordering; use std::cmp::Ordering;
@ -85,127 +85,126 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
) )
} }
fn lower_range_expr( fn lower_pattern_range_endpoint(
&mut self, &mut self,
expr: &'tcx hir::Expr<'tcx>, expr: Option<&'tcx hir::Expr<'tcx>>,
) -> (PatKind<'tcx>, Option<Ascription<'tcx>>) { ) -> Result<(Option<mir::Const<'tcx>>, Option<Ascription<'tcx>>), ErrorGuaranteed> {
match self.lower_lit(expr) { match expr {
PatKind::AscribeUserType { ascription, subpattern: box Pat { kind, .. } } => { None => Ok((None, None)),
(kind, Some(ascription)) Some(expr) => {
let (kind, ascr) = match self.lower_lit(expr) {
PatKind::AscribeUserType { ascription, subpattern: box Pat { kind, .. } } => {
(kind, Some(ascription))
}
kind => (kind, None),
};
let value = if let PatKind::Constant { value } = kind {
value
} else {
let msg = format!(
"found bad range pattern endpoint `{expr:?}` outside of error recovery"
);
return Err(self.tcx.sess.delay_span_bug(expr.span, msg));
};
Ok((Some(value), ascr))
} }
kind => (kind, None),
} }
} }
fn lower_pattern_range( fn lower_pattern_range(
&mut self, &mut self,
ty: Ty<'tcx>, lo_expr: Option<&'tcx hir::Expr<'tcx>>,
lo: mir::Const<'tcx>, hi_expr: Option<&'tcx hir::Expr<'tcx>>,
hi: mir::Const<'tcx>,
end: RangeEnd, end: RangeEnd,
ty: Ty<'tcx>,
span: Span, span: Span,
lo_expr: Option<&hir::Expr<'tcx>>, ) -> Result<PatKind<'tcx>, ErrorGuaranteed> {
hi_expr: Option<&hir::Expr<'tcx>>, if lo_expr.is_none() && hi_expr.is_none() {
) -> PatKind<'tcx> { let msg = format!("found twice-open range pattern (`..`) outside of error recovery");
return Err(self.tcx.sess.delay_span_bug(span, msg));
}
let (lo, lo_ascr) = self.lower_pattern_range_endpoint(lo_expr)?;
let (hi, hi_ascr) = self.lower_pattern_range_endpoint(hi_expr)?;
let lo = lo.unwrap_or_else(|| {
// Unwrap is ok because the type is known to be numeric.
let lo = ty.numeric_min_val(self.tcx).unwrap();
mir::Const::from_ty_const(lo, self.tcx)
});
let hi = hi.unwrap_or_else(|| {
// Unwrap is ok because the type is known to be numeric.
let hi = ty.numeric_max_val(self.tcx).unwrap();
mir::Const::from_ty_const(hi, self.tcx)
});
assert_eq!(lo.ty(), ty); assert_eq!(lo.ty(), ty);
assert_eq!(hi.ty(), ty); assert_eq!(hi.ty(), ty);
let cmp = compare_const_vals(self.tcx, lo, hi, self.param_env); let cmp = compare_const_vals(self.tcx, lo, hi, self.param_env);
let max = || { let mut kind = match (end, cmp) {
self.tcx
.layout_of(self.param_env.with_reveal_all_normalized(self.tcx).and(ty))
.ok()
.unwrap()
.size
.unsigned_int_max()
};
match (end, cmp) {
// `x..y` where `x < y`. // `x..y` where `x < y`.
// Non-empty because the range includes at least `x`. // Non-empty because the range includes at least `x`.
(RangeEnd::Excluded, Some(Ordering::Less)) => { (RangeEnd::Excluded, Some(Ordering::Less)) => {
PatKind::Range(Box::new(PatRange { lo, hi, end })) PatKind::Range(Box::new(PatRange { lo, hi, end }))
} }
// `x..y` where `x >= y`. The range is empty => error.
(RangeEnd::Excluded, _) => {
let mut lower_overflow = false;
let mut higher_overflow = false;
if let Some(hir::Expr { kind: hir::ExprKind::Lit(lit), .. }) = lo_expr
&& let rustc_ast::ast::LitKind::Int(val, _) = lit.node
{
if lo.eval_bits(self.tcx, self.param_env) != val {
lower_overflow = true;
self.tcx.sess.emit_err(LiteralOutOfRange { span: lit.span, ty, max: max() });
}
}
if let Some(hir::Expr { kind: hir::ExprKind::Lit(lit), .. }) = hi_expr
&& let rustc_ast::ast::LitKind::Int(val, _) = lit.node
{
if hi.eval_bits(self.tcx, self.param_env) != val {
higher_overflow = true;
self.tcx.sess.emit_err(LiteralOutOfRange { span: lit.span, ty, max: max() });
}
}
if !lower_overflow && !higher_overflow {
self.tcx.sess.emit_err(LowerRangeBoundMustBeLessThanUpper { span });
}
PatKind::Wild
}
// `x..=y` where `x == y`. // `x..=y` where `x == y`.
(RangeEnd::Included, Some(Ordering::Equal)) => PatKind::Constant { value: lo }, (RangeEnd::Included, Some(Ordering::Equal)) => PatKind::Constant { value: lo },
// `x..=y` where `x < y`. // `x..=y` where `x < y`.
(RangeEnd::Included, Some(Ordering::Less)) => { (RangeEnd::Included, Some(Ordering::Less)) => {
PatKind::Range(Box::new(PatRange { lo, hi, end })) PatKind::Range(Box::new(PatRange { lo, hi, end }))
} }
// `x..=y` where `x > y` hence the range is empty => error. // `x..y` where `x >= y`, or `x..=y` where `x > y`. The range is empty => error.
(RangeEnd::Included, _) => { _ => {
let mut lower_overflow = false; let max = || {
let mut higher_overflow = false; self.tcx
.layout_of(self.param_env.with_reveal_all_normalized(self.tcx).and(ty))
.ok()
.unwrap()
.size
.unsigned_int_max()
};
// Emit a different message if there was overflow.
if let Some(hir::Expr { kind: hir::ExprKind::Lit(lit), .. }) = lo_expr if let Some(hir::Expr { kind: hir::ExprKind::Lit(lit), .. }) = lo_expr
&& let rustc_ast::ast::LitKind::Int(val, _) = lit.node && let rustc_ast::ast::LitKind::Int(val, _) = lit.node
{ {
if lo.eval_bits(self.tcx, self.param_env) != val { if lo.eval_bits(self.tcx, self.param_env) != val {
lower_overflow = true; return Err(self.tcx.sess.emit_err(LiteralOutOfRange { span: lit.span, ty, max: max() }));
self.tcx.sess.emit_err(LiteralOutOfRange { span: lit.span, ty, max: max() });
} }
} }
if let Some(hir::Expr { kind: hir::ExprKind::Lit(lit), .. }) = hi_expr if let Some(hir::Expr { kind: hir::ExprKind::Lit(lit), .. }) = hi_expr
&& let rustc_ast::ast::LitKind::Int(val, _) = lit.node && let rustc_ast::ast::LitKind::Int(val, _) = lit.node
{ {
if hi.eval_bits(self.tcx, self.param_env) != val { if hi.eval_bits(self.tcx, self.param_env) != val {
higher_overflow = true; return Err(self.tcx.sess.emit_err(LiteralOutOfRange { span: lit.span, ty, max: max() }));
self.tcx.sess.emit_err(LiteralOutOfRange { span: lit.span, ty, max: max() });
} }
} }
if !lower_overflow && !higher_overflow { let e = match end {
self.tcx.sess.emit_err(LowerRangeBoundMustBeLessThanOrEqualToUpper { RangeEnd::Included => {
span, self.tcx.sess.emit_err(LowerRangeBoundMustBeLessThanOrEqualToUpper {
teach: self.tcx.sess.teach(&error_code!(E0030)).then_some(()), span,
}); teach: self.tcx.sess.teach(&error_code!(E0030)).then_some(()),
} })
PatKind::Wild }
RangeEnd::Excluded => {
self.tcx.sess.emit_err(LowerRangeBoundMustBeLessThanUpper { span })
}
};
return Err(e);
} }
} };
}
fn normalize_range_pattern_ends( // If we are handling a range with associated constants (e.g.
&self, // `Foo::<'a>::A..=Foo::B`), we need to put the ascriptions for the associated
ty: Ty<'tcx>, // constants somewhere. Have them on the range pattern.
lo: Option<&PatKind<'tcx>>, for ascr in [lo_ascr, hi_ascr] {
hi: Option<&PatKind<'tcx>>, if let Some(ascription) = ascr {
) -> Option<(mir::Const<'tcx>, mir::Const<'tcx>)> { kind = PatKind::AscribeUserType {
match (lo, hi) { ascription,
(Some(PatKind::Constant { value: lo }), Some(PatKind::Constant { value: hi })) => { subpattern: Box::new(Pat { span, ty, kind }),
Some((*lo, *hi)) };
} }
(Some(PatKind::Constant { value: lo }), None) => {
let hi = ty.numeric_max_val(self.tcx)?;
Some((*lo, mir::Const::from_ty_const(hi, self.tcx)))
}
(None, Some(PatKind::Constant { value: hi })) => {
let lo = ty.numeric_min_val(self.tcx)?;
Some((mir::Const::from_ty_const(lo, self.tcx), *hi))
}
_ => None,
} }
Ok(kind)
} }
#[instrument(skip(self), level = "debug")] #[instrument(skip(self), level = "debug")]
@ -220,37 +219,11 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
hir::PatKind::Range(ref lo_expr, ref hi_expr, end) => { hir::PatKind::Range(ref lo_expr, ref hi_expr, end) => {
let (lo_expr, hi_expr) = (lo_expr.as_deref(), hi_expr.as_deref()); let (lo_expr, hi_expr) = (lo_expr.as_deref(), hi_expr.as_deref());
let lo_span = lo_expr.map_or(pat.span, |e| e.span); let span = lo_expr.map_or(span, |e| e.span);
let lo = lo_expr.map(|e| self.lower_range_expr(e)); // FIXME?: returning `_` can cause inaccurate "unreachable" warnings. This can be
let hi = hi_expr.map(|e| self.lower_range_expr(e)); // fixed by returning `PatKind::Const(ConstKind::Error(...))` if #115937 gets
// merged.
let (lp, hp) = (lo.as_ref().map(|(x, _)| x), hi.as_ref().map(|(x, _)| x)); self.lower_pattern_range(lo_expr, hi_expr, end, ty, span).unwrap_or(PatKind::Wild)
let mut kind = match self.normalize_range_pattern_ends(ty, lp, hp) {
Some((lc, hc)) => {
self.lower_pattern_range(ty, lc, hc, end, lo_span, lo_expr, hi_expr)
}
None => {
let msg = format!(
"found bad range pattern `{:?}` outside of error recovery",
(&lo, &hi),
);
self.tcx.sess.delay_span_bug(pat.span, msg);
PatKind::Wild
}
};
// If we are handling a range with associated constants (e.g.
// `Foo::<'a>::A..=Foo::B`), we need to put the ascriptions for the associated
// constants somewhere. Have them on the range pattern.
for end in &[lo, hi] {
if let Some((_, Some(ascription))) = end {
let subpattern = Box::new(Pat { span: pat.span, ty, kind });
kind =
PatKind::AscribeUserType { ascription: ascription.clone(), subpattern };
}
}
kind
} }
hir::PatKind::Path(ref qpath) => { hir::PatKind::Path(ref qpath) => {