Skip to content

Commit 3fb1230

Browse files
authoredApr 2, 2025
Rollup merge of #138992 - dianne:simplify-byte-string-to-pat, r=oli-obk
literal pattern lowering: use the pattern's type instead of the literal's in `const_to_pat` This has two purposes: - First, it enables removing the `treat_byte_string_as_slice` fields from `TypeckResults` and `ConstToPat`. A byte string pattern's type will be `&[u8]` when matching on a slice reference, so `const_to_pat` will lower it to a slice ref pattern. I believe this is tested by `tests/ui/match/pattern-deref-miscompile.rs`. - Second, it will simplify the implementation of byte string literals in deref patterns. If byte string patterns can be given the type `[u8; N]` or `[u8]` during HIR typeck, then nothing needs to be changed in `const_to_pat` in order to lower the patterns `deref!(b"..."): Vec<u8>` and `deref!(b"..."): Box<[u8; 3]>`. Implementation-wise, this uses `lit_to_const` to make a const with the pattern's type and the literal's valtree; that feels to me like the best way to make sure that the valtree representations of the pattern type and literal are the same. Though it may necessitate later changes to `lit_to_const` to accommodate giving byte string literal patterns non-reference types—would that be reasonable? This unfortunately doesn't work for the `string_deref_patterns` feature (since that gives string literal patterns the `String` type), so I added a workaround for that. However, once `deref_patterns` supports string literals, it may be able to replace `string_deref_patterns`; the special case for `String` can removed at that point. r? ``@oli-obk``
2 parents ae9173d + 7a4d4de commit 3fb1230

File tree

7 files changed

+172
-48
lines changed

7 files changed

+172
-48
lines changed
 

‎compiler/rustc_hir_typeck/src/pat.rs

-4
Original file line numberDiff line numberDiff line change
@@ -632,10 +632,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
632632
{
633633
let tcx = self.tcx;
634634
trace!(?lt.hir_id.local_id, "polymorphic byte string lit");
635-
self.typeck_results
636-
.borrow_mut()
637-
.treat_byte_string_as_slice
638-
.insert(lt.hir_id.local_id);
639635
pat_ty =
640636
Ty::new_imm_ref(tcx, tcx.lifetimes.re_static, Ty::new_slice(tcx, tcx.types.u8));
641637
}

‎compiler/rustc_hir_typeck/src/writeback.rs

-3
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
8181
debug!("used_trait_imports({:?}) = {:?}", item_def_id, used_trait_imports);
8282
wbcx.typeck_results.used_trait_imports = used_trait_imports;
8383

84-
wbcx.typeck_results.treat_byte_string_as_slice =
85-
mem::take(&mut self.typeck_results.borrow_mut().treat_byte_string_as_slice);
86-
8784
debug!("writeback: typeck results for {:?} are {:#?}", item_def_id, wbcx.typeck_results);
8885

8986
self.tcx.arena.alloc(wbcx.typeck_results)

‎compiler/rustc_middle/src/ty/typeck_results.rs

-7
Original file line numberDiff line numberDiff line change
@@ -197,12 +197,6 @@ pub struct TypeckResults<'tcx> {
197197
/// formatting modified file tests/ui/coroutine/retain-resume-ref.rs
198198
pub coroutine_stalled_predicates: FxIndexSet<(ty::Predicate<'tcx>, ObligationCause<'tcx>)>,
199199

200-
/// We sometimes treat byte string literals (which are of type `&[u8; N]`)
201-
/// as `&[u8]`, depending on the pattern in which they are used.
202-
/// This hashset records all instances where we behave
203-
/// like this to allow `const_to_pat` to reliably handle this situation.
204-
pub treat_byte_string_as_slice: ItemLocalSet,
205-
206200
/// Contains the data for evaluating the effect of feature `capture_disjoint_fields`
207201
/// on closure size.
208202
pub closure_size_eval: LocalDefIdMap<ClosureSizeProfileData<'tcx>>,
@@ -237,7 +231,6 @@ impl<'tcx> TypeckResults<'tcx> {
237231
closure_fake_reads: Default::default(),
238232
rvalue_scopes: Default::default(),
239233
coroutine_stalled_predicates: Default::default(),
240-
treat_byte_string_as_slice: Default::default(),
241234
closure_size_eval: Default::default(),
242235
offset_of_data: Default::default(),
243236
}

‎compiler/rustc_mir_build/src/thir/pattern/const_to_pat.rs

+2-29
Original file line numberDiff line numberDiff line change
@@ -58,25 +58,13 @@ struct ConstToPat<'tcx> {
5858
span: Span,
5959
id: hir::HirId,
6060

