Skip to content

Commit e4b7b3d

Browse files
committed
pattern typing for immutable implicit deref patterns
1 parent cb6c499 commit e4b7b3d

File tree

10 files changed

+272
-30
lines changed

10 files changed

+272
-30
lines changed

compiler/rustc_hir_typeck/src/pat.rs

Lines changed: 89 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -162,12 +162,27 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
162162
/// Mode for adjusting the expected type and binding mode.
163163
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
164164
enum AdjustMode {
165-
/// Peel off all immediate reference types.
166-
Peel,
165+
/// Peel off all immediate reference types. If the `deref_patterns` feature is enabled, this
166+
/// also peels smart pointer ADTs.
167+
Peel { kind: PeelKind },
167168
/// Pass on the input binding mode and expected type.
168169
Pass,
169170
}
170171

172+
/// Restrictions on what types to peel when adjusting the expected type and binding mode.
173+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
174+
enum PeelKind {
175+
/// Only peel reference types. This is used for explicit `deref!(_)` patterns, which dereference
176+
/// any number of `&`/`&mut` references, plus a single smart pointer.
177+
ExplicitDerefPat,
178+
/// Implicitly peel any number of references, and if `deref_patterns` is enabled, smart pointer
179+
/// ADTs. In order to peel only as much as necessary for the pattern to match, the `until_adt`
180+
/// field contains the ADT def that the pattern is a constructor for, if applicable, so that we
181+
/// don't peel it. See [`ResolvedPat`] for more information.
182+
// TODO: add `ResolvedPat` and `until_adt`.
183+
Implicit,
184+
}
185+
171186
/// `ref mut` bindings (explicit or match-ergonomics) are not allowed behind an `&` reference.
172187
/// Normally, the borrow checker enforces this, but for (currently experimental) match ergonomics,
173188
/// we track this when typing patterns for two purposes:
@@ -390,7 +405,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
390405
}
391406

