Skip to content

Commit fe98130

Browse files
committed
match ergonomics for string and byte string literal patterns
1 parent e587901 commit fe98130

File tree

8 files changed

+223
-63
lines changed

8 files changed

+223
-63
lines changed

compiler/rustc_hir_typeck/src/pat.rs

Lines changed: 67 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -177,16 +177,20 @@ enum PeelKind {
177177
/// Only peel reference types. This is used for explicit `deref!(_)` patterns, which dereference
178178
/// any number of `&`/`&mut` references, plus a single smart pointer.
179179
ExplicitDerefPat,
180-
/// Implicitly peel any number of references, and if `deref_patterns` is enabled, smart pointer
181-
/// ADTs. In order to peel only as much as necessary for the pattern to match, the `until_adt`
182-
/// field contains the ADT def that the pattern is a constructor for, if applicable, so that we
183-
/// don't peel it. See [`ResolvedPat`] for more information.
184-
Implicit { until_adt: Option<DefId> },
180+
/// Implicitly peel references, and if `deref_patterns` is enabled, smart pointer ADTs.
181+
Implicit {
182+
/// The ADT the pattern is a constructor for, if applicable, so that we don't peel it. See
183+
/// [`ResolvedPat`] for more information.
184+
until_adt: Option<DefId>,
185+
/// The number of references at the head of the pattern's type, so we can leave that many
186+
/// untouched. This is `1` for string literals, and `0` for most patterns.
187+
pat_ref_layers: usize,
188+
},
185189
}
186190