61-
treat_byte_string_as_slice: bool,
62-
6361
c: ty::Const<'tcx>,
6462
}
6563

6664
impl<'tcx> ConstToPat<'tcx> {
6765
fn new(pat_ctxt: &PatCtxt<'_, 'tcx>, id: hir::HirId, span: Span, c: ty::Const<'tcx>) -> Self {
6866
trace!(?pat_ctxt.typeck_results.hir_owner);
69-
ConstToPat {
70-
tcx: pat_ctxt.tcx,
71-
typing_env: pat_ctxt.typing_env,
72-
span,
73-
id,
74-
treat_byte_string_as_slice: pat_ctxt
75-
.typeck_results
76-
.treat_byte_string_as_slice
77-
.contains(&id.local_id),
78-
c,
79-
}
67+
ConstToPat { tcx: pat_ctxt.tcx, typing_env: pat_ctxt.typing_env, span, id, c }
8068
}
8169

8270
fn type_marked_structural(&self, ty: Ty<'tcx>) -> bool {
@@ -108,8 +96,6 @@ impl<'tcx> ConstToPat<'tcx> {
10896
uv: ty::UnevaluatedConst<'tcx>,
10997
ty: Ty<'tcx>,
11098
) -> Box<Pat<'tcx>> {
111-
trace!(self.treat_byte_string_as_slice);
112-
11399
// It's not *technically* correct to be revealing opaque types here as borrowcheck has
114100
// not run yet. However, CTFE itself uses `TypingMode::PostAnalysis` unconditionally even
115101
// during typeck and not doing so has a lot of (undesirable) fallout (#101478, #119821).
@@ -307,21 +293,8 @@ impl<'tcx> ConstToPat<'tcx> {
307293
ty,
308294
);
309295
} else {
310-
// `b"foo"` produces a `&[u8; 3]`, but you can't use constants of array type when
311-
// matching against references, you can only use byte string literals.
312-
// The typechecker has a special case for byte string literals, by treating them
313-
// as slices. This means we turn `&[T; N]` constants into slice patterns, which
314-
// has no negative effects on pattern matching, even if we're actually matching on
315-
// arrays.
316-
let pointee_ty = match *pointee_ty.kind() {
317-
ty::Array(elem_ty, _) if self.treat_byte_string_as_slice => {
318-
Ty::new_slice(tcx, elem_ty)
319-
}
320-
_ => *pointee_ty,
321-
};
322296
// References have the same valtree representation as their pointee.
323-
let subpattern = self.valtree_to_pat(cv, pointee_ty);
324-
PatKind::Deref { subpattern }
297+
PatKind::Deref { subpattern: self.valtree_to_pat(cv, *pointee_ty) }
325298
}
326299
}
327300
},

‎compiler/rustc_mir_build/src/thir/pattern/mod.rs