392407
// Resolve type if needed.
393-
let expected = if let AdjustMode::Peel = adjust_mode
408+
let expected = if let AdjustMode::Peel { .. } = adjust_mode
394409
&& pat.default_binding_modes
395410
{
396411
self.try_structurally_resolve_type(pat.span, expected)
@@ -403,7 +418,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
403418
match pat.kind {
404419
// Peel off a `&` or `&mut` from the scrutinee type. See the examples in
405420
// `tests/ui/rfcs/rfc-2005-default-binding-mode`.
406-
_ if let AdjustMode::Peel = adjust_mode
421+
_ if let AdjustMode::Peel { .. } = adjust_mode
407422
&& pat.default_binding_modes
408423
&& let ty::Ref(_, inner_ty, inner_mutability) = *expected.kind() =>
409424
{
@@ -443,6 +458,45 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
443458
// Recurse with the new expected type.
444459
self.check_pat_inner(pat, opt_path_res, adjust_mode, inner_ty, new_pat_info)
445460
}
461+
// If `deref_patterns` is enabled, peel a smart pointer from the scrutinee type. See the
462+
// examples in `tests/ui/pattern/deref_patterns/`.
463+
_ if self.tcx.features().deref_patterns()
464+
&& let AdjustMode::Peel { kind: PeelKind::Implicit } = adjust_mode
465+
&& pat.default_binding_modes
466+
// For simplicity, only apply overloaded derefs if `expected` is a known ADT.
467+
// FIXME(deref_patterns): we'll get better diagnostics for users trying to
468+
// implicitly deref generics if we allow them here, but primitives, tuples, and
469+
// inference vars definitely should be stopped. Figure out what makes most sense.
470+
// TODO: stop peeling if the pattern is a constructor for the scrutinee type
471+
&& expected.is_adt()
472+
// At this point, the pattern isn't able to match `expected` without peeling. Check
473+
// that it implements `Deref` before assuming it's a smart pointer, to get a normal
474+
// type error instead of a missing impl error if not. This only checks for `Deref`,
475+
// not `DerefPure`: we require that too, but we want a trait error if it's missing.
476+
&& let Some(deref_trait) = self.tcx.lang_items().deref_trait()
477+
&& self
478+
.type_implements_trait(deref_trait, [expected], self.param_env)
479+
.may_apply() =>
480+
{
481+
debug!("scrutinee ty {expected:?} is a smart pointer, inserting overloaded deref");
482+
// The scrutinee is a smart pointer; implicitly dereference it. This adds a
483+
// requirement that `expected: DerefPure`.
484+
let inner_ty = self.deref_pat_target(pat.span, expected);
485+
// Once we've checked `pat`, we'll add a `DerefMut` bound if it contains any
486+
// `ref mut` bindings. TODO: implement that, then reference here.
487+
488+
// Preserve the smart pointer type for THIR lowering and upvar analysis.
489+
self.typeck_results
490+
.borrow_mut()
491+
.pat_adjustments_mut()
492+
.entry(pat.hir_id)
493+
.or_default()
494+
.push(PatAdjustment { kind: PatAdjust::OverloadedDeref, source: expected });
495+
496+
// Recurse, using the old pat info to keep `current_depth` to its old value.
497+
// Peeling smart pointers does not update the default binding mode.
498+
self.check_pat_inner(pat, opt_path_res, adjust_mode, inner_ty, old_pat_info)
499+
}
446500
PatKind::Missing | PatKind::Wild | PatKind::Err(_) => expected,
447501
// We allow any type here; we ensure that the type is uninhabited during match checking.
448502
PatKind::Never => expected,
@@ -505,12 +559,15 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
505559
PatKind::Struct(..)
506560
| PatKind::TupleStruct(..)
507561
| PatKind::Tuple(..)
508-
| PatKind::Box(_)
509-
| PatKind::Deref(_)
510562
| PatKind::Range(..)
511-
| PatKind::Slice(..) => AdjustMode::Peel,
563+
| PatKind::Slice(..) => AdjustMode::Peel { kind: PeelKind::Implicit },
564+
// When checking an explicit deref pattern, only peel reference types.
565+
// FIXME(deref_patterns): If box patterns and deref patterns need to coexist, box
566+
// patterns may want `PeelKind::Implicit`, stopping on encountering a box.
567+
| PatKind::Box(_)
568+
| PatKind::Deref(_) => AdjustMode::Peel { kind: PeelKind::ExplicitDerefPat },
512569
// A never pattern behaves somewhat like a literal or unit variant.
513-
PatKind::Never => AdjustMode::Peel,
570+
PatKind::Never => AdjustMode::Peel { kind: PeelKind::Implicit },
514571
PatKind::Expr(PatExpr { kind: PatExprKind::Path(_), .. }) => match opt_path_res.unwrap() {
515572
// These constants can be of a reference type, e.g. `const X: &u8 = &0;`.
516573
// Peeling the reference types too early will cause type checking failures.
@@ -520,7 +577,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
520577
// could successfully compile. The former being `Self` requires a unit struct.
521578
// In either case, and unlike constants, the pattern itself cannot be
522579
// a reference type wherefore peeling doesn't give up any expressiveness.
523-
_ => AdjustMode::Peel,
580+
_ => AdjustMode::Peel { kind: PeelKind::Implicit },
524581
},
525582

526583
// String and byte-string literals result in types `&str` and `&[u8]` respectively.
@@ -530,7 +587,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
530587
// Call `resolve_vars_if_possible` here for inline const blocks.
531588
PatKind::Expr(lt) => match self.resolve_vars_if_possible(self.check_pat_expr_unadjusted(lt)).kind() {
532589
ty::Ref(..) => AdjustMode::Pass,
533-
_ => AdjustMode::Peel,
590+
_ => AdjustMode::Peel { kind: PeelKind::Implicit },
534591
},
535592

536593
// Ref patterns are complicated, we handle them in `check_pat_ref`.
@@ -2256,38 +2313,43 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
22562313
expected: Ty<'tcx>,
22572314
pat_info: PatInfo<'tcx>,
22582315
) -> Ty<'tcx> {
2259-
let tcx = self.tcx;
2260-
// Register a `DerefPure` bound, which is required by all `deref!()` pats.
2261-
self.register_bound(
2262-
expected,
2263-
tcx.require_lang_item(hir::LangItem::DerefPure, Some(span)),
2264-
self.misc(span),
2265-
);
2266-
// <expected as Deref>::Target
2267-
let ty = Ty::new_projection(
2268-
tcx,
2269-
tcx.require_lang_item(hir::LangItem::DerefTarget, Some(span)),
2270-
[expected],
2271-
);
2272-
let ty = self.normalize(span, ty);
2273-
let ty = self.try_structurally_resolve_type(span, ty);
2274-
self.check_pat(inner, ty, pat_info);
2316+
let target_ty = self.deref_pat_target(span, expected);
2317+
self.check_pat(inner, target_ty, pat_info);
22752318

22762319
// Check if the pattern has any `ref mut` bindings, which would require
22772320
// `DerefMut` to be emitted in MIR building instead of just `Deref`.
22782321
// We do this *after* checking the inner pattern, since we want to make
22792322
// sure to apply any match-ergonomics adjustments.
2323+
// TODO: move this to a separate definition to share it with implicit deref pats
22802324
if self.typeck_results.borrow().pat_has_ref_mut_binding(inner) {
22812325
self.register_bound(
22822326
expected,
2283-
tcx.require_lang_item(hir::LangItem::DerefMut, Some(span)),
2327+
self.tcx.require_lang_item(hir::LangItem::DerefMut, Some(span)),
22842328
self.misc(span),
22852329
);
22862330
}
22872331

22882332
expected
22892333
}
22902334

