Skip to content
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

Fix exhaustiveness in case a byte string literal is used at slice type #79072

Merged
merged 1 commit into from
Nov 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions compiler/rustc_middle/src/ty/context.rs
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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),
oli-obk marked this conversation as resolved.
Show resolved Hide resolved
},
};
self.behind_reference.set(old);
Expand Down
8 changes: 6 additions & 2 deletions compiler/rustc_typeck/src/check/pat.rs
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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,
}
}
oli-obk marked this conversation as resolved.
Show resolved Hide resolved

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
Original file line number Diff line number Diff line change
@@ -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`.
Original file line number Diff line number Diff line change
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