Skip to content

deref_patterns: let string and byte string literal patterns peel references and smart pointers before matching #140658

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

Merged
merged 4 commits into from
May 6, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
always peel &mut, to allow matching on &mut str
  • Loading branch information
dianne committed May 5, 2025
commit 17bb4bbc86c2078e8ca09e2fdf6fd380094be6d6
13 changes: 10 additions & 3 deletions compiler/rustc_hir_typeck/src/pat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -740,10 +740,17 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
);

// If the pattern has as many or more layers of reference as the expected type, we can match
// without peeling more, *unless* we find a smart pointer that we also need to peel.
// TODO: always peel `&mut`
// without peeling more, unless we find a smart pointer or `&mut` that we also need to peel.
// We don't treat `&` and `&mut` as interchangeable, but by peeling `&mut`s before matching,
// we can still, e.g., match on a `&mut str` with a string literal pattern. This is because
// string literal patterns may be used where `str` is expected.
let mut expected_ref_layers = 0;
while let ty::Ref(_, inner_ty, _) = *expected.kind() {
while let ty::Ref(_, inner_ty, mutbl) = *expected.kind() {
if mutbl.is_mut() {
// Mutable references can't be in the final value of constants, thus they can't be
// at the head of their types, thus we should always peel `&mut`.
return true;
}
expected_ref_layers += 1;
expected = inner_ty;
}
Expand Down
19 changes: 19 additions & 0 deletions tests/ui/pattern/deref-patterns/byte-string-type-errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,23 @@ fn main() {
if let b"test" = *b"this array is too long" {}
//~^ ERROR mismatched types
//~| NOTE expected an array with a size of 22, found one with a size of 4

// Test matching on `&mut T`: we peel the `&mut` before applying the usual special cases.
// No special cases apply to `()`, so the "found" type is the type of the literal.
if let b"test" = &mut () {}
//~^ ERROR mismatched types
//~| NOTE expected `()`, found `&[u8; 4]`

// If the pointee is an array or slice, the usual special cases will apply to the "found" type:
if let b"test" = &mut [] as &mut [i8] {}
//~^ ERROR mismatched type
//~| NOTE expected `[i8]`, found `[u8]`

if let b"test" = &mut [()] {}
//~^ ERROR mismatched types
//~| NOTE expected `[(); 1]`, found `[u8; 4]`

if let b"test" = &mut *b"this array is too long" {}
//~^ ERROR mismatched type
//~| NOTE expected an array with a size of 22, found one with a size of 4
}
40 changes: 39 additions & 1 deletion tests/ui/pattern/deref-patterns/byte-string-type-errors.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,44 @@ LL | if let b"test" = *b"this array is too long" {}
| |
| expected an array with a size of 22, found one with a size of 4

error: aborting due to 5 previous errors
error[E0308]: mismatched types
--> $DIR/byte-string-type-errors.rs:39:12
|
LL | if let b"test" = &mut () {}
| ^^^^^^^ ------- this expression has type `&mut ()`
| |
| expected `()`, found `&[u8; 4]`

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

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

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

error: aborting due to 9 previous errors

For more information about this error, try `rustc --explain E0308`.
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,10 @@ fn main() {
if let b"..." = Box::new(&x) {}
//[stable]~^ ERROR mismatched types
has_type::<[u8; 3]>(x);

// `&` and `&mut` aren't interchangeable: `&mut`s need to be peeled before unifying, like boxes:
let mut x = uninferred();
if let "..." = &mut x {}
//[stable]~^ ERROR mismatched types
has_type::<&str>(x);
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,17 @@ help: consider dereferencing to access the inner value using the Deref trait
LL | if let b"..." = *Box::new(&x) {}
| +

error: aborting due to 3 previous errors
error[E0308]: mismatched types
--> $DIR/const-pats-do-not-mislead-inference.rs:51:12
|
LL | if let "..." = &mut x {}
| ^^^^^ ------ this expression has type `&mut _`
| |
| types differ in mutability
|
= note: expected mutable reference `&mut _`
found reference `&'static str`

error: aborting due to 4 previous errors

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

// `deref_patterns` allows string and byte string patterns to match on mutable references.
// See also `tests/ui/pattern/byte-string-mutability-mismatch.rs`.
if let "str" = &mut *"str".to_string() {}
//~^ ERROR mismatched types
if let b"str" = &mut b"str".clone() {}
//~^ ERROR mismatched types
if let b"str" = &mut b"str".clone()[..] {}
//~^ ERROR mismatched types
}
35 changes: 34 additions & 1 deletion tests/ui/pattern/deref-patterns/needs-gate.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,40 @@ LL | match "str".to_owned() {
LL | "str" => {}
| ^^^^^ expected `String`, found `&str`

error: aborting due to 8 previous errors
error[E0308]: mismatched types
--> $DIR/needs-gate.rs:52:12
|
LL | if let "str" = &mut *"str".to_string() {}
| ^^^^^ ----------------------- this expression has type `&mut str`
| |
| types differ in mutability
|
= note: expected mutable reference `&mut _`
found reference `&'static _`

error[E0308]: mismatched types
--> $DIR/needs-gate.rs:54:12
|
LL | if let b"str" = &mut b"str".clone() {}
| ^^^^^^ ------------------- this expression has type `&mut [u8; 3]`
| |
| types differ in mutability
|
= note: expected mutable reference `&mut _`
found reference `&'static _`

error[E0308]: mismatched types
--> $DIR/needs-gate.rs:56:12
|
LL | if let b"str" = &mut b"str".clone()[..] {}
| ^^^^^^ ----------------------- this expression has type `&mut [u8]`
| |
| types differ in mutability
|
= note: expected mutable reference `&mut _`
found reference `&'static _`

error: aborting due to 11 previous errors

Some errors have detailed explanations: E0308, E0658.
For more information about an error, try `rustc --explain E0308`.
24 changes: 24 additions & 0 deletions tests/ui/pattern/deref-patterns/strings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ fn main() {
};
assert_eq!(test_actual, test_expect);

// Test matching on `&mut str`.
let test_actual = match &mut *test_in.to_string() {
"zero" => 0,
"one" => 1,
_ => 2,
};
assert_eq!(test_actual, test_expect);

// Test string literals in deref patterns.
let test_actual = match test_in.to_string() {
deref!("zero") => 0,
Expand Down Expand Up @@ -55,6 +63,22 @@ fn main() {
};
assert_eq!(test_actual, test_expect);

// Test matching on `&mut [u8; N]`.
let test_actual = match &mut test_in.clone() {
b"0" => 0,
b"1" => 1,
_ => 2,
};
assert_eq!(test_actual, test_expect);

// Test matching on `&mut [u8]`.
let test_actual = match &mut test_in.clone()[..] {
b"0" => 0,
b"1" => 1,
_ => 2,
};
assert_eq!(test_actual, test_expect);

// Test byte string literals used as arrays in deref patterns.
let test_actual = match Box::new(*test_in) {
deref!(b"0") => 0,
Expand Down