187191
impl AdjustMode {
188192
const fn peel_until_adt(opt_adt_def: Option<DefId>) -> AdjustMode {
189-
AdjustMode::Peel { kind: PeelKind::Implicit { until_adt: opt_adt_def } }
193+
AdjustMode::Peel { kind: PeelKind::Implicit { until_adt: opt_adt_def, pat_ref_layers: 0 } }
190194
}
191195
const fn peel_all() -> AdjustMode {
192196
AdjustMode::peel_until_adt(None)
@@ -488,9 +492,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
488492
match pat.kind {
489493
// Peel off a `&` or `&mut` from the scrutinee type. See the examples in
490494
// `tests/ui/rfcs/rfc-2005-default-binding-mode`.
491-
_ if let AdjustMode::Peel { .. } = adjust_mode
495+
_ if let AdjustMode::Peel { kind: peel_kind } = adjust_mode
492496
&& pat.default_binding_modes
493-
&& let ty::Ref(_, inner_ty, inner_mutability) = *expected.kind() =>
497+
&& let ty::Ref(_, inner_ty, inner_mutability) = *expected.kind()
498+
&& self.should_peel_ref(peel_kind, expected) =>
494499
{
495500
debug!("inspecting {:?}", expected);
496501

@@ -665,21 +670,32 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
665670

666671
// String and byte-string literals result in types `&str` and `&[u8]` respectively.
667672
// All other literals result in non-reference types.
668-
// As a result, we allow `if let 0 = &&0 {}` but not `if let "foo" = &&"foo" {}`.
669-
//
670-
// Call `resolve_vars_if_possible` here for inline const blocks.
671-
PatKind::Expr(lt) => match self.resolve_vars_if_possible(self.check_pat_expr_unadjusted(lt)).kind() {
672-
ty::Ref(..) => AdjustMode::Pass,
673-
_ => {
674-
// Path patterns have already been handled, and inline const blocks currently
675-
// aren't possible to write, so any handling for them would be untested.
676-
if cfg!(debug_assertions)
677-
&& self.tcx.features().deref_patterns()
678-
&& !matches!(lt.kind, PatExprKind::Lit { .. })
679-
{
680-
span_bug!(lt.span, "FIXME(deref_patterns): adjust mode unimplemented for {:?}", lt.kind);
673+
// As a result, we allow `if let 0 = &&0 {}` but not `if let "foo" = &&"foo" {}` unless
674+
// `deref_patterns` is enabled.
675+
PatKind::Expr(lt) => {
676+
// Path patterns have already been handled, and inline const blocks currently
677+
// aren't possible to write, so any handling for them would be untested.
678+
if cfg!(debug_assertions)
679+
&& self.tcx.features().deref_patterns()
680+
&& !matches!(lt.kind, PatExprKind::Lit { .. })
681+
{
682+
span_bug!(lt.span, "FIXME(deref_patterns): adjust mode unimplemented for {:?}", lt.kind);
683+
}
684+
// Call `resolve_vars_if_possible` here for inline const blocks.
685+
let lit_ty = self.resolve_vars_if_possible(self.check_pat_expr_unadjusted(lt));
686+
// If `deref_patterns` is enabled, allow `if let "foo" = &&"foo" {}`.
687+
if self.tcx.features().deref_patterns() {
688+
let mut peeled_ty = lit_ty;
689+
let mut pat_ref_layers = 0;
690+
while let ty::Ref(_, inner_ty, mutbl) = *peeled_ty.kind() {
691+
// We rely on references at the head of constants being immutable.
692+
debug_assert!(mutbl.is_not());
693+
pat_ref_layers += 1;
694+
peeled_ty = inner_ty;
681695
}
682-
AdjustMode::peel_all()
696+
AdjustMode::Peel { kind: PeelKind::Implicit { until_adt: None, pat_ref_layers } }
697+
} else {
698+
if lit_ty.is_ref() { AdjustMode::Pass } else { AdjustMode::peel_all() }
683699
}
684700
},
685701

@@ -705,6 +721,35 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
705721
}
706722
}
707723

724+
/// Assuming `expected` is a reference type, determine whether to peel it before matching.
725+
fn should_peel_ref(&self, peel_kind: PeelKind, mut expected: Ty<'tcx>) -> bool {
726+
debug_assert!(expected.is_ref());
727+
let pat_ref_layers = match peel_kind {
728+
PeelKind::ExplicitDerefPat => 0,
729+
PeelKind::Implicit { pat_ref_layers, .. } => pat_ref_layers,
730+
};
731+
732+
// Most patterns don't have reference types, so we'll want to peel all references from the
733+
// scrutinee before matching. To optimize for the common case, return early.
734+
if pat_ref_layers == 0 {
735+
return true;
736+
}
737+
debug_assert!(
738+
self.tcx.features().deref_patterns(),
739+
"Peeling for patterns with reference types is gated by `deref_patterns`."
740+
);
741+
742+
// If the pattern has as many or more layers of reference as the expected type, we can match
743+
// without peeling more, *unless* we find a smart pointer that we also need to peel.
744+
// TODO: always peel `&mut`
745+
let mut expected_ref_layers = 0;
746+
while let ty::Ref(_, inner_ty, _) = *expected.kind() {
747+
expected_ref_layers += 1;
748+
expected = inner_ty;
749+
}
750+
pat_ref_layers < expected_ref_layers || self.should_peel_smart_pointer(peel_kind, expected)
751+
}
752+
708753
/// Determine whether `expected` is a smart pointer type that should be peeled before matching.
709754
fn should_peel_smart_pointer(&self, peel_kind: PeelKind, expected: Ty<'tcx>) -> bool {
710755
// Explicit `deref!(_)` patterns match against smart pointers; don't peel in that case.
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
//@ revisions: stable deref_patterns
2+
//@[deref_patterns] check-pass
3+
//! `deref_patterns` allows string and byte string literal patterns to implicitly peel references
4+
//! and smart pointers from the scrutinee before matching. Since strings and byte strings themselves
5+
//! have reference types, we need to make sure we don't peel too much. By leaving the type of the
6+
//! match scrutinee partially uninferred, these tests make sure we only peel as much as needed in
7+
//! order to match. In particular, when peeling isn't needed, the results should be the same was
8+
//! we'd get without `deref_patterns` enabled.
9+
10+
#![cfg_attr(deref_patterns, feature(deref_patterns))]
11+
#![cfg_attr(deref_patterns, expect(incomplete_features))]
12+
13+
fn uninferred<T>() -> T { unimplemented!() }
14+
15+
// Assert type equality without allowing coercions.
16+
trait Is<T> {}
17+
impl<T> Is<T> for T {}
18+
fn has_type<T>(_: impl Is<T>) {}
19+
20+
fn main() {
21+
// We don't need to peel anything to unify the type of `x` with `&str`, so `x: &str`.
22+
let x = uninferred();
23+
if let "..." = x {}
24+
has_type::<&str>(x);
25+
26+
// We don't need to peel anything to unify the type of `&x` with `&[u8; 3]`, so `x: [u8; 3]`.
27+
let x = uninferred();
28+
if let b"..." = &x {}
29+
has_type::<[u8; 3]>(x);
30+
31+
// Peeling a single `&` lets us unify the type of `&x` with `&[u8; 3]`, giving `x: [u8; 3]`.
32+
let x = uninferred();
33+
if let b"..." = &&x {}
34+
//[stable]~^ ERROR: mismatched types
35+
has_type::<[u8; 3]>(x);
36+
37+
// We have to peel both the `&` and the box before unifying the type of `x` with `&str`.
38+
let x = uninferred();
39+
if let "..." = &Box::new(x) {}
40+
//[stable]~^ ERROR mismatched types
41+
has_type::<&str>(x);
42+
43+
// After peeling the box, we can unify the type of `&x` with `&[u8; 3]`, giving `x: [u8; 3]`.
44+
let x = uninferred();
45+
if let b"..." = Box::new(&x) {}
46+
//[stable]~^ ERROR mismatched types
47+
has_type::<[u8; 3]>(x);
48+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
error[E0308]: mismatched types
2+
--> $DIR/const-pats-do-not-mislead-inference.rs:33:12
3+
|
4+
LL | if let b"..." = &&x {}
5+
| ^^^^^^ --- this expression has type `&&_`
6+
| |
7+
| expected `&&_`, found `&[u8; 3]`
8+
|
9+
= note: expected reference `&&_`
10+
found reference `&'static [u8; 3]`
11+
12+
error[E0308]: mismatched types
13+
--> $DIR/const-pats-do-not-mislead-inference.rs:39:12
14+
|
15+
LL | if let "..." = &Box::new(x) {}
16+
| ^^^^^ ------------ this expression has type `&Box<_>`
17+
| |
18+
| expected `&Box<_>`, found `&str`
19+
|
20+
= note: expected reference `&Box<_>`
21+
found reference `&'static str`
22+
help: consider dereferencing to access the inner value using the Deref trait
23+
|
24+
LL | if let "..." = &*Box::new(x) {}
25+
| +
26+
27+
error[E0308]: mismatched types
28+
--> $DIR/const-pats-do-not-mislead-inference.rs:45:12
29+
|
30+
LL | if let b"..." = Box::new(&x) {}
31+
| ^^^^^^ ------------ this expression has type `Box<&_>`
32+
| |
33+
| expected `Box<&_>`, found `&[u8; 3]`
34+
|
35+
= note: expected struct `Box<&_>`
36+
found reference `&'static [u8; 3]`
37+
help: consider dereferencing to access the inner value using the Deref trait
38+
|
39+
LL | if let b"..." = *Box::new(&x) {}
40+
| +
41+
42+
error: aborting due to 3 previous errors
43+
44+
For more information about this error, try `rustc --explain E0308`.

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,21 @@ fn main() {
2929
//~^ ERROR: mismatched types
3030
_ => {}
3131
}
32+
33+
// `deref_patterns` allows string and byte string patterns to implicitly peel references.
34+
match &"str" {
35+
"str" => {}
36+
//~^ ERROR: mismatched types
37+
_ => {}
38+
}
39+
match &b"str" {
40+
b"str" => {}
41+
//~^ ERROR: mismatched types
42+
_ => {}
43+
}
44+
match "str".to_owned() {
45+
"str" => {}
46+
//~^ ERROR: mismatched types
47+
_ => {}
48+
}
3249
}

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

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,37 @@ LL | match *(b"test" as &[u8]) {
4747
LL | b"test" => {}
4848
| ^^^^^^^ expected `[u8]`, found `&[u8; 4]`
4949

50-
error: aborting due to 5 previous errors
50+
error[E0308]: mismatched types
51+
--> $DIR/needs-gate.rs:35:9
52+
|
53+
LL | match &"str" {
54+
| ------ this expression has type `&&str`
55+
LL | "str" => {}
56+
| ^^^^^ expected `&&str`, found `&str`
57+
|
58+
= note: expected reference `&&_`
59+
found reference `&'static _`
60+
61+
error[E0308]: mismatched types
62+
--> $DIR/needs-gate.rs:40:9
63+
|
64+
LL | match &b"str" {
65+
| ------- this expression has type `&&[u8; 3]`
66+
LL | b"str" => {}
67+
| ^^^^^^ expected `&&[u8; 3]`, found `&[u8; 3]`
68+
|
69+
= note: expected reference `&&_`
70+
found reference `&'static _`
71+
72+
error[E0308]: mismatched types
73+
--> $DIR/needs-gate.rs:45:9
74+
|
75+
LL | match "str".to_owned() {
76+
| ---------------- this expression has type `String`
77+
LL | "str" => {}
78+
| ^^^^^ expected `String`, found `&str`
79+
80+
error: aborting due to 8 previous errors
5181

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

tests/ui/pattern/deref-patterns/strings.rs

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,18 @@ fn main() {
1414
};
1515
assert_eq!(test_actual, test_expect);
1616

17-
// Test string literals in explicit `deref!(_)` patterns.
17+
// Test string literals in deref patterns.
1818
let test_actual = match test_in.to_string() {
1919
deref!("zero") => 0,
20-
deref!("one") => 1,
20+
"one" => 1,
21+
_ => 2,
22+
};
23+
assert_eq!(test_actual, test_expect);
24+
25+
// Test peeling references in addition to smart pointers.
26+
let test_actual = match &test_in.to_string() {
27+
deref!("zero") => 0,
28+
"one" => 1,
2129
_ => 2,
2230
};
2331
assert_eq!(test_actual, test_expect);
@@ -47,18 +55,18 @@ fn main() {
4755
};
4856
assert_eq!(test_actual, test_expect);
4957

50-
// Test byte string literals used as arrays in explicit `deref!(_)` patterns.
58+
// Test byte string literals used as arrays in deref patterns.
5159
let test_actual = match Box::new(*test_in) {
5260
deref!(b"0") => 0,
53-
deref!(b"1") => 1,
61+
b"1" => 1,
5462
_ => 2,
5563
};
5664
assert_eq!(test_actual, test_expect);
5765

58-
// Test byte string literals used as slices in explicit `deref!(_)` patterns.
66+
// Test byte string literals used as slices in deref patterns.
5967
let test_actual = match test_in.to_vec() {
6068
deref!(b"0") => 0,
61-
deref!(b"1") => 1,
69+
b"1" => 1,
6270
_ => 2,
6371
};
6472
assert_eq!(test_actual, test_expect);

tests/ui/pattern/deref-patterns/typeck_fail.rs

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,6 @@
22
#![allow(incomplete_features)]
33

44
fn main() {
5-
// FIXME(deref_patterns): fails to typecheck because string literal patterns don't peel
6-
// references from the scrutinee.
7-
match "foo".to_string() {
8-
"foo" => {}
9-
//~^ ERROR: mismatched types
10-
_ => {}
11-
}
12-
match &"foo".to_string() {
13-
"foo" => {}
14-
//~^ ERROR: mismatched types
15-
_ => {}
16-
}
17-
185
// Make sure we don't try implicitly dereferncing any ADT.
196
match Some(0) {
207
Ok(0) => {}
Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,5 @@
11
error[E0308]: mismatched types
2-
--> $DIR/typeck_fail.rs:8:9
3-
|
4-
LL | match "foo".to_string() {
5-
| ----------------- this expression has type `String`
6-
LL | "foo" => {}
7-
| ^^^^^ expected `String`, found `&str`
8-
9-
error[E0308]: mismatched types
10-
--> $DIR/typeck_fail.rs:13:9
11-
|
12-
LL | match &"foo".to_string() {
13-
| ------------------ this expression has type `&String`
14-
LL | "foo" => {}
15-
| ^^^^^ expected `&String`, found `&str`
16-
|
17-
= note: expected reference `&String`
18-
found reference `&'static str`
19-
20-
error[E0308]: mismatched types
21-
--> $DIR/typeck_fail.rs:20:9
2+
--> $DIR/typeck_fail.rs:7:9
223
|
234
LL | match Some(0) {
245
| ------- this expression has type `Option<{integer}>`
@@ -28,6 +9,6 @@ LL | Ok(0) => {}
289
= note: expected enum `Option<{integer}>`
2910
found enum `Result<_, _>`
3011

31-
error: aborting due to 3 previous errors
12+
error: aborting due to 1 previous error
3213

3314
For more information about this error, try `rustc --explain E0308`.

0 commit comments

Comments
 (0)