Skip to content

Commit 5cbf073

Browse files
authored
Unrolled build for rust-lang#140028
Rollup merge of rust-lang#140028 - dianne:lit-deref-pats-p1, r=oli-obk `deref_patterns`: support string and byte string literals in explicit `deref!("...")` patterns When `deref_patterns` is enabled, this allows string literal patterns to be used where `str` is expected and byte string literal patterns to be used where `[u8]` or `[u8; N]` is expected. This lets them be used in explicit `deref!("...")` patterns to match on `String`, `Box<str>`, `Vec<u8>`, `Box<[u8;N]>`, etc. (as well as to match on slices and arrays obtained through other means). Implementation-wise, this follows up on rust-lang#138992: similar to how byte string literals matching on `&[u8]` is implemented, this changes the type of the patterns as determined by HIR typeck, which informs const-to-pat on how to translate them to THIR (though strings needed a bit of extra work since we need references to call `<str as PartialEq>::eq` in the MIR lowering for string equality tests). This PR does not add support for implicit deref pattern syntax (e.g. `"..."` matching on `String`, as `string_deref_patterns` allows). I have that implemented locally, but I'm saving it for a follow-up PR[^1]. This also does not add support for using named or associated constants of type `&str` where `str` is expected (nor likewise with named byte string constants). It'd be possible to add that if there's an appetite for it, but I figure it's simplest to start with literals. This is gated by the `deref_patterns` feature since it's motivated by deref patterns. That said, its impact reaches outside of deref patterns; it may warrant a separate experiment and feature gate, particularly factoring in the follow-up[^1]. Even without deref patterns, I think there's probably motivation for these changes. The update to the unstable book added by this will conflict with rust-lang#140022, so they shouldn't be merged at the same time. Tracking issue for deref patterns: rust-lang#87121 r? ``@oli-obk`` cc ``@Nadrieril`` [^1]: The piece missing from this PR to support implicit deref pattern syntax is to allow string literal patterns to implicitly dereference their scrutinees before matching (see rust-lang#44849). As a consequence, it also makes examples like the one in that issue work (though it's still gated by `deref_patterns`). I can provide more information on how I've implemented it or open a draft if it'd help in reviewing this PR.
2 parents 7f69523 + 4c7e866 commit 5cbf073

File tree

14 files changed

+349
-38
lines changed

14 files changed

+349
-38
lines changed

compiler/rustc_hir_typeck/src/pat.rs

+39-7
Original file line numberDiff line numberDiff line change
@@ -759,22 +759,54 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
759759

