Skip to content

Commit

Permalink
Fix exhaustiveness in case a byte string literal is used at slice type
Browse files Browse the repository at this point in the history
  • Loading branch information
oli-obk committed Nov 17, 2020
1 parent 30e49a9 commit a1cdf72
Show file tree
Hide file tree
Showing 7 changed files with 113 additions and 12 deletions.
9 changes: 9 additions & 0 deletions compiler/rustc_middle/src/ty/context.rs
Expand Up @@ -418,6 +418,12 @@ pub struct TypeckResults<'tcx> {
/// Stores the type, expression, span and optional scope span of all types
/// that are live across the yield of this generator (if a generator).
pub generator_interior_types: Vec<GeneratorInteriorTypeCause<'tcx>>,

/// We sometimes treat byte string literals (which are of type `&[u8; N]`)
/// as `&[u8]`, depending on the pattern in which they are used.
/// This hashset records all instances where we behave
/// like this to allow `const_to_pat` to reliably handle this situation.
pub treat_byte_string_as_slice: ItemLocalSet,
}

impl<'tcx> TypeckResults<'tcx> {
Expand All @@ -443,6 +449,7 @@ impl<'tcx> TypeckResults<'tcx> {
concrete_opaque_types: Default::default(),
closure_captures: Default::default(),
generator_interior_types: Default::default(),
treat_byte_string_as_slice: Default::default(),
}
}

Expand Down Expand Up @@ -677,6 +684,7 @@ impl<'a, 'tcx> HashStable<StableHashingContext<'a>> for TypeckResults<'tcx> {
ref concrete_opaque_types,
ref closure_captures,
ref generator_interior_types,
ref treat_byte_string_as_slice,
} = *self;

hcx.with_node_id_hashing_mode(NodeIdHashingMode::HashDefPath, |hcx| {
Expand Down Expand Up @@ -710,6 +718,7 @@ impl<'a, 'tcx> HashStable<StableHashingContext<'a>> for TypeckResults<'tcx> {
concrete_opaque_types.hash_stable(hcx, hasher);
closure_captures.hash_stable(hcx, hasher);
generator_interior_types.hash_stable(hcx, hasher);
treat_byte_string_as_slice.hash_stable(hcx, hasher);
})
}
}
Expand Down
44 changes: 36 additions & 8 deletions compiler/rustc_mir_build/src/thir/pattern/const_to_pat.rs
Expand Up @@ -18,22 +18,20 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
/// Converts an evaluated constant to a pattern (if possible).
/// This means aggregate values (like structs and enums) are converted
/// to a pattern that matches the value (as if you'd compared via structural equality).
#[instrument(skip(self))]
pub(super) fn const_to_pat(
&self,
cv: &'tcx ty::Const<'tcx>,
id: hir::HirId,
span: Span,
mir_structural_match_violation: bool,
) -> Pat<'tcx> {
debug!("const_to_pat: cv={:#?} id={:?}", cv, id);
debug!("const_to_pat: cv.ty={:?} span={:?}", cv.ty, span);

let pat = self.tcx.infer_ctxt().enter(|infcx| {
let mut convert = ConstToPat::new(self, id, span, infcx);
convert.to_pat(cv, mir_structural_match_violation)
});

debug!("const_to_pat: pat={:?}", pat);
debug!(?pat);
pat
}
}
Expand Down Expand Up @@ -61,6 +59,8 @@ struct ConstToPat<'a, 'tcx> {
infcx: InferCtxt<'a, 'tcx>,

include_lint_checks: bool,

treat_byte_string_as_slice: bool,
}