+33-5
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use rustc_abi::{FieldIdx, Integer};
1111
use rustc_errors::codes::*;
1212
use rustc_hir::def::{CtorOf, DefKind, Res};
1313
use rustc_hir::pat_util::EnumerateAndAdjustIterator;
14-
use rustc_hir::{self as hir, RangeEnd};
14+
use rustc_hir::{self as hir, LangItem, RangeEnd};
1515
use rustc_index::Idx;
1616
use rustc_middle::mir::interpret::LitToConstInput;
1717
use rustc_middle::thir::{
@@ -130,7 +130,7 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
130130

131131
// Lower the endpoint into a temporary `PatKind` that will then be
132132
// deconstructed to obtain the constant value and other data.
133-
let mut kind: PatKind<'tcx> = self.lower_pat_expr(expr);
133+
let mut kind: PatKind<'tcx> = self.lower_pat_expr(expr, None);
134134

135135
// Unpeel any ascription or inline-const wrapper nodes.
136136
loop {
@@ -294,7 +294,7 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
294294

295295
hir::PatKind::Never => PatKind::Never,
296296

297-
hir::PatKind::Expr(value) => self.lower_pat_expr(value),
297+
hir::PatKind::Expr(value) => self.lower_pat_expr(value, Some(ty)),
298298

299299
hir::PatKind::Range(ref lo_expr, ref hi_expr, end) => {
300300
let (lo_expr, hi_expr) = (lo_expr.as_deref(), hi_expr.as_deref());
@@ -630,7 +630,11 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
630630
/// - Paths (e.g. `FOO`, `foo::BAR`, `Option::None`)
631631
/// - Inline const blocks (e.g. `const { 1 + 1 }`)
632632
/// - Literals, possibly negated (e.g. `-128u8`, `"hello"`)
633-
fn lower_pat_expr(&mut self, expr: &'tcx hir::PatExpr<'tcx>) -> PatKind<'tcx> {
633+
fn lower_pat_expr(
634+
&mut self,
635+
expr: &'tcx hir::PatExpr<'tcx>,
636+
pat_ty: Option<Ty<'tcx>>,
637+
) -> PatKind<'tcx> {
634638
let (lit, neg) = match &expr.kind {
635639
hir::PatExprKind::Path(qpath) => {
636640
return self.lower_path(qpath, expr.hir_id, expr.span).kind;
@@ -641,7 +645,31 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
641645
hir::PatExprKind::Lit { lit, negated } => (lit, *negated),
642646
};
643647

644-
let ct_ty = self.typeck_results.node_type(expr.hir_id);
648+
// We handle byte string literal patterns by using the pattern's type instead of the
649+
// literal's type in `const_to_pat`: if the literal `b"..."` matches on a slice reference,
650+
// the pattern's type will be `&[u8]` whereas the literal's type is `&[u8; 3]`; using the
651+
// pattern's type means we'll properly translate it to a slice reference pattern. This works
652+
// because slices and arrays have the same valtree representation.
653+
// HACK: As an exception, use the literal's type if `pat_ty` is `String`; this can happen if
654+
// `string_deref_patterns` is enabled. There's a special case for that when lowering to MIR.
655+
// FIXME(deref_patterns): This hack won't be necessary once `string_deref_patterns` is
656+
// superseded by a more general implementation of deref patterns.
657+
let ct_ty = match pat_ty {
658+
Some(pat_ty)
659+
if let ty::Adt(def, _) = *pat_ty.kind()
660+
&& self.tcx.is_lang_item(def.did(), LangItem::String) =>
661+
{
662+
if !self.tcx.features().string_deref_patterns() {
663+
span_bug!(
664+
expr.span,
665+
"matching on `String` went through without enabling string_deref_patterns"
666+
);
667+
}
668+
self.typeck_results.node_type(expr.hir_id)
669+
}
670+
Some(pat_ty) => pat_ty,
671+
None => self.typeck_results.node_type(expr.hir_id),
672+
};
645673
let lit_input = LitToConstInput { lit: &lit.node, ty: ct_ty, neg };
646674
let constant = self.tcx.at(expr.span).lit_to_const(lit_input);
647675
self.const_to_pat(constant, ct_ty, expr.hir_id, lit.span).kind
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
//! Tests that arrays and slices in constants aren't interchangeable when used as patterns.
2+
3+
#[derive(PartialEq, Eq)]
4+
struct SomeStruct<T: ?Sized>(T);
5+
6+
const BSTR_SIZED: &'static [u8; 3] = b"012";
7+
const BSTR_UNSIZED: &'static [u8] = BSTR_SIZED;
8+
const STRUCT_SIZED: &'static SomeStruct<[u8; 3]> = &SomeStruct(*BSTR_SIZED);
9+
const STRUCT_UNSIZED: &'static SomeStruct<[u8]> = STRUCT_SIZED;
10+
11+
fn type_mismatches() {
12+
// Test that array consts can't be used where a slice pattern is expected. This helps ensure
13+
// that `const_to_pat` won't produce irrefutable `thir::PatKind::Array` patterns when matching
14+
// on slices, which would result in missing length checks.
15+
// See also `tests/ui/match/pattern-deref-miscompile.rs`, which tests that byte string literal
16+
// patterns check slices' length appropriately when matching on slices.
17+
match BSTR_UNSIZED {
18+
BSTR_SIZED => {}
19+
//~^ ERROR: mismatched types
20+
_ => {}
21+
}
22+
match STRUCT_UNSIZED {
23+
STRUCT_SIZED => {}
24+
//~^ ERROR: mismatched types
25+
_ => {}
26+
}
27+
28+
// Test that slice consts can't be used where an array pattern is expected.
29+
match BSTR_UNSIZED {
30+
BSTR_SIZED => {}
31+
//~^ ERROR: mismatched types
32+
_ => {}
33+
}
34+
// If the types matched here, this would still error, since unsized structs aren't permitted in
35+
// constant patterns. See the `invalid_patterns` test below.
36+
match STRUCT_UNSIZED {
37+
STRUCT_SIZED => {}
38+
//~^ ERROR: mismatched types
39+
_ => {}
40+
}
41+
}
42+
43+
fn invalid_patterns() {
44+
// Test that unsized structs containing slices can't be used as patterns.
45+
// See `tests/ui/consts/issue-87046.rs` for an example with `str`.
46+
match STRUCT_UNSIZED {
47+
STRUCT_UNSIZED => {}
48+
//~^ ERROR: cannot use unsized non-slice type `SomeStruct<[u8]>` in constant patterns
49+
_ => {}
50+
}
51+
}
52+
53+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
error[E0308]: mismatched types
2+
--> $DIR/arrays-and-slices.rs:18:9
3+
|
4+
LL | const BSTR_SIZED: &'static [u8; 3] = b"012";
5+
| ---------------------------------- constant defined here
6+
...
7+
LL | match BSTR_UNSIZED {
8+
| ------------ this expression has type `&[u8]`
9+
LL | BSTR_SIZED => {}
10+
| ^^^^^^^^^^
11+
| |
12+
| expected `&[u8]`, found `&[u8; 3]`
13+
| `BSTR_SIZED` is interpreted as a constant, not a new binding
14+
| help: introduce a new binding instead: `other_bstr_sized`
15+
|
16+
= note: expected reference `&[u8]`
17+
found reference `&'static [u8; 3]`
18+
19+
error[E0308]: mismatched types
20+
--> $DIR/arrays-and-slices.rs:23:9
21+
|
22+
LL | const STRUCT_SIZED: &'static SomeStruct<[u8; 3]> = &SomeStruct(*BSTR_SIZED);
23+
| ------------------------------------------------ constant defined here
24+
...
25+
LL | match STRUCT_UNSIZED {
26+
| -------------- this expression has type `&SomeStruct<[u8]>`
27+
LL | STRUCT_SIZED => {}
28+
| ^^^^^^^^^^^^
29+
| |
30+
| expected `&SomeStruct<[u8]>`, found `&SomeStruct<[u8; 3]>`
31+
| `STRUCT_SIZED` is interpreted as a constant, not a new binding
32+
| help: introduce a new binding instead: `other_struct_sized`
33+
|
34+
= note: expected reference `&SomeStruct<[u8]>`
35+
found reference `&'static SomeStruct<[u8; 3]>`
36+
37+
error[E0308]: mismatched types
38+
--> $DIR/arrays-and-slices.rs:30:9
39+
|
40+
LL | const BSTR_SIZED: &'static [u8; 3] = b"012";
41+
| ---------------------------------- constant defined here
42+
...
43+
LL | match BSTR_UNSIZED {
44+
| ------------ this expression has type `&[u8]`
45+
LL | BSTR_SIZED => {}
46+
| ^^^^^^^^^^
47+
| |
48+
| expected `&[u8]`, found `&[u8; 3]`
49+
| `BSTR_SIZED` is interpreted as a constant, not a new binding
50+
| help: introduce a new binding instead: `other_bstr_sized`
51+
|
52+
= note: expected reference `&[u8]`
53+
found reference `&'static [u8; 3]`
54+
55+
error[E0308]: mismatched types
56+
--> $DIR/arrays-and-slices.rs:37:9
57+
|
58+
LL | const STRUCT_SIZED: &'static SomeStruct<[u8; 3]> = &SomeStruct(*BSTR_SIZED);
59+
| ------------------------------------------------ constant defined here
60+
...
61+
LL | match STRUCT_UNSIZED {
62+
| -------------- this expression has type `&SomeStruct<[u8]>`
63+
LL | STRUCT_SIZED => {}
64+
| ^^^^^^^^^^^^
65+
| |
66+
| expected `&SomeStruct<[u8]>`, found `&SomeStruct<[u8; 3]>`
67+
| `STRUCT_SIZED` is interpreted as a constant, not a new binding
68+
| help: introduce a new binding instead: `other_struct_sized`
69+
|
70+
= note: expected reference `&SomeStruct<[u8]>`
71+
found reference `&'static SomeStruct<[u8; 3]>`
72+
73+
error: cannot use unsized non-slice type `SomeStruct<[u8]>` in constant patterns
74+
--> $DIR/arrays-and-slices.rs:47:9
75+
|
76+
LL | const STRUCT_UNSIZED: &'static SomeStruct<[u8]> = STRUCT_SIZED;
77+
| ----------------------------------------------- constant defined here
78+
...
79+
LL | STRUCT_UNSIZED => {}
80+
| ^^^^^^^^^^^^^^
81+
82+
error: aborting due to 5 previous errors
83+
84+
For more information about this error, try `rustc --explain E0308`.

0 commit comments

Comments
 (0)
Please sign in to comment.