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 4 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, _)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

similarly, shouldn't we forward the mutability? Please add tests for this

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added a test: (diff) (playground). Forwarding the mutability of the scrutinee would make sense, but since the current stable behavior is to use the literal's mutability, that would also need to be gated. And if we do forward ref mutability, we'd probably want to do it for array references too. Interestingly, it doesn't look like there were any other tests preventing this from changing; nice catch! I've made it the first commit since it was existing behavior and is unchanged by this PR.

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
10 changes: 10 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,16 @@ 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`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's important to note that the valtrees of str and &str are the same

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done: (diff)

// This works because `str` and `&str` have the same valtree representation.
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
19 changes: 19 additions & 0 deletions tests/ui/pattern/byte-string-mutability-mismatch.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//! Byte string literal patterns use the mutability of the literal, rather than the mutability of
//! the pattern's scrutinee. Since byte string literals are always shared references, it's a
//! mismatch to use a byte string literal pattern to match on a mutable array or slice reference.
fn main() {
let mut val = [97u8, 10u8];
match &mut val {
b"a\n" => {},
//~^ ERROR mismatched types
//~| types differ in mutability
_ => {},
}
match &mut val[..] {
b"a\n" => {},
//~^ ERROR mismatched types
//~| types differ in mutability
_ => {},
}
}
25 changes: 25 additions & 0 deletions tests/ui/pattern/byte-string-mutability-mismatch.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
error[E0308]: mismatched types
--> $DIR/byte-string-mutability-mismatch.rs:8:9
|
LL | match &mut val {
| -------- this expression has type `&mut [u8; 2]`
LL | b"a\n" => {},
| ^^^^^^ types differ in mutability
|
= note: expected mutable reference `&mut _`
found reference `&'static _`

error[E0308]: mismatched types
--> $DIR/byte-string-mutability-mismatch.rs:14:10
|
LL | match &mut val[..] {
| ------------ this expression has type `&mut [u8]`
LL | b"a\n" => {},
| ^^^^^^ types differ in mutability
|
= note: expected mutable reference `&mut _`
found reference `&'static _`

error: aborting due to 2 previous errors

For more information about this error, try `rustc --explain E0308`.
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`.
Loading
Loading