Skip to content

deref_patterns: support string and byte string literals in explicit deref!("...") patterns #140028

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

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
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
46 changes: 39 additions & 7 deletions compiler/rustc_hir_typeck/src/pat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -759,22 +759,54 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {

// Byte string patterns behave the same way as array patterns
// They can denote both statically and dynamically-sized byte arrays.
// Additionally, when `deref_patterns` is enabled, byte string literal patterns may have
// types `[u8]` or `[u8; N]`, in order to type, e.g., `deref!(b"..."): Vec<u8>`.
let mut pat_ty = ty;
if let hir::PatExprKind::Lit {
lit: Spanned { node: ast::LitKind::ByteStr(..), .. }, ..
} = lt.kind
{
let tcx = self.tcx;
let expected = self.structurally_resolve_type(span, expected);
if let ty::Ref(_, inner_ty, _) = *expected.kind()
&& self.try_structurally_resolve_type(span, inner_ty).is_slice()
{
let tcx = self.tcx;
trace!(?lt.hir_id.local_id, "polymorphic byte string lit");
pat_ty =
Ty::new_imm_ref(tcx, tcx.lifetimes.re_static, Ty::new_slice(tcx, tcx.types.u8));
match *expected.kind() {
// Allow `b"...": &[u8]`
ty::Ref(_, inner_ty, _)
if self.try_structurally_resolve_type(span, inner_ty).is_slice() =>
{
trace!(?lt.hir_id.local_id, "polymorphic byte string lit");
pat_ty = Ty::new_imm_ref(
tcx,
tcx.lifetimes.re_static,
Ty::new_slice(tcx, tcx.types.u8),
);
}
// Allow `b"...": [u8; 3]` for `deref_patterns`
ty::Array(..) if tcx.features().deref_patterns() => {
pat_ty = match *ty.kind() {
ty::Ref(_, inner_ty, _) => inner_ty,
_ => span_bug!(span, "found byte string literal with non-ref type {ty:?}"),
}
}
// Allow `b"...": [u8]` for `deref_patterns`
ty::Slice(..) if tcx.features().deref_patterns() => {
pat_ty = Ty::new_slice(tcx, tcx.types.u8);
}
// Otherwise, `b"...": &[u8; 3]`
_ => {}
}
}

// When `deref_patterns` is enabled, in order to allow `deref!("..."): String`, we allow
// string literal patterns to have type `str`. This is accounted for when lowering to MIR.
if self.tcx.features().deref_patterns()
&& let hir::PatExprKind::Lit {
lit: Spanned { node: ast::LitKind::Str(..), .. }, ..
} = lt.kind
&& self.try_structurally_resolve_type(span, expected).is_str()
{
pat_ty = self.tcx.types.str_;
}

if self.tcx.features().string_deref_patterns()
&& let hir::PatExprKind::Lit {
lit: Spanned { node: ast::LitKind::Str(..), .. }, ..
Expand Down
23 changes: 23 additions & 0 deletions compiler/rustc_mir_build/src/builder/matches/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,29 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
let mut place = place;
let mut block = block;
match ty.kind() {
ty::Str => {
// String literal patterns may have type `str` if `deref_patterns` is
// enabled, in order to allow `deref!("..."): String`. In this case, `value`
// is of type `&str`, so we compare it to `&place`.
if !tcx.features().deref_patterns() {
span_bug!(
test.span,
"matching on `str` went through without enabling deref_patterns"
);
}
let re_erased = tcx.lifetimes.re_erased;
let ref_str_ty = Ty::new_imm_ref(tcx, re_erased, tcx.types.str_);
let ref_place = self.temp(ref_str_ty, test.span);
// `let ref_place: &str = &place;`
self.cfg.push_assign(
block,
self.source_info(test.span),
ref_place,
Rvalue::Ref(re_erased, BorrowKind::Shared, place),
);
place = ref_place;
ty = ref_str_ty;
}
ty::Adt(def, _) if tcx.is_lang_item(def.did(), LangItem::String) => {
if !tcx.features().string_deref_patterns() {
span_bug!(
Expand Down
14 changes: 12 additions & 2 deletions compiler/rustc_mir_build/src/thir/constant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,23 @@ pub(crate) fn lit_to_const<'tcx>(
let str_bytes = s.as_str().as_bytes();
ty::ValTree::from_raw_bytes(tcx, str_bytes)
}
(ast::LitKind::Str(s, _), ty::Str) if tcx.features().deref_patterns() => {
// String literal patterns may have type `str` if `deref_patterns` is enabled, in order
// to allow `deref!("..."): String`.
let str_bytes = s.as_str().as_bytes();
ty::ValTree::from_raw_bytes(tcx, str_bytes)
}
(ast::LitKind::ByteStr(data, _), ty::Ref(_, inner_ty, _))
if matches!(inner_ty.kind(), ty::Slice(_)) =>
if matches!(inner_ty.kind(), ty::Slice(_) | ty::Array(..)) =>
{
let bytes = data as &[u8];
ty::ValTree::from_raw_bytes(tcx, bytes)
}
(ast::LitKind::ByteStr(data, _), ty::Ref(_, inner_ty, _)) if inner_ty.is_array() => {
(ast::LitKind::ByteStr(data, _), ty::Slice(_) | ty::Array(..))
if tcx.features().deref_patterns() =>
{
// Byte string literal patterns may have type `[u8]` or `[u8; N]` if `deref_patterns` is
// enabled, in order to allow, e.g., `deref!(b"..."): Vec<u8>`.
let bytes = data as &[u8];
ty::ValTree::from_raw_bytes(tcx, bytes)
}
Expand Down
9 changes: 9 additions & 0 deletions compiler/rustc_mir_build/src/thir/pattern/const_to_pat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,15 @@ impl<'tcx> ConstToPat<'tcx> {
slice: None,
suffix: Box::new([]),
},
ty::Str => {
// String literal patterns may have type `str` if `deref_patterns` is enabled, in
// order to allow `deref!("..."): String`. Since we need a `&str` for the comparison
// when lowering to MIR in `Builder::perform_test`, treat the constant as a `&str`.
let ref_str_ty = Ty::new_imm_ref(tcx, tcx.lifetimes.re_erased, ty);
PatKind::Constant {
value: mir::Const::Ty(ref_str_ty, ty::Const::new_value(tcx, cv, ref_str_ty)),
}
}
ty::Ref(_, pointee_ty, ..) => match *pointee_ty.kind() {
// `&str` is represented as a valtree, let's keep using this
// optimization for now.
Expand Down
21 changes: 21 additions & 0 deletions src/doc/unstable-book/src/language-features/deref-patterns.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,25 @@ if let [b] = &mut *v {
assert_eq!(v, [Box::new(Some(2))]);
```

Additionally, when `deref_patterns` is enabled, string literal patterns may be written where `str`
is expected. Likewise, byte string literal patterns may be written where `[u8]` or `[u8; _]` is
expected. This lets them be used in `deref!(_)` patterns:

```rust
# #![feature(deref_patterns)]
# #![allow(incomplete_features)]
match ("test".to_string(), b"test".to_vec()) {
(deref!("test"), deref!(b"test")) => {}
_ => panic!(),
}

// Matching on slices and arrays using literals is possible elsewhere as well:
match *"test" {
"test" => {}
_ => panic!(),
}
```

Implicit deref pattern syntax is not yet supported for string or byte string literals.

[smart pointers in the standard library]: https://doc.rust-lang.org/std/ops/trait.DerefPure.html#implementors
34 changes: 34 additions & 0 deletions tests/ui/pattern/deref-patterns/byte-string-type-errors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//! Test type errors for byte string literal patterns. `deref_patterns` allows byte string literal
//! patterns to have type `[u8]` or `[u8; N]` when matching on a slice or array; this can affect the
//! "found" type reported in error messages when matching on a slice or array of the wrong type.

#![feature(deref_patterns)]
#![expect(incomplete_features)]

fn main() {
// Baseline 1: under normal circumstances, byte string literal patterns have type `&[u8; N]`,
// the same as byte string literals.
if let b"test" = () {}
//~^ ERROR mismatched types
//~| expected `()`, found `&[u8; 4]`

// Baseline 2: there's a special case for byte string patterns in stable rust, allowing them to
// match on slice references. This affects the error when matching on a non-`&[u8]` slice ref,
// reporting the "found" type as `&[u8]`.
if let b"test" = &[] as &[i8] {}
//~^ ERROR mismatched types
//~| expected `&[i8]`, found `&[u8]`

// Test matching on a non-`[u8]` slice: the pattern has type `[u8]` if a slice is expected.
if let b"test" = *(&[] as &[i8]) {}
//~^ ERROR mismatched types
//~| expected `[i8]`, found `[u8]`

// Test matching on a non-`[u8;4]` array: the pattern has type `[u8;4]` if an array is expected.
if let b"test" = [()] {}
//~^ ERROR mismatched types
//~| expected `[(); 1]`, found `[u8; 4]`
if let b"test" = *b"this array is too long" {}
//~^ ERROR mismatched types
//~| expected an array with a size of 22, found one with a size of 4
}
52 changes: 52 additions & 0 deletions tests/ui/pattern/deref-patterns/byte-string-type-errors.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
error[E0308]: mismatched types
--> $DIR/byte-string-type-errors.rs:11:12
|
LL | if let b"test" = () {}
| ^^^^^^^ -- this expression has type `()`
| |
| expected `()`, found `&[u8; 4]`

error[E0308]: mismatched types
--> $DIR/byte-string-type-errors.rs:18:12
|
LL | if let b"test" = &[] as &[i8] {}
| ^^^^^^^ ------------ this expression has type `&[i8]`
| |
| expected `&[i8]`, found `&[u8]`
|
= note: expected reference `&[i8]`
found reference `&'static [u8]`

error[E0308]: mismatched types
--> $DIR/byte-string-type-errors.rs:23:12
|
LL | if let b"test" = *(&[] as &[i8]) {}
| ^^^^^^^ --------------- this expression has type `[i8]`
| |
| expected `[i8]`, found `[u8]`
|
= note: expected slice `[i8]`
found slice `[u8]`

error[E0308]: mismatched types
--> $DIR/byte-string-type-errors.rs:28:12
|
LL | if let b"test" = [()] {}
| ^^^^^^^ ---- this expression has type `[(); 1]`
| |
| expected `[(); 1]`, found `[u8; 4]`
|
= note: expected array `[(); 1]`
found array `[u8; 4]`

error[E0308]: mismatched types
--> $DIR/byte-string-type-errors.rs:31:12
|
LL | if let b"test" = *b"this array is too long" {}
| ^^^^^^^ -------------------------- this expression has type `[u8; 22]`
| |
| expected an array with a size of 22, found one with a size of 4

error: aborting due to 5 previous errors

For more information about this error, try `rustc --explain E0308`.
17 changes: 17 additions & 0 deletions tests/ui/pattern/deref-patterns/needs-gate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,21 @@ fn main() {
//~^ ERROR: mismatched types
_ => {}
}

// `deref_patterns` allows string and byte string literals to have non-ref types.
match *"test" {
"test" => {}
//~^ ERROR: mismatched types
_ => {}
}
match *b"test" {
b"test" => {}
//~^ ERROR: mismatched types
_ => {}
}
match *(b"test" as &[u8]) {
b"test" => {}
//~^ ERROR: mismatched types
_ => {}
}
}
26 changes: 25 additions & 1 deletion tests/ui/pattern/deref-patterns/needs-gate.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,31 @@ help: consider dereferencing to access the inner value using the Deref trait
LL | match *Box::new(0) {
| +

error: aborting due to 2 previous errors
error[E0308]: mismatched types
--> $DIR/needs-gate.rs:18:9
|
LL | match *"test" {
| ------- this expression has type `str`
LL | "test" => {}
| ^^^^^^ expected `str`, found `&str`

error[E0308]: mismatched types
--> $DIR/needs-gate.rs:23:9
|
LL | match *b"test" {
| -------- this expression has type `[u8; 4]`
LL | b"test" => {}
| ^^^^^^^ expected `[u8; 4]`, found `&[u8; 4]`

error[E0308]: mismatched types
--> $DIR/needs-gate.rs:28:9
|
LL | match *(b"test" as &[u8]) {
| ------------------- this expression has type `[u8]`
LL | b"test" => {}
| ^^^^^^^ expected `[u8]`, found `&[u8; 4]`

error: aborting due to 5 previous errors

Some errors have detailed explanations: E0308, E0658.
For more information about an error, try `rustc --explain E0308`.
66 changes: 66 additions & 0 deletions tests/ui/pattern/deref-patterns/strings.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
//@ run-pass
//! Test deref patterns using string and bytestring literals.

#![feature(deref_patterns)]
#![allow(incomplete_features)]

fn main() {
for (test_in, test_expect) in [("zero", 0), ("one", 1), ("two", 2)] {
// Test string literal patterns having type `str`.
let test_actual = match *test_in {
"zero" => 0,
"one" => 1,
_ => 2,
};
assert_eq!(test_actual, test_expect);

// Test string literals in explicit `deref!(_)` patterns.
let test_actual = match test_in.to_string() {
deref!("zero") => 0,
deref!("one") => 1,
_ => 2,
};
assert_eq!(test_actual, test_expect);
}

// Test that we can still mutate in the match arm after using a literal to test equality:
let mut test = "test".to_string();
if let deref!(s @ "test") = &mut test {
s.make_ascii_uppercase();
}
assert_eq!(test, "TEST");

for (test_in, test_expect) in [(b"0", 0), (b"1", 1), (b"2", 2)] {
// Test byte string literal patterns having type `[u8; N]`
let test_actual = match *test_in {
b"0" => 0,
b"1" => 1,
_ => 2,
};
assert_eq!(test_actual, test_expect);

// Test byte string literal patterns having type `[u8]`
let test_actual = match *(test_in as &[u8]) {
b"0" => 0,
b"1" => 1,
_ => 2,
};
assert_eq!(test_actual, test_expect);

// Test byte string literals used as arrays in explicit `deref!(_)` patterns.
let test_actual = match Box::new(*test_in) {
deref!(b"0") => 0,
deref!(b"1") => 1,
_ => 2,
};
assert_eq!(test_actual, test_expect);

// Test byte string literals used as slices in explicit `deref!(_)` patterns.
let test_actual = match test_in.to_vec() {
deref!(b"0") => 0,
deref!(b"1") => 1,
_ => 2,
};
assert_eq!(test_actual, test_expect);
}
}
8 changes: 2 additions & 6 deletions tests/ui/pattern/deref-patterns/typeck_fail.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,14 @@
#![allow(incomplete_features)]

fn main() {
// FIXME(deref_patterns): fails to typecheck because `"foo"` has type &str but deref creates a
// place of type `str`.
// FIXME(deref_patterns): fails to typecheck because string literal patterns don't peel
// references from the scrutinee.
match "foo".to_string() {
deref!("foo") => {}
//~^ ERROR: mismatched types
"foo" => {}
//~^ ERROR: mismatched types
_ => {}
}
match &"foo".to_string() {
deref!("foo") => {}
//~^ ERROR: mismatched types
"foo" => {}
//~^ ERROR: mismatched types
_ => {}
Expand Down
Loading
Loading