2335+
fn deref_pat_target(&self, span: Span, source_ty: Ty<'tcx>) -> Ty<'tcx> {
2336+
// Register a `DerefPure` bound, which is required by all `deref!()` pats.
2337+
let tcx = self.tcx;
2338+
self.register_bound(
2339+
source_ty,
2340+
tcx.require_lang_item(hir::LangItem::DerefPure, Some(span)),
2341+
self.misc(span),
2342+
);
2343+
// The expected type for the deref pat's inner pattern is `<expected as Deref>::Target`.
2344+
let target_ty = Ty::new_projection(
2345+
tcx,
2346+
tcx.require_lang_item(hir::LangItem::DerefTarget, Some(span)),
2347+
[source_ty],
2348+
);
2349+
let target_ty = self.normalize(span, target_ty);
2350+
self.try_structurally_resolve_type(span, target_ty)
2351+
}
2352+
22912353
// Precondition: Pat is Ref(inner)
22922354
fn check_pat_ref(
22932355
&self,

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

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
//@ revisions: explicit implicit
12
//@ run-pass
23
#![feature(deref_patterns)]
34
#![allow(incomplete_features)]
45

6+
#[cfg(explicit)]
57
fn simple_vec(vec: Vec<u32>) -> u32 {
68
match vec {
79
deref!([]) => 100,
@@ -13,6 +15,19 @@ fn simple_vec(vec: Vec<u32>) -> u32 {
1315
}
1416
}
1517

18+
#[cfg(implicit)]
19+
fn simple_vec(vec: Vec<u32>) -> u32 {
20+
match vec {
21+
[] => 100,
22+
[x] if x == 4 => x + 4,
23+
[x] => x,
24+
[1, x] => x + 200,
25+
deref!(ref slice) => slice.iter().sum(),
26+
_ => 2000,
27+
}
28+
}
29+
30+
#[cfg(explicit)]
1631
fn nested_vec(vecvec: Vec<Vec<u32>>) -> u32 {
1732
match vecvec {
1833
deref!([]) => 0,
@@ -24,6 +39,18 @@ fn nested_vec(vecvec: Vec<Vec<u32>>) -> u32 {
2439
}
2540
}
2641

42+
#[cfg(implicit)]
43+
fn nested_vec(vecvec: Vec<Vec<u32>>) -> u32 {
44+
match vecvec {
45+
[] => 0,
46+
[[x]] => x,
47+
[[0, x] | [1, x]] => x,
48+
[ref x] => x.iter().sum(),
49+
[[], [1, x, y]] => y - x,
50+
_ => 2000,
51+
}
52+
}
53+
2754
fn ref_mut(val: u32) -> u32 {
2855
let mut b = Box::new(0u32);
2956
match &mut b {

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1+
//@ revisions: explicit implicit
12
//@ run-pass
23
// Test the execution of deref patterns.
34
#![feature(deref_patterns)]
45
#![allow(incomplete_features)]
56

7+
#[cfg(explicit)]
68
fn branch(vec: Vec<u32>) -> u32 {
79
match vec {
810
deref!([]) => 0,
@@ -12,6 +14,17 @@ fn branch(vec: Vec<u32>) -> u32 {
1214
}
1315
}
1416

17+
#[cfg(implicit)]
18+
fn branch(vec: Vec<u32>) -> u32 {
19+
match vec {
20+
[] => 0,
21+
[1, _, 3] => 1,
22+
[2, ..] => 2,
23+
_ => 1000,
24+
}
25+
}
26+
27+
#[cfg(explicit)]
1528
fn nested(vec: Vec<Vec<u32>>) -> u32 {
1629
match vec {
1730
deref!([deref!([]), ..]) => 1,
@@ -20,6 +33,15 @@ fn nested(vec: Vec<Vec<u32>>) -> u32 {
2033
}
2134
}
2235

36+
#[cfg(implicit)]
37+
fn nested(vec: Vec<Vec<u32>>) -> u32 {
38+
match vec {
39+
[[], ..] => 1,
40+
[[0, ..], [1, ..]] => 2,
41+
_ => 1000,
42+
}
43+
}
44+
2345
fn main() {
2446
assert!(matches!(Vec::<u32>::new(), deref!([])));
2547
assert!(matches!(vec![1], deref!([1])));

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,22 @@ fn cant_move_out_rc(rc: Rc<Struct>) -> Struct {
2121
}
2222
}
2323

24+
struct Container(Struct);
25+
26+
fn cant_move_out_box_implicit(b: Box<Container>) -> Struct {
27+
match b {
28+
//~^ ERROR: cannot move out of a shared reference
29+
Container(x) => x,
30+
_ => unreachable!(),
31+
}
32+
}
33+
34+
fn cant_move_out_rc_implicit(rc: Rc<Container>) -> Struct {
35+
match rc {
36+
//~^ ERROR: cannot move out of a shared reference
37+
Container(x) => x,
38+
_ => unreachable!(),
39+
}
40+
}
41+
2442
fn main() {}

tests/ui/pattern/deref-patterns/cant_move_out_of_pattern.stderr

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,40 @@ help: consider borrowing the pattern binding
3232
LL | deref!(ref x) => x,
3333
| +++
3434

35-
error: aborting due to 2 previous errors
35+
error[E0507]: cannot move out of a shared reference
36+
--> $DIR/cant_move_out_of_pattern.rs:27:11
37+
|
38+
LL | match b {
39+
| ^
40+
LL |
41+
LL | Container(x) => x,
42+
| -
43+
| |
44+
| data moved here
45+
| move occurs because `x` has type `Struct`, which does not implement the `Copy` trait
46+
|
47+
help: consider borrowing the pattern binding
48+
|
49+
LL | Container(ref x) => x,
50+
| +++
51+
52+
error[E0507]: cannot move out of a shared reference
53+
--> $DIR/cant_move_out_of_pattern.rs:35:11
54+
|
55+
LL | match rc {
56+
| ^^
57+
LL |
58+
LL | Container(x) => x,
59+
| -
60+
| |
61+
| data moved here
62+
| move occurs because `x` has type `Struct`, which does not implement the `Copy` trait
63+
|
64+
help: consider borrowing the pattern binding
65+
|
66+
LL | Container(ref x) => x,
67+
| +++
68+
69+
error: aborting due to 4 previous errors
3670

3771
For more information about this error, try `rustc --explain E0507`.

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,26 +10,32 @@ fn main() {
1010
let vec: Vec<u32> = Vec::new();
1111
match vec {
1212
deref!([..]) => {}
13+
[..] => {}
1314
_ => {}
1415
}
1516
match Box::new(true) {
1617
deref!(true) => {}
18+
true => {}
1719
_ => {}
1820
}
1921
match &Box::new(true) {
2022
deref!(true) => {}
23+
true => {}
2124
_ => {}
2225
}
2326
match &Rc::new(0) {
2427
deref!(1..) => {}
28+
1.. => {}
2529
_ => {}
2630
}
2731
let _: &Struct = match &Rc::new(Struct) {
2832
deref!(x) => x,
33+
Struct => &Struct,
2934
_ => unreachable!(),
3035
};
3136
let _: &[Struct] = match &Rc::new(vec![Struct]) {
3237
deref!(deref!(x)) => x,
38+
[Struct] => &[Struct],
3339
_ => unreachable!(),
3440
};
3541
}

0 commit comments

Comments
 (0)