760760
// Byte string patterns behave the same way as array patterns
761761
// They can denote both statically and dynamically-sized byte arrays.
762+
// Additionally, when `deref_patterns` is enabled, byte string literal patterns may have
763+
// types `[u8]` or `[u8; N]`, in order to type, e.g., `deref!(b"..."): Vec<u8>`.
762764
let mut pat_ty = ty;
763765
if let hir::PatExprKind::Lit {
764766
lit: Spanned { node: ast::LitKind::ByteStr(..), .. }, ..
765767
} = lt.kind
766768
{
769+
let tcx = self.tcx;
767770
let expected = self.structurally_resolve_type(span, expected);
768-
if let ty::Ref(_, inner_ty, _) = *expected.kind()
769-
&& self.try_structurally_resolve_type(span, inner_ty).is_slice()
770-
{
771-
let tcx = self.tcx;
772-
trace!(?lt.hir_id.local_id, "polymorphic byte string lit");
773-
pat_ty =
774-
Ty::new_imm_ref(tcx, tcx.lifetimes.re_static, Ty::new_slice(tcx, tcx.types.u8));
771+
match *expected.kind() {
772+
// Allow `b"...": &[u8]`
773+
ty::Ref(_, inner_ty, _)
774+
if self.try_structurally_resolve_type(span, inner_ty).is_slice() =>
775+
{
776+
trace!(?lt.hir_id.local_id, "polymorphic byte string lit");
777+
pat_ty = Ty::new_imm_ref(
778+
tcx,
779+
tcx.lifetimes.re_static,
780+
Ty::new_slice(tcx, tcx.types.u8),
781+
);
782+
}
783+
// Allow `b"...": [u8; 3]` for `deref_patterns`
784+
ty::Array(..) if tcx.features().deref_patterns() => {
785+
pat_ty = match *ty.kind() {
786+
ty::Ref(_, inner_ty, _) => inner_ty,
787+
_ => span_bug!(span, "found byte string literal with non-ref type {ty:?}"),
788+
}
789+
}
790+
// Allow `b"...": [u8]` for `deref_patterns`
791+
ty::Slice(..) if tcx.features().deref_patterns() => {
792+
pat_ty = Ty::new_slice(tcx, tcx.types.u8);
793+
}
794+
// Otherwise, `b"...": &[u8; 3]`
795+
_ => {}
775796
}
776797
}
777798

799+
// When `deref_patterns` is enabled, in order to allow `deref!("..."): String`, we allow
800+
// string literal patterns to have type `str`. This is accounted for when lowering to MIR.
801+
if self.tcx.features().deref_patterns()
802+
&& let hir::PatExprKind::Lit {
803+
lit: Spanned { node: ast::LitKind::Str(..), .. }, ..
804+
} = lt.kind
805+
&& self.try_structurally_resolve_type(span, expected).is_str()
806+
{
807+
pat_ty = self.tcx.types.str_;
808+
}
809+
778810
if self.tcx.features().string_deref_patterns()
779811
&& let hir::PatExprKind::Lit {
780812
lit: Spanned { node: ast::LitKind::Str(..), .. }, ..

compiler/rustc_mir_build/src/builder/matches/test.rs

+23
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,29 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
146146
let mut place = place;
147147
let mut block = block;
148148
match ty.kind() {
149+
ty::Str => {
150+
// String literal patterns may have type `str` if `deref_patterns` is
151+
// enabled, in order to allow `deref!("..."): String`. In this case, `value`
152+
// is of type `&str`, so we compare it to `&place`.
153+
if !tcx.features().deref_patterns() {
154+
span_bug!(
155+
test.span,
156+
"matching on `str` went through without enabling deref_patterns"
157+
);
158+
}
159+
let re_erased = tcx.lifetimes.re_erased;
160+
let ref_str_ty = Ty::new_imm_ref(tcx, re_erased, tcx.types.str_);
161+
let ref_place = self.temp(ref_str_ty, test.span);
162+
// `let ref_place: &str = &place;`
163+
self.cfg.push_assign(
164+
block,
165+
self.source_info(test.span),
166+
ref_place,
167+
Rvalue::Ref(re_erased, BorrowKind::Shared, place),
168+
);
169+
place = ref_place;
170+
ty = ref_str_ty;
171+
}
149172
ty::Adt(def, _) if tcx.is_lang_item(def.did(), LangItem::String) => {
150173
if !tcx.features().string_deref_patterns() {
151174
span_bug!(

compiler/rustc_mir_build/src/thir/constant.rs

+12-2
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,23 @@ pub(crate) fn lit_to_const<'tcx>(
3737
let str_bytes = s.as_str().as_bytes();
3838
ty::ValTree::from_raw_bytes(tcx, str_bytes)
3939
}
40+
(ast::LitKind::Str(s, _), ty::Str) if tcx.features().deref_patterns() => {
41+
// String literal patterns may have type `str` if `deref_patterns` is enabled, in order
42+
// to allow `deref!("..."): String`.
43+
let str_bytes = s.as_str().as_bytes();
44+
ty::ValTree::from_raw_bytes(tcx, str_bytes)
45+
}
4046
(ast::LitKind::ByteStr(data, _), ty::Ref(_, inner_ty, _))
41-
if matches!(inner_ty.kind(), ty::Slice(_)) =>
47+
if matches!(inner_ty.kind(), ty::Slice(_) | ty::Array(..)) =>
4248
{
4349
let bytes = data as &[u8];
4450
ty::ValTree::from_raw_bytes(tcx, bytes)
4551
}
46-
(ast::LitKind::ByteStr(data, _), ty::Ref(_, inner_ty, _)) if inner_ty.is_array() => {
52+
(ast::LitKind::ByteStr(data, _), ty::Slice(_) | ty::Array(..))
53+
if tcx.features().deref_patterns() =>
54+
{
55+
// Byte string literal patterns may have type `[u8]` or `[u8; N]` if `deref_patterns` is
56+
// enabled, in order to allow, e.g., `deref!(b"..."): Vec<u8>`.
4757
let bytes = data as &[u8];
4858
ty::ValTree::from_raw_bytes(tcx, bytes)
4959
}

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

+10
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,16 @@ impl<'tcx> ConstToPat<'tcx> {
280280
slice: None,
281281
suffix: Box::new([]),
282282
},
283+
ty::Str => {
284+
// String literal patterns may have type `str` if `deref_patterns` is enabled, in
285+
// order to allow `deref!("..."): String`. Since we need a `&str` for the comparison
286+
// when lowering to MIR in `Builder::perform_test`, treat the constant as a `&str`.
287+
// This works because `str` and `&str` have the same valtree representation.
288+
let ref_str_ty = Ty::new_imm_ref(tcx, tcx.lifetimes.re_erased, ty);
289+
PatKind::Constant {
290+
value: mir::Const::Ty(ref_str_ty, ty::Const::new_value(tcx, cv, ref_str_ty)),
291+
}
292+
}
283293
ty::Ref(_, pointee_ty, ..) => match *pointee_ty.kind() {
284294
// `&str` is represented as a valtree, let's keep using this
285295
// optimization for now.

src/doc/unstable-book/src/language-features/deref-patterns.md

+21
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,25 @@ if let [b] = &mut *v {
5454
assert_eq!(v, [Box::new(Some(2))]);
5555
```
5656

57+
Additionally, when `deref_patterns` is enabled, string literal patterns may be written where `str`
58+
is expected. Likewise, byte string literal patterns may be written where `[u8]` or `[u8; _]` is
59+
expected. This lets them be used in `deref!(_)` patterns:
60+
61+
```rust
62+
# #![feature(deref_patterns)]
63+
# #![allow(incomplete_features)]
64+
match ("test".to_string(), b"test".to_vec()) {
65+
(deref!("test"), deref!(b"test")) => {}
66+
_ => panic!(),
67+
}
68+
69+
// Matching on slices and arrays using literals is possible elsewhere as well:
70+
match *"test" {
71+
"test" => {}
72+
_ => panic!(),
73+
}
74+
```
75+
76+
Implicit deref pattern syntax is not yet supported for string or byte string literals.
77+
5778
[smart pointers in the standard library]: https://doc.rust-lang.org/std/ops/trait.DerefPure.html#implementors
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
//! Byte string literal patterns use the mutability of the literal, rather than the mutability of
2+
//! the pattern's scrutinee. Since byte string literals are always shared references, it's a
3+
//! mismatch to use a byte string literal pattern to match on a mutable array or slice reference.
4+
5+
fn main() {
6+
let mut val = [97u8, 10u8];
7+
match &mut val {
8+
b"a\n" => {},
9+
//~^ ERROR mismatched types
10+
//~| types differ in mutability
11+
_ => {},
12+
}
13+
match &mut val[..] {
14+
b"a\n" => {},
15+
//~^ ERROR mismatched types
16+
//~| types differ in mutability
17+
_ => {},
18+
}
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
error[E0308]: mismatched types
2+
--> $DIR/byte-string-mutability-mismatch.rs:8:9
3+
|
4+
LL | match &mut val {
5+
| -------- this expression has type `&mut [u8; 2]`
6+
LL | b"a\n" => {},
7+
| ^^^^^^ types differ in mutability
8+
|
9+
= note: expected mutable reference `&mut _`
10+
found reference `&'static _`
11+
12+
error[E0308]: mismatched types
13+
--> $DIR/byte-string-mutability-mismatch.rs:14:10
14+
|
15+
LL | match &mut val[..] {
16+
| ------------ this expression has type `&mut [u8]`
17+
LL | b"a\n" => {},
18+
| ^^^^^^ types differ in mutability
19+
|
20+
= note: expected mutable reference `&mut _`
21+
found reference `&'static _`
22+
23+
error: aborting due to 2 previous errors
24+
25+
For more information about this error, try `rustc --explain E0308`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
//! Test type errors for byte string literal patterns. `deref_patterns` allows byte string literal
2+
//! patterns to have type `[u8]` or `[u8; N]` when matching on a slice or array; this can affect the
3+
//! "found" type reported in error messages when matching on a slice or array of the wrong type.
4+
5+
#![feature(deref_patterns)]
6+
#![expect(incomplete_features)]
7+
8+
fn main() {
9+
// Baseline 1: under normal circumstances, byte string literal patterns have type `&[u8; N]`,
10+
// the same as byte string literals.
11+
if let b"test" = () {}
12+
//~^ ERROR mismatched types
13+
//~| expected `()`, found `&[u8; 4]`
14+
15+
// Baseline 2: there's a special case for byte string patterns in stable rust, allowing them to
16+
// match on slice references. This affects the error when matching on a non-`&[u8]` slice ref,
17+
// reporting the "found" type as `&[u8]`.
18+
if let b"test" = &[] as &[i8] {}
19+
//~^ ERROR mismatched types
20+
//~| expected `&[i8]`, found `&[u8]`
21+
22+
// Test matching on a non-`[u8]` slice: the pattern has type `[u8]` if a slice is expected.
23+
if let b"test" = *(&[] as &[i8]) {}
24+
//~^ ERROR mismatched types
25+
//~| expected `[i8]`, found `[u8]`
26+
27+
// Test matching on a non-`[u8;4]` array: the pattern has type `[u8;4]` if an array is expected.
28+
if let b"test" = [()] {}
29+
//~^ ERROR mismatched types
30+
//~| expected `[(); 1]`, found `[u8; 4]`
31+
if let b"test" = *b"this array is too long" {}
32+
//~^ ERROR mismatched types
33+
//~| expected an array with a size of 22, found one with a size of 4
34+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
error[E0308]: mismatched types
2+
--> $DIR/byte-string-type-errors.rs:11:12
3+
|
4+
LL | if let b"test" = () {}
5+
| ^^^^^^^ -- this expression has type `()`
6+
| |
7+
| expected `()`, found `&[u8; 4]`
8+
9+
error[E0308]: mismatched types
10+
--> $DIR/byte-string-type-errors.rs:18:12
11+
|
12+
LL | if let b"test" = &[] as &[i8] {}
13+
| ^^^^^^^ ------------ this expression has type `&[i8]`
14+
| |
15+
| expected `&[i8]`, found `&[u8]`
16+
|
17+
= note: expected reference `&[i8]`
18+
found reference `&'static [u8]`
19+
20+
error[E0308]: mismatched types
21+
--> $DIR/byte-string-type-errors.rs:23:12
22+
|
23+
LL | if let b"test" = *(&[] as &[i8]) {}
24+
| ^^^^^^^ --------------- this expression has type `[i8]`
25+
| |
26+
| expected `[i8]`, found `[u8]`
27+
|
28+
= note: expected slice `[i8]`
29+
found slice `[u8]`
30+
31+
error[E0308]: mismatched types
32+
--> $DIR/byte-string-type-errors.rs:28:12
33+
|
34+
LL | if let b"test" = [()] {}
35+
| ^^^^^^^ ---- this expression has type `[(); 1]`
36+
| |
37+
| expected `[(); 1]`, found `[u8; 4]`
38+
|
39+
= note: expected array `[(); 1]`
40+
found array `[u8; 4]`
41+
42+
error[E0308]: mismatched types
43+
--> $DIR/byte-string-type-errors.rs:31:12
44+
|
45+
LL | if let b"test" = *b"this array is too long" {}
46+
| ^^^^^^^ -------------------------- this expression has type `[u8; 22]`
47+
| |
48+
| expected an array with a size of 22, found one with a size of 4
49+
50+
error: aborting due to 5 previous errors
51+
52+
For more information about this error, try `rustc --explain E0308`.

tests/ui/pattern/deref-patterns/needs-gate.rs

+17
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,21 @@ fn main() {
1212
//~^ ERROR: mismatched types
1313
_ => {}
1414
}
15+
16+
// `deref_patterns` allows string and byte string literals to have non-ref types.
17+
match *"test" {
18+
"test" => {}
19+
//~^ ERROR: mismatched types
20+
_ => {}
21+
}
22+
match *b"test" {
23+
b"test" => {}
24+
//~^ ERROR: mismatched types
25+
_ => {}
26+
}
27+
match *(b"test" as &[u8]) {
28+
b"test" => {}
29+
//~^ ERROR: mismatched types
30+
_ => {}
31+
}
1532
}

tests/ui/pattern/deref-patterns/needs-gate.stderr

+25-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,31 @@ help: consider dereferencing to access the inner value using the Deref trait
2323
LL | match *Box::new(0) {
2424
| +
2525

26-
error: aborting due to 2 previous errors
26+
error[E0308]: mismatched types
27+
--> $DIR/needs-gate.rs:18:9
28+
|
29+
LL | match *"test" {
30+
| ------- this expression has type `str`
31+
LL | "test" => {}
32+
| ^^^^^^ expected `str`, found `&str`
33+
34+
error[E0308]: mismatched types
35+
--> $DIR/needs-gate.rs:23:9
36+
|
37+
LL | match *b"test" {
38+
| -------- this expression has type `[u8; 4]`
39+
LL | b"test" => {}
40+
| ^^^^^^^ expected `[u8; 4]`, found `&[u8; 4]`
41+
42+
error[E0308]: mismatched types
43+
--> $DIR/needs-gate.rs:28:9
44+
|
45+
LL | match *(b"test" as &[u8]) {
46+
| ------------------- this expression has type `[u8]`
47+
LL | b"test" => {}
48+
| ^^^^^^^ expected `[u8]`, found `&[u8; 4]`
49+
50+
error: aborting due to 5 previous errors
2751

2852
Some errors have detailed explanations: E0308, E0658.
2953
For more information about an error, try `rustc --explain E0308`.

0 commit comments

Comments
 (0)