-
Notifications
You must be signed in to change notification settings - Fork 12.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fully destructure slice and array constants into patterns #77390
Changes from 3 commits
002a1c2
14a0b10
e0e48ac
523b752
6553d38
d0bb77d
0915063
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -284,10 +284,9 @@ use super::{FieldPat, Pat, PatKind, PatRange}; | |
|
||
use rustc_arena::TypedArena; | ||
use rustc_attr::{SignedInt, UnsignedInt}; | ||
use rustc_errors::ErrorReported; | ||
use rustc_hir::def_id::DefId; | ||
use rustc_hir::{HirId, RangeEnd}; | ||
use rustc_middle::mir::interpret::{truncate, AllocId, ConstValue, Pointer, Scalar}; | ||
use rustc_middle::mir::interpret::{truncate, ConstValue}; | ||
use rustc_middle::mir::Field; | ||
use rustc_middle::ty::layout::IntegerExt; | ||
use rustc_middle::ty::{self, Const, Ty, TyCtxt}; | ||
|
@@ -296,108 +295,21 @@ use rustc_span::{Span, DUMMY_SP}; | |
use rustc_target::abi::{Integer, Size, VariantIdx}; | ||
|
||
use smallvec::{smallvec, SmallVec}; | ||
use std::borrow::Cow; | ||
use std::cmp::{self, max, min, Ordering}; | ||
use std::convert::TryInto; | ||
use std::fmt; | ||
use std::iter::{FromIterator, IntoIterator}; | ||
use std::ops::RangeInclusive; | ||
|
||
crate fn expand_pattern<'a, 'tcx>(cx: &MatchCheckCtxt<'a, 'tcx>, pat: Pat<'tcx>) -> Pat<'tcx> { | ||
LiteralExpander { tcx: cx.tcx, param_env: cx.param_env }.fold_pattern(&pat) | ||
crate fn expand_pattern<'tcx>(pat: Pat<'tcx>) -> Pat<'tcx> { | ||
LiteralExpander.fold_pattern(&pat) | ||
} | ||
|
||
struct LiteralExpander<'tcx> { | ||
tcx: TyCtxt<'tcx>, | ||
param_env: ty::ParamEnv<'tcx>, | ||
} | ||
struct LiteralExpander; | ||
|
||
impl<'tcx> LiteralExpander<'tcx> { | ||
/// Derefs `val` and potentially unsizes the value if `crty` is an array and `rty` a slice. | ||
/// | ||
/// `crty` and `rty` can differ because you can use array constants in the presence of slice | ||
/// patterns. So the pattern may end up being a slice, but the constant is an array. We convert | ||
/// the array to a slice in that case. | ||
fn fold_const_value_deref( | ||
&mut self, | ||
val: ConstValue<'tcx>, | ||
// the pattern's pointee type | ||
rty: Ty<'tcx>, | ||
// the constant's pointee type | ||
crty: Ty<'tcx>, | ||
) -> ConstValue<'tcx> { | ||
debug!("fold_const_value_deref {:?} {:?} {:?}", val, rty, crty); | ||
match (val, &crty.kind(), &rty.kind()) { | ||
// the easy case, deref a reference | ||
(ConstValue::Scalar(p), x, y) if x == y => { | ||
match p { | ||
Scalar::Ptr(p) => { | ||
let alloc = self.tcx.global_alloc(p.alloc_id).unwrap_memory(); | ||
ConstValue::ByRef { alloc, offset: p.offset } | ||
} | ||
Scalar::Raw { .. } => { | ||
let layout = self.tcx.layout_of(self.param_env.and(rty)).unwrap(); | ||
if layout.is_zst() { | ||
// Deref of a reference to a ZST is a nop. | ||
ConstValue::Scalar(Scalar::zst()) | ||
} else { | ||
// FIXME(oli-obk): this is reachable for `const FOO: &&&u32 = &&&42;` | ||
bug!("cannot deref {:#?}, {} -> {}", val, crty, rty); | ||
} | ||
} | ||
} | ||
} | ||
// unsize array to slice if pattern is array but match value or other patterns are slice | ||
(ConstValue::Scalar(Scalar::Ptr(p)), ty::Array(t, n), ty::Slice(u)) => { | ||
assert_eq!(t, u); | ||
ConstValue::Slice { | ||
data: self.tcx.global_alloc(p.alloc_id).unwrap_memory(), | ||
start: p.offset.bytes().try_into().unwrap(), | ||
end: n.eval_usize(self.tcx, ty::ParamEnv::empty()).try_into().unwrap(), | ||
} | ||
} | ||
// fat pointers stay the same | ||
(ConstValue::Slice { .. }, _, _) | ||
| (_, ty::Slice(_), ty::Slice(_)) | ||
| (_, ty::Str, ty::Str) => val, | ||
// FIXME(oli-obk): this is reachable for `const FOO: &&&u32 = &&&42;` being used | ||
_ => bug!("cannot deref {:#?}, {} -> {}", val, crty, rty), | ||
} | ||
} | ||
} | ||
|
||
impl<'tcx> PatternFolder<'tcx> for LiteralExpander<'tcx> { | ||
impl<'tcx> PatternFolder<'tcx> for LiteralExpander { | ||
fn fold_pattern(&mut self, pat: &Pat<'tcx>) -> Pat<'tcx> { | ||
debug!("fold_pattern {:?} {:?} {:?}", pat, pat.ty.kind(), pat.kind); | ||
match (pat.ty.kind(), &*pat.kind) { | ||
(&ty::Ref(_, rty, _), &PatKind::Constant { value: Const { val, ty: const_ty } }) | ||
if const_ty.is_ref() => | ||
{ | ||
let crty = | ||
if let ty::Ref(_, crty, _) = const_ty.kind() { crty } else { unreachable!() }; | ||
if let ty::ConstKind::Value(val) = val { | ||
Pat { | ||
ty: pat.ty, | ||
span: pat.span, | ||
kind: box PatKind::Deref { | ||
subpattern: Pat { | ||
ty: rty, | ||
span: pat.span, | ||
kind: box PatKind::Constant { | ||
value: Const::from_value( | ||
self.tcx, | ||
self.fold_const_value_deref(*val, rty, crty), | ||
rty, | ||
), | ||
}, | ||
}, | ||
}, | ||
} | ||
} else { | ||
bug!("cannot deref {:#?}, {} -> {}", val, crty, rty) | ||
} | ||
} | ||
|
||
(_, &PatKind::Binding { subpattern: Some(ref s), .. }) => s.fold_with(self), | ||
(_, &PatKind::AscribeUserType { subpattern: ref s, .. }) => s.fold_with(self), | ||
_ => pat.super_fold_with(self), | ||
|
@@ -965,8 +877,6 @@ impl<'tcx> Constructor<'tcx> { | |
.iter() | ||
.filter_map(|c: &Constructor<'_>| match c { | ||
Slice(slice) => Some(*slice), | ||
// FIXME(oli-obk): implement `deref` for `ConstValue` | ||
ConstantValue(..) => None, | ||
_ => bug!("bad slice pattern constructor {:?}", c), | ||
}) | ||
.map(Slice::value_kind); | ||
|
@@ -1186,12 +1096,6 @@ impl<'p, 'tcx> Fields<'p, 'tcx> { | |
Fields::Slice(std::slice::from_ref(pat)) | ||
} | ||
|
||
/// Construct a new `Fields` from the given patterns. You must be sure those patterns can't | ||
/// contain fields that need to be filtered out. When in doubt, prefer `replace_fields`. | ||
fn from_slice_unfiltered(pats: &'p [Pat<'tcx>]) -> Self { | ||
Fields::Slice(pats) | ||
} | ||
|
||
/// Convenience; internal use. | ||
fn wildcards_from_tys( | ||
cx: &MatchCheckCtxt<'p, 'tcx>, | ||
|
@@ -1581,9 +1485,7 @@ fn all_constructors<'a, 'tcx>( | |
) | ||
}; | ||
match *pcx.ty.kind() { | ||
ty::Bool => { | ||
[true, false].iter().map(|&b| ConstantValue(ty::Const::from_bool(cx.tcx, b))).collect() | ||
} | ||
ty::Bool => vec![make_range(0, 1)], | ||
ty::Array(ref sub_ty, len) if len.try_eval_usize(cx.tcx, cx.param_env).is_some() => { | ||
let len = len.eval_usize(cx.tcx, cx.param_env); | ||
if len != 0 && cx.is_uninhabited(sub_ty) { | ||
|
@@ -1696,7 +1598,7 @@ impl<'tcx> IntRange<'tcx> { | |
#[inline] | ||
fn is_integral(ty: Ty<'_>) -> bool { | ||
match ty.kind() { | ||
ty::Char | ty::Int(_) | ty::Uint(_) => true, | ||
ty::Char | ty::Int(_) | ty::Uint(_) | ty::Bool => true, | ||
_ => false, | ||
} | ||
} | ||
|
@@ -1718,6 +1620,7 @@ impl<'tcx> IntRange<'tcx> { | |
#[inline] | ||
fn integral_size_and_signed_bias(tcx: TyCtxt<'tcx>, ty: Ty<'_>) -> Option<(Size, u128)> { | ||
match *ty.kind() { | ||
ty::Bool => Some((Size::from_bytes(1), 0)), | ||
ty::Char => Some((Size::from_bytes(4), 0)), | ||
ty::Int(ity) => { | ||
let size = Integer::from_attr(&tcx, SignedInt(ity)).size(); | ||
|
@@ -2207,19 +2110,7 @@ fn pat_constructor<'tcx>( | |
if let Some(int_range) = IntRange::from_const(tcx, param_env, value, pat.span) { | ||
RalfJung marked this conversation as resolved.
Show resolved
Hide resolved
|
||
Some(IntRange(int_range)) | ||
} else { | ||
match (value.val, &value.ty.kind()) { | ||
(_, ty::Array(_, n)) => { | ||
let len = n.eval_usize(tcx, param_env); | ||
Some(Slice(Slice { array_len: Some(len), kind: FixedLen(len) })) | ||
} | ||
(ty::ConstKind::Value(ConstValue::Slice { start, end, .. }), ty::Slice(_)) => { | ||
let len = (end - start) as u64; | ||
Some(Slice(Slice { array_len: None, kind: FixedLen(len) })) | ||
} | ||
// FIXME(oli-obk): implement `deref` for `ConstValue` | ||
// (ty::ConstKind::Value(ConstValue::ByRef { .. }), ty::Slice(_)) => { ... } | ||
_ => Some(ConstantValue(value)), | ||
} | ||
Some(ConstantValue(value)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this related to exhaustiveness checking? Shouldn't opaque consts be treated as matching nothing for that as they might not be structural-match? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it is, very good catch. We still need a variant for string slices here, as string slices are not opaque, but There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I should note that I understand basically nothing of this code, if I caught anything that was an accident.^^ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also if the last 2 comments are bugfixes, is it possible to add a test? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was no bugfix, no behaviour was changed, all I did was to move the bail-out (that was guaranteed to happen later) to an earlier site so the rest of the code that I was able to remove became dead code. This PR generally is mostly just removing dead code that became obsolete after destructuring all structural-eq constants into (non-const) patterns. This means except for Edit: they were behaving opaquely before already, but there was lots of code making sure we didn't run into ICEs around them by bailing out as "not contributing to exhaustiveness". There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see. Maybe add some doc comments to |
||
} | ||
} | ||
PatKind::Range(PatRange { lo, hi, end }) => { | ||
|
@@ -2254,75 +2145,6 @@ fn pat_constructor<'tcx>( | |
} | ||
} | ||
|
||
// checks whether a constant is equal to a user-written slice pattern. Only supports byte slices, | ||
// meaning all other types will compare unequal and thus equal patterns often do not cause the | ||
// second pattern to lint about unreachable match arms. | ||
fn slice_pat_covered_by_const<'tcx>( | ||
tcx: TyCtxt<'tcx>, | ||
_span: Span, | ||
const_val: &'tcx ty::Const<'tcx>, | ||
prefix: &[Pat<'tcx>], | ||
slice: &Option<Pat<'tcx>>, | ||
suffix: &[Pat<'tcx>], | ||
param_env: ty::ParamEnv<'tcx>, | ||
) -> Result<bool, ErrorReported> { | ||
let const_val_val = if let ty::ConstKind::Value(val) = const_val.val { | ||
val | ||
} else { | ||
bug!( | ||
"slice_pat_covered_by_const: {:#?}, {:#?}, {:#?}, {:#?}", | ||
const_val, | ||
prefix, | ||
slice, | ||
suffix, | ||
) | ||
}; | ||
|
||
let data: &[u8] = match (const_val_val, &const_val.ty.kind()) { | ||
(ConstValue::ByRef { offset, alloc, .. }, ty::Array(t, n)) => { | ||
assert_eq!(*t, tcx.types.u8); | ||
let n = n.eval_usize(tcx, param_env); | ||
let ptr = Pointer::new(AllocId(0), offset); | ||
alloc.get_bytes(&tcx, ptr, Size::from_bytes(n)).unwrap() | ||
} | ||
(ConstValue::Slice { data, start, end }, ty::Slice(t)) => { | ||
assert_eq!(*t, tcx.types.u8); | ||
let ptr = Pointer::new(AllocId(0), Size::from_bytes(start)); | ||
data.get_bytes(&tcx, ptr, Size::from_bytes(end - start)).unwrap() | ||
} | ||
// FIXME(oli-obk): create a way to extract fat pointers from ByRef | ||
(_, ty::Slice(_)) => return Ok(false), | ||
_ => bug!( | ||
"slice_pat_covered_by_const: {:#?}, {:#?}, {:#?}, {:#?}", | ||
const_val, | ||
prefix, | ||
slice, | ||
suffix, | ||
), | ||
}; | ||
|
||
let pat_len = prefix.len() + suffix.len(); | ||
if data.len() < pat_len || (slice.is_none() && data.len() > pat_len) { | ||
return Ok(false); | ||
} | ||
|
||
for (ch, pat) in data[..prefix.len()] | ||
.iter() | ||
.zip(prefix) | ||
.chain(data[data.len() - suffix.len()..].iter().zip(suffix)) | ||
{ | ||
if let box PatKind::Constant { value } = pat.kind { | ||
let b = value.eval_bits(tcx, param_env, pat.ty); | ||
assert_eq!(b as u8 as u128, b); | ||
if b as u8 != *ch { | ||
return Ok(false); | ||
} | ||
} | ||
} | ||
|
||
Ok(true) | ||
} | ||
|
||
/// For exhaustive integer matching, some constructors are grouped within other constructors | ||
/// (namely integer typed values are grouped within ranges). However, when specialising these | ||
/// constructors, we want to be specialising for the underlying constructors (the integers), not | ||
|
@@ -2694,73 +2516,7 @@ fn specialize_one_pattern<'p, 'tcx>( | |
PatKind::Deref { ref subpattern } => Some(Fields::from_single_pattern(subpattern)), | ||
|
||
PatKind::Constant { value } if constructor.is_slice() => { | ||
// We extract an `Option` for the pointer because slices of zero | ||
// elements don't necessarily point to memory, they are usually | ||
// just integers. The only time they should be pointing to memory | ||
// is when they are subslices of nonzero slices. | ||
let (alloc, offset, n, ty) = match value.ty.kind() { | ||
ty::Array(t, n) => { | ||
let n = n.eval_usize(cx.tcx, cx.param_env); | ||
// Shortcut for `n == 0` where no matter what `alloc` and `offset` we produce, | ||
// the result would be exactly what we early return here. | ||
if n == 0 { | ||
if ctor_wild_subpatterns.len() as u64 != n { | ||
return None; | ||
} | ||
return Some(Fields::empty()); | ||
} | ||
match value.val { | ||
ty::ConstKind::Value(ConstValue::ByRef { offset, alloc, .. }) => { | ||
(Cow::Borrowed(alloc), offset, n, t) | ||
} | ||
_ => span_bug!(pat.span, "array pattern is {:?}", value,), | ||
} | ||
} | ||
ty::Slice(t) => { | ||
match value.val { | ||
ty::ConstKind::Value(ConstValue::Slice { data, start, end }) => { | ||
let offset = Size::from_bytes(start); | ||
let n = (end - start) as u64; | ||
(Cow::Borrowed(data), offset, n, t) | ||
} | ||
ty::ConstKind::Value(ConstValue::ByRef { .. }) => { | ||
// FIXME(oli-obk): implement `deref` for `ConstValue` | ||
return None; | ||
} | ||
_ => span_bug!( | ||
pat.span, | ||
"slice pattern constant must be scalar pair but is {:?}", | ||
value, | ||
), | ||
} | ||
} | ||
_ => span_bug!( | ||
pat.span, | ||
"unexpected const-val {:?} with ctor {:?}", | ||
value, | ||
constructor, | ||
), | ||
}; | ||
if ctor_wild_subpatterns.len() as u64 != n { | ||
return None; | ||
} | ||
|
||
// Convert a constant slice/array pattern to a list of patterns. | ||
let layout = cx.tcx.layout_of(cx.param_env.and(ty)).ok()?; | ||
let ptr = Pointer::new(AllocId(0), offset); | ||
let pats = cx.pattern_arena.alloc_from_iter((0..n).filter_map(|i| { | ||
let ptr = ptr.offset(layout.size * i, &cx.tcx).ok()?; | ||
let scalar = alloc.read_scalar(&cx.tcx, ptr, layout.size).ok()?; | ||
let scalar = scalar.check_init().ok()?; | ||
let value = ty::Const::from_scalar(cx.tcx, scalar, ty); | ||
let pattern = Pat { ty, span: pat.span, kind: box PatKind::Constant { value } }; | ||
Some(pattern) | ||
})); | ||
// Ensure none of the dereferences failed. | ||
if pats.len() as u64 != n { | ||
return None; | ||
} | ||
Some(Fields::from_slice_unfiltered(pats)) | ||
span_bug!(pat.span, "unexpected const-val {:?} with ctor {:?}", value, constructor) | ||
} | ||
|
||
PatKind::Constant { .. } | PatKind::Range { .. } => { | ||
|
@@ -2802,21 +2558,7 @@ fn specialize_one_pattern<'p, 'tcx>( | |
let suffix = suffix.iter().enumerate().map(|(i, p)| (arity - suffix.len() + i, p)); | ||
Some(ctor_wild_subpatterns.replace_fields_indexed(prefix.chain(suffix))) | ||
} | ||
ConstantValue(cv) => { | ||
match slice_pat_covered_by_const( | ||
cx.tcx, | ||
pat.span, | ||
cv, | ||
prefix, | ||
slice, | ||
suffix, | ||
cx.param_env, | ||
) { | ||
Ok(true) => Some(Fields::empty()), | ||
Ok(false) => None, | ||
Err(ErrorReported) => None, | ||
} | ||
} | ||
ConstantValue(_) => None, | ||
_ => span_bug!(pat.span, "unexpected ctor {:?} for slice pat", constructor), | ||
}, | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doesn't this affect diagnostics? I'd expect this would display "pattern
0
not covered" instead of "patternfalse
not covered", but that would clearly be caught by the tests. I don't understand what piece of code makes it so it doesn't.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had also wondered this, but assumed this would be caught by tests, so it must have been okay. But you're right that it would be better to understand this (and add a comment).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is because while decisions are made on
Constructor
s, they do not directly flow into diagnostics. Instead diagnostics are reported onPat
s in https://github.com/rust-lang/rust/blob/6553d38bcb0c9193808ae93db8bd95bcf4c4824a/compiler/rustc_mir_build/src/thir/pattern/check_match.rs