mod fallback_to_const_ref {
Expand Down Expand Up @@ -88,6 +88,7 @@ impl<'a, 'tcx> ConstToPat<'a, 'tcx> {
span: Span,
infcx: InferCtxt<'a, 'tcx>,
) -> Self {
trace!(?pat_ctxt.typeck_results.hir_owner);
ConstToPat {
id,
span,
Expand All @@ -97,6 +98,10 @@ impl<'a, 'tcx> ConstToPat<'a, 'tcx> {
saw_const_match_error: Cell::new(false),
saw_const_match_lint: Cell::new(false),
behind_reference: Cell::new(false),
treat_byte_string_as_slice: pat_ctxt
.typeck_results
.treat_byte_string_as_slice
.contains(&id.local_id),
}
}

Expand Down Expand Up @@ -153,6 +158,7 @@ impl<'a, 'tcx> ConstToPat<'a, 'tcx> {
cv: &'tcx ty::Const<'tcx>,
mir_structural_match_violation: bool,
) -> Pat<'tcx> {
trace!(self.treat_byte_string_as_slice);
// This method is just a wrapper handling a validity check; the heavy lifting is
// performed by the recursive `recur` method, which is not meant to be
// invoked except by this method.
Expand Down Expand Up @@ -384,7 +390,7 @@ impl<'a, 'tcx> ConstToPat<'a, 'tcx> {
}
PatKind::Wild
}
// `&str` and `&[u8]` are represented as `ConstValue::Slice`, let's keep using this
// `&str` is represented as `ConstValue::Slice`, let's keep using this
// optimization for now.
ty::Str => PatKind::Constant { value: cv },
// `b"foo"` produces a `&[u8; 3]`, but you can't use constants of array type when
Expand All @@ -393,11 +399,33 @@ impl<'a, 'tcx> ConstToPat<'a, 'tcx> {
// as slices. This means we turn `&[T; N]` constants into slice patterns, which
// has no negative effects on pattern matching, even if we're actually matching on
// arrays.
ty::Array(..) |
ty::Array(..) if !self.treat_byte_string_as_slice => {
let old = self.behind_reference.replace(true);
let array = tcx.deref_const(self.param_env.and(cv));
let val = PatKind::Deref {
subpattern: Pat {
kind: Box::new(PatKind::Array {
prefix: tcx
.destructure_const(param_env.and(array))
.fields
.iter()
.map(|val| self.recur(val, false))
.collect::<Result<_, _>>()?,
slice: None,
suffix: vec![],
}),
span,
ty: pointee_ty,
},
};
self.behind_reference.set(old);
val
}
ty::Array(elem_ty, _) |
// Cannot merge this with the catch all branch below, because the `const_deref`
// changes the type from slice to array, we need to keep the original type in the
// pattern.
ty::Slice(..) => {
ty::Slice(elem_ty) => {
let old = self.behind_reference.replace(true);
let array = tcx.deref_const(self.param_env.and(cv));
let val = PatKind::Deref {
Expand All @@ -413,7 +441,7 @@ impl<'a, 'tcx> ConstToPat<'a, 'tcx> {
suffix: vec![],
}),
span,
ty: pointee_ty,
ty: tcx.mk_slice(elem_ty),
},
};
self.behind_reference.set(old);
Expand Down
8 changes: 6 additions & 2 deletions compiler/rustc_typeck/src/check/pat.rs
Expand Up @@ -149,15 +149,14 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
///
/// Outside of this module, `check_pat_top` should always be used.
/// Conversely, inside this module, `check_pat_top` should never be used.
#[instrument(skip(self, ti))]
fn check_pat(
&self,
pat: &'tcx Pat<'tcx>,
expected: Ty<'tcx>,
def_bm: BindingMode,
ti: TopInfo<'tcx>,
) {
debug!("check_pat(pat={:?},expected={:?},def_bm={:?})", pat, expected, def_bm);

let path_res = match &pat.kind {
PatKind::Path(qpath) => Some(self.resolve_ty_and_res_ufcs(qpath, pat.hir_id, pat.span)),
_ => None,
Expand Down Expand Up @@ -398,6 +397,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
if let ty::Ref(_, inner_ty, _) = expected.kind() {
if matches!(inner_ty.kind(), ty::Slice(_)) {
let tcx = self.tcx;
trace!(?lt.hir_id.local_id, "polymorphic byte string lit");
self.typeck_results
.borrow_mut()
.treat_byte_string_as_slice
.insert(lt.hir_id.local_id);
pat_ty = tcx.mk_imm_ref(tcx.lifetimes.re_static, tcx.mk_slice(tcx.types.u8));
}
}
Expand Down
3 changes: 3 additions & 0 deletions compiler/rustc_typeck/src/check/writeback.rs
Expand Up @@ -70,6 +70,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
debug!("used_trait_imports({:?}) = {:?}", item_def_id, used_trait_imports);
wbcx.typeck_results.used_trait_imports = used_trait_imports;

wbcx.typeck_results.treat_byte_string_as_slice =
mem::take(&mut self.typeck_results.borrow_mut().treat_byte_string_as_slice);

wbcx.typeck_results.closure_captures =
mem::take(&mut self.typeck_results.borrow_mut().closure_captures);

Expand Down
36 changes: 36 additions & 0 deletions src/test/ui/match/type_polymorphic_byte_str_literals.rs
@@ -0,0 +1,36 @@
#[deny(unreachable_patterns)]

fn parse_data1(data: &[u8]) -> u32 {
match data {
b"" => 1,
_ => 2,
}
}

fn parse_data2(data: &[u8]) -> u32 {
match data { //~ ERROR non-exhaustive patterns: `&[_, ..]` not covered
b"" => 1,
}
}

fn parse_data3(data: &[u8; 0]) -> u8 {
match data {
b"" => 1,
}
}

fn parse_data4(data: &[u8]) -> u8 {
match data { //~ ERROR non-exhaustive patterns
b"aaa" => 0,
[_, _, _] => 1,
}
}

fn parse_data5(data: &[u8; 3]) -> u8 {
match data {
b"aaa" => 0,
[_, _, _] => 1,
}
}

fn main() {}
21 changes: 21 additions & 0 deletions src/test/ui/match/type_polymorphic_byte_str_literals.stderr
@@ -0,0 +1,21 @@
error[E0004]: non-exhaustive patterns: `&[_, ..]` not covered
--> $DIR/type_polymorphic_byte_str_literals.rs:11:11
|
LL | match data {
| ^^^^ pattern `&[_, ..]` not covered
|
= help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms
= note: the matched value is of type `&[u8]`

error[E0004]: non-exhaustive patterns: `&[]`, `&[_]`, `&[_, _]` and 1 more not covered
--> $DIR/type_polymorphic_byte_str_literals.rs:23:11
|
LL | match data {
| ^^^^ patterns `&[]`, `&[_]`, `&[_, _]` and 1 more not covered
|
= help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms
= note: the matched value is of type `&[u8]`

error: aborting due to 2 previous errors

For more information about this error, try `rustc --explain E0004`.
Expand Up @@ -7,11 +7,11 @@ LL | match buf {
= help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms
= note: the matched value is of type `&[u8; 4]`

error[E0004]: non-exhaustive patterns: `&[0_u8..=64_u8, _, _, _]` and `&[66_u8..=u8::MAX, _, _, _]` not covered
error[E0004]: non-exhaustive patterns: `&[]`, `&[_]`, `&[_, _]` and 2 more not covered
--> $DIR/match-byte-array-patterns-2.rs:10:11
|
LL | match buf {
| ^^^ patterns `&[0_u8..=64_u8, _, _, _]` and `&[66_u8..=u8::MAX, _, _, _]` not covered
| ^^^ patterns `&[]`, `&[_]`, `&[_, _]` and 2 more not covered
|
= help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms
= note: the matched value is of type `&[u8]`
Expand Down

0 comments on commit a1cdf72

Please sign in to comment.