diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs index a5237ffaf78..36cbd36a770 100644 --- a/compiler/rustc_middle/src/ty/context.rs +++ b/compiler/rustc_middle/src/ty/context.rs @@ -422,6 +422,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>, + + /// 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> { @@ -448,6 +454,7 @@ pub fn new(hir_owner: LocalDefId) -> TypeckResults<'tcx> { closure_captures: Default::default(), closure_min_captures: Default::default(), generator_interior_types: Default::default(), + treat_byte_string_as_slice: Default::default(), } } @@ -683,6 +690,7 @@ fn hash_stable(&self, hcx: &mut StableHashingContext<'a>, hasher: &mut StableHas ref closure_captures, ref closure_min_captures, ref generator_interior_types, + ref treat_byte_string_as_slice, } = *self; hcx.with_node_id_hashing_mode(NodeIdHashingMode::HashDefPath, |hcx| { @@ -717,6 +725,7 @@ fn hash_stable(&self, hcx: &mut StableHashingContext<'a>, hasher: &mut StableHas closure_captures.hash_stable(hcx, hasher); closure_min_captures.hash_stable(hcx, hasher); generator_interior_types.hash_stable(hcx, hasher); + treat_byte_string_as_slice.hash_stable(hcx, hasher); }) } } diff --git a/compiler/rustc_mir_build/src/thir/pattern/const_to_pat.rs b/compiler/rustc_mir_build/src/thir/pattern/const_to_pat.rs index 6370f8c375b..32fc0f008e9 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/const_to_pat.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/const_to_pat.rs @@ -18,6 +18,7 @@ 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>, @@ -25,15 +26,12 @@ pub(super) fn const_to_pat( 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 } } @@ -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 { @@ -88,6 +88,7 @@ fn new( span: Span, infcx: InferCtxt<'a, 'tcx>, ) -> Self { + trace!(?pat_ctxt.typeck_results.hir_owner); ConstToPat { id, span, @@ -97,6 +98,10 @@ fn new( 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), } } @@ -153,6 +158,7 @@ fn to_pat( 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. @@ -384,7 +390,7 @@ fn recur( } 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 @@ -393,11 +399,33 @@ fn recur( // 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::>()?, + 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 { @@ -413,7 +441,7 @@ fn recur( suffix: vec![], }), span, - ty: pointee_ty, + ty: tcx.mk_slice(elem_ty), }, }; self.behind_reference.set(old); diff --git a/compiler/rustc_typeck/src/check/pat.rs b/compiler/rustc_typeck/src/check/pat.rs index dd8f4152c7c..a729912126e 100644 --- a/compiler/rustc_typeck/src/check/pat.rs +++ b/compiler/rustc_typeck/src/check/pat.rs @@ -149,6 +149,7 @@ pub fn check_pat_top( /// /// 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>, @@ -156,8 +157,6 @@ fn check_pat( 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, @@ -398,6 +397,11 @@ fn check_pat_lit( 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)); } } diff --git a/compiler/rustc_typeck/src/check/writeback.rs b/compiler/rustc_typeck/src/check/writeback.rs index e54e0086483..335f2cc2716 100644 --- a/compiler/rustc_typeck/src/check/writeback.rs +++ b/compiler/rustc_typeck/src/check/writeback.rs @@ -70,6 +70,9 @@ pub fn resolve_type_vars_in_body( 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); diff --git a/src/test/ui/match/type_polymorphic_byte_str_literals.rs b/src/test/ui/match/type_polymorphic_byte_str_literals.rs new file mode 100644 index 00000000000..cb44c1da76b --- /dev/null +++ b/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() {} diff --git a/src/test/ui/match/type_polymorphic_byte_str_literals.stderr b/src/test/ui/match/type_polymorphic_byte_str_literals.stderr new file mode 100644 index 00000000000..6ce53a4f21e --- /dev/null +++ b/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`. diff --git a/src/test/ui/pattern/usefulness/match-byte-array-patterns-2.stderr b/src/test/ui/pattern/usefulness/match-byte-array-patterns-2.stderr index 7968f9713ff..ffc8433403f 100644 --- a/src/test/ui/pattern/usefulness/match-byte-array-patterns-2.stderr +++ b/src/test/ui/pattern/usefulness/match-byte-array-patterns-2.stderr @@ -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]`