pattern typing for immutable implicit deref patterns
This commit is contained in:
parent
cb6c499bc9
commit
e4b7b3d820
10 changed files with 272 additions and 30 deletions
|
@ -162,12 +162,27 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
|||
/// Mode for adjusting the expected type and binding mode.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum AdjustMode {
|
||||
/// Peel off all immediate reference types.
|
||||
Peel,
|
||||
/// Peel off all immediate reference types. If the `deref_patterns` feature is enabled, this
|
||||
/// also peels smart pointer ADTs.
|
||||
Peel { kind: PeelKind },
|
||||
/// Pass on the input binding mode and expected type.
|
||||
Pass,
|
||||
}
|
||||
|
||||
/// Restrictions on what types to peel when adjusting the expected type and binding mode.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum PeelKind {
|
||||
/// Only peel reference types. This is used for explicit `deref!(_)` patterns, which dereference
|
||||
/// any number of `&`/`&mut` references, plus a single smart pointer.
|
||||
ExplicitDerefPat,
|
||||
/// Implicitly peel any number of references, and if `deref_patterns` is enabled, smart pointer
|
||||
/// ADTs. In order to peel only as much as necessary for the pattern to match, the `until_adt`
|
||||
/// field contains the ADT def that the pattern is a constructor for, if applicable, so that we
|
||||
/// don't peel it. See [`ResolvedPat`] for more information.
|
||||
// TODO: add `ResolvedPat` and `until_adt`.
|
||||
Implicit,
|
||||
}
|
||||
|
||||
/// `ref mut` bindings (explicit or match-ergonomics) are not allowed behind an `&` reference.
|
||||
/// Normally, the borrow checker enforces this, but for (currently experimental) match ergonomics,
|
||||
/// we track this when typing patterns for two purposes:
|
||||
|
@ -390,7 +405,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
|||
}
|
||||
|
||||
// Resolve type if needed.
|
||||
let expected = if let AdjustMode::Peel = adjust_mode
|
||||
let expected = if let AdjustMode::Peel { .. } = adjust_mode
|
||||
&& pat.default_binding_modes
|
||||
{
|
||||
self.try_structurally_resolve_type(pat.span, expected)
|
||||
|
@ -403,7 +418,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
|||
match pat.kind {
|
||||
// Peel off a `&` or `&mut` from the scrutinee type. See the examples in
|
||||
// `tests/ui/rfcs/rfc-2005-default-binding-mode`.
|
||||
_ if let AdjustMode::Peel = adjust_mode
|
||||
_ if let AdjustMode::Peel { .. } = adjust_mode
|
||||
&& pat.default_binding_modes
|
||||
&& let ty::Ref(_, inner_ty, inner_mutability) = *expected.kind() =>
|
||||
{
|
||||
|
@ -443,6 +458,45 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
|||
// Recurse with the new expected type.
|
||||
self.check_pat_inner(pat, opt_path_res, adjust_mode, inner_ty, new_pat_info)
|
||||
}
|
||||
// If `deref_patterns` is enabled, peel a smart pointer from the scrutinee type. See the
|
||||
// examples in `tests/ui/pattern/deref_patterns/`.
|
||||
_ if self.tcx.features().deref_patterns()
|
||||
&& let AdjustMode::Peel { kind: PeelKind::Implicit } = adjust_mode
|
||||
&& pat.default_binding_modes
|
||||
// For simplicity, only apply overloaded derefs if `expected` is a known ADT.
|
||||
// FIXME(deref_patterns): we'll get better diagnostics for users trying to
|
||||
// implicitly deref generics if we allow them here, but primitives, tuples, and
|
||||
// inference vars definitely should be stopped. Figure out what makes most sense.
|
||||
// TODO: stop peeling if the pattern is a constructor for the scrutinee type
|
||||
&& expected.is_adt()
|
||||
// At this point, the pattern isn't able to match `expected` without peeling. Check
|
||||
// that it implements `Deref` before assuming it's a smart pointer, to get a normal
|
||||
// type error instead of a missing impl error if not. This only checks for `Deref`,
|
||||
// not `DerefPure`: we require that too, but we want a trait error if it's missing.
|
||||
&& let Some(deref_trait) = self.tcx.lang_items().deref_trait()
|
||||
&& self
|
||||
.type_implements_trait(deref_trait, [expected], self.param_env)
|
||||
.may_apply() =>
|
||||
{
|
||||
debug!("scrutinee ty {expected:?} is a smart pointer, inserting overloaded deref");
|
||||
// The scrutinee is a smart pointer; implicitly dereference it. This adds a
|
||||
// requirement that `expected: DerefPure`.
|
||||
let inner_ty = self.deref_pat_target(pat.span, expected);
|
||||
// Once we've checked `pat`, we'll add a `DerefMut` bound if it contains any
|
||||
// `ref mut` bindings. TODO: implement that, then reference here.
|
||||
|
||||
// Preserve the smart pointer type for THIR lowering and upvar analysis.
|
||||
self.typeck_results
|
||||
.borrow_mut()
|
||||
.pat_adjustments_mut()
|
||||
.entry(pat.hir_id)
|
||||
.or_default()
|
||||
.push(PatAdjustment { kind: PatAdjust::OverloadedDeref, source: expected });
|
||||
|
||||
// Recurse, using the old pat info to keep `current_depth` to its old value.
|
||||
// Peeling smart pointers does not update the default binding mode.
|
||||
self.check_pat_inner(pat, opt_path_res, adjust_mode, inner_ty, old_pat_info)
|
||||
}
|
||||
PatKind::Missing | PatKind::Wild | PatKind::Err(_) => expected,
|
||||
// We allow any type here; we ensure that the type is uninhabited during match checking.
|
||||
PatKind::Never => expected,
|
||||
|
@ -505,12 +559,15 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
|||
PatKind::Struct(..)
|
||||
| PatKind::TupleStruct(..)
|
||||
| PatKind::Tuple(..)
|
||||
| PatKind::Box(_)
|
||||
| PatKind::Deref(_)
|
||||
| PatKind::Range(..)
|
||||
| PatKind::Slice(..) => AdjustMode::Peel,
|
||||
| PatKind::Slice(..) => AdjustMode::Peel { kind: PeelKind::Implicit },
|
||||
// When checking an explicit deref pattern, only peel reference types.
|
||||
// FIXME(deref_patterns): If box patterns and deref patterns need to coexist, box
|
||||
// patterns may want `PeelKind::Implicit`, stopping on encountering a box.
|
||||
| PatKind::Box(_)
|
||||
| PatKind::Deref(_) => AdjustMode::Peel { kind: PeelKind::ExplicitDerefPat },
|
||||
// A never pattern behaves somewhat like a literal or unit variant.
|
||||
PatKind::Never => AdjustMode::Peel,
|
||||
PatKind::Never => AdjustMode::Peel { kind: PeelKind::Implicit },
|
||||
PatKind::Expr(PatExpr { kind: PatExprKind::Path(_), .. }) => match opt_path_res.unwrap() {
|
||||
// These constants can be of a reference type, e.g. `const X: &u8 = &0;`.
|
||||
// Peeling the reference types too early will cause type checking failures.
|
||||
|
@ -520,7 +577,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
|||
// could successfully compile. The former being `Self` requires a unit struct.
|
||||
// In either case, and unlike constants, the pattern itself cannot be
|
||||
// a reference type wherefore peeling doesn't give up any expressiveness.
|
||||
_ => AdjustMode::Peel,
|
||||
_ => AdjustMode::Peel { kind: PeelKind::Implicit },
|
||||
},
|
||||
|
||||
// String and byte-string literals result in types `&str` and `&[u8]` respectively.
|
||||
|
@ -530,7 +587,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
|||
// Call `resolve_vars_if_possible` here for inline const blocks.
|
||||
PatKind::Expr(lt) => match self.resolve_vars_if_possible(self.check_pat_expr_unadjusted(lt)).kind() {
|
||||
ty::Ref(..) => AdjustMode::Pass,
|
||||
_ => AdjustMode::Peel,
|
||||
_ => AdjustMode::Peel { kind: PeelKind::Implicit },
|
||||
},
|
||||
|
||||
// Ref patterns are complicated, we handle them in `check_pat_ref`.
|
||||
|
@ -2256,31 +2313,18 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
|||
expected: Ty<'tcx>,
|
||||
pat_info: PatInfo<'tcx>,
|
||||
) -> Ty<'tcx> {
|
||||
let tcx = self.tcx;
|
||||
// Register a `DerefPure` bound, which is required by all `deref!()` pats.
|
||||
self.register_bound(
|
||||
expected,
|
||||
tcx.require_lang_item(hir::LangItem::DerefPure, Some(span)),
|
||||
self.misc(span),
|
||||
);
|
||||
// <expected as Deref>::Target
|
||||
let ty = Ty::new_projection(
|
||||
tcx,
|
||||
tcx.require_lang_item(hir::LangItem::DerefTarget, Some(span)),
|
||||
[expected],
|
||||
);
|
||||
let ty = self.normalize(span, ty);
|
||||
let ty = self.try_structurally_resolve_type(span, ty);
|
||||
self.check_pat(inner, ty, pat_info);
|
||||
let target_ty = self.deref_pat_target(span, expected);
|
||||
self.check_pat(inner, target_ty, pat_info);
|
||||
|
||||
// Check if the pattern has any `ref mut` bindings, which would require
|
||||
// `DerefMut` to be emitted in MIR building instead of just `Deref`.
|
||||
// We do this *after* checking the inner pattern, since we want to make
|
||||
// sure to apply any match-ergonomics adjustments.
|
||||
// TODO: move this to a separate definition to share it with implicit deref pats
|
||||
if self.typeck_results.borrow().pat_has_ref_mut_binding(inner) {
|
||||
self.register_bound(
|
||||
expected,
|
||||
tcx.require_lang_item(hir::LangItem::DerefMut, Some(span)),
|
||||
self.tcx.require_lang_item(hir::LangItem::DerefMut, Some(span)),
|
||||
self.misc(span),
|
||||
);
|
||||
}
|
||||
|
@ -2288,6 +2332,24 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
|||
expected
|
||||
}
|
||||
|
||||
fn deref_pat_target(&self, span: Span, source_ty: Ty<'tcx>) -> Ty<'tcx> {
|
||||
// Register a `DerefPure` bound, which is required by all `deref!()` pats.
|
||||
let tcx = self.tcx;
|
||||
self.register_bound(
|
||||
source_ty,
|
||||
tcx.require_lang_item(hir::LangItem::DerefPure, Some(span)),
|
||||
self.misc(span),
|
||||
);
|
||||
// The expected type for the deref pat's inner pattern is `<expected as Deref>::Target`.
|
||||
let target_ty = Ty::new_projection(
|
||||
tcx,
|
||||
tcx.require_lang_item(hir::LangItem::DerefTarget, Some(span)),
|
||||
[source_ty],
|
||||
);
|
||||
let target_ty = self.normalize(span, target_ty);
|
||||
self.try_structurally_resolve_type(span, target_ty)
|
||||
}
|
||||
|
||||
// Precondition: Pat is Ref(inner)
|
||||
fn check_pat_ref(
|
||||
&self,
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
//@ revisions: explicit implicit
|
||||
//@ run-pass
|
||||
#![feature(deref_patterns)]
|
||||
#![allow(incomplete_features)]
|
||||
|
||||
#[cfg(explicit)]
|
||||
fn simple_vec(vec: Vec<u32>) -> u32 {
|
||||
match vec {
|
||||
deref!([]) => 100,
|
||||
|
@ -13,6 +15,19 @@ fn simple_vec(vec: Vec<u32>) -> u32 {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(implicit)]
|
||||
fn simple_vec(vec: Vec<u32>) -> u32 {
|
||||
match vec {
|
||||
[] => 100,
|
||||
[x] if x == 4 => x + 4,
|
||||
[x] => x,
|
||||
[1, x] => x + 200,
|
||||
deref!(ref slice) => slice.iter().sum(),
|
||||
_ => 2000,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(explicit)]
|
||||
fn nested_vec(vecvec: Vec<Vec<u32>>) -> u32 {
|
||||
match vecvec {
|
||||
deref!([]) => 0,
|
||||
|
@ -24,6 +39,18 @@ fn nested_vec(vecvec: Vec<Vec<u32>>) -> u32 {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(implicit)]
|
||||
fn nested_vec(vecvec: Vec<Vec<u32>>) -> u32 {
|
||||
match vecvec {
|
||||
[] => 0,
|
||||
[[x]] => x,
|
||||
[[0, x] | [1, x]] => x,
|
||||
[ref x] => x.iter().sum(),
|
||||
[[], [1, x, y]] => y - x,
|
||||
_ => 2000,
|
||||
}
|
||||
}
|
||||
|
||||
fn ref_mut(val: u32) -> u32 {
|
||||
let mut b = Box::new(0u32);
|
||||
match &mut b {
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
//@ revisions: explicit implicit
|
||||
//@ run-pass
|
||||
// Test the execution of deref patterns.
|
||||
#![feature(deref_patterns)]
|
||||
#![allow(incomplete_features)]
|
||||
|
||||
#[cfg(explicit)]
|
||||
fn branch(vec: Vec<u32>) -> u32 {
|
||||
match vec {
|
||||
deref!([]) => 0,
|
||||
|
@ -12,6 +14,17 @@ fn branch(vec: Vec<u32>) -> u32 {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(implicit)]
|
||||
fn branch(vec: Vec<u32>) -> u32 {
|
||||
match vec {
|
||||
[] => 0,
|
||||
[1, _, 3] => 1,
|
||||
[2, ..] => 2,
|
||||
_ => 1000,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(explicit)]
|
||||
fn nested(vec: Vec<Vec<u32>>) -> u32 {
|
||||
match vec {
|
||||
deref!([deref!([]), ..]) => 1,
|
||||
|
@ -20,6 +33,15 @@ fn nested(vec: Vec<Vec<u32>>) -> u32 {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(implicit)]
|
||||
fn nested(vec: Vec<Vec<u32>>) -> u32 {
|
||||
match vec {
|
||||
[[], ..] => 1,
|
||||
[[0, ..], [1, ..]] => 2,
|
||||
_ => 1000,
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
assert!(matches!(Vec::<u32>::new(), deref!([])));
|
||||
assert!(matches!(vec![1], deref!([1])));
|
||||
|
|
|
@ -21,4 +21,22 @@ fn cant_move_out_rc(rc: Rc<Struct>) -> Struct {
|
|||
}
|
||||
}
|
||||
|
||||
struct Container(Struct);
|
||||
|
||||
fn cant_move_out_box_implicit(b: Box<Container>) -> Struct {
|
||||
match b {
|
||||
//~^ ERROR: cannot move out of a shared reference
|
||||
Container(x) => x,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn cant_move_out_rc_implicit(rc: Rc<Container>) -> Struct {
|
||||
match rc {
|
||||
//~^ ERROR: cannot move out of a shared reference
|
||||
Container(x) => x,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
|
|
|
@ -32,6 +32,40 @@ help: consider borrowing the pattern binding
|
|||
LL | deref!(ref x) => x,
|
||||
| +++
|
||||
|
||||
error: aborting due to 2 previous errors
|
||||
error[E0507]: cannot move out of a shared reference
|
||||
--> $DIR/cant_move_out_of_pattern.rs:27:11
|
||||
|
|
||||
LL | match b {
|
||||
| ^
|
||||
LL |
|
||||
LL | Container(x) => x,
|
||||
| -
|
||||
| |
|
||||
| data moved here
|
||||
| move occurs because `x` has type `Struct`, which does not implement the `Copy` trait
|
||||
|
|
||||
help: consider borrowing the pattern binding
|
||||
|
|
||||
LL | Container(ref x) => x,
|
||||
| +++
|
||||
|
||||
error[E0507]: cannot move out of a shared reference
|
||||
--> $DIR/cant_move_out_of_pattern.rs:35:11
|
||||
|
|
||||
LL | match rc {
|
||||
| ^^
|
||||
LL |
|
||||
LL | Container(x) => x,
|
||||
| -
|
||||
| |
|
||||
| data moved here
|
||||
| move occurs because `x` has type `Struct`, which does not implement the `Copy` trait
|
||||
|
|
||||
help: consider borrowing the pattern binding
|
||||
|
|
||||
LL | Container(ref x) => x,
|
||||
| +++
|
||||
|
||||
error: aborting due to 4 previous errors
|
||||
|
||||
For more information about this error, try `rustc --explain E0507`.
|
||||
|
|
|
@ -10,26 +10,32 @@ fn main() {
|
|||
let vec: Vec<u32> = Vec::new();
|
||||
match vec {
|
||||
deref!([..]) => {}
|
||||
[..] => {}
|
||||
_ => {}
|
||||
}
|
||||
match Box::new(true) {
|
||||
deref!(true) => {}
|
||||
true => {}
|
||||
_ => {}
|
||||
}
|
||||
match &Box::new(true) {
|
||||
deref!(true) => {}
|
||||
true => {}
|
||||
_ => {}
|
||||
}
|
||||
match &Rc::new(0) {
|
||||
deref!(1..) => {}
|
||||
1.. => {}
|
||||
_ => {}
|
||||
}
|
||||
let _: &Struct = match &Rc::new(Struct) {
|
||||
deref!(x) => x,
|
||||
Struct => &Struct,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let _: &[Struct] = match &Rc::new(vec![Struct]) {
|
||||
deref!(deref!(x)) => x,
|
||||
[Struct] => &[Struct],
|
||||
_ => unreachable!(),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -7,11 +7,22 @@ fn main() {
|
|||
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
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// Make sure we don't try implicitly dereferncing any ADT.
|
||||
match Some(0) {
|
||||
Ok(0) => {}
|
||||
//~^ ERROR: mismatched types
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,13 +7,45 @@ LL | deref!("foo") => {}
|
|||
| ^^^^^ expected `str`, found `&str`
|
||||
|
||||
error[E0308]: mismatched types
|
||||
--> $DIR/typeck_fail.rs:13:16
|
||||
--> $DIR/typeck_fail.rs:10:9
|
||||
|
|
||||
LL | match "foo".to_string() {
|
||||
| ----------------- this expression has type `String`
|
||||
...
|
||||
LL | "foo" => {}
|
||||
| ^^^^^ expected `String`, found `&str`
|
||||
|
||||
error[E0308]: mismatched types
|
||||
--> $DIR/typeck_fail.rs:15:16
|
||||
|
|
||||
LL | match &"foo".to_string() {
|
||||
| ------------------ this expression has type `&String`
|
||||
LL | deref!("foo") => {}
|
||||
| ^^^^^ expected `str`, found `&str`
|
||||
|
||||
error: aborting due to 2 previous errors
|
||||
error[E0308]: mismatched types
|
||||
--> $DIR/typeck_fail.rs:17:9
|
||||
|
|
||||
LL | match &"foo".to_string() {
|
||||
| ------------------ this expression has type `&String`
|
||||
...
|
||||
LL | "foo" => {}
|
||||
| ^^^^^ expected `&String`, found `&str`
|
||||
|
|
||||
= note: expected reference `&String`
|
||||
found reference `&'static str`
|
||||
|
||||
error[E0308]: mismatched types
|
||||
--> $DIR/typeck_fail.rs:24:9
|
||||
|
|
||||
LL | match Some(0) {
|
||||
| ------- this expression has type `Option<{integer}>`
|
||||
LL | Ok(0) => {}
|
||||
| ^^^^^ expected `Option<{integer}>`, found `Result<_, _>`
|
||||
|
|
||||
= note: expected enum `Option<{integer}>`
|
||||
found enum `Result<_, _>`
|
||||
|
||||
error: aborting due to 5 previous errors
|
||||
|
||||
For more information about this error, try `rustc --explain E0308`.
|
||||
|
|
21
tests/ui/pattern/deref-patterns/unsatisfied-bounds.rs
Normal file
21
tests/ui/pattern/deref-patterns/unsatisfied-bounds.rs
Normal file
|
@ -0,0 +1,21 @@
|
|||
#![feature(deref_patterns)]
|
||||
#![allow(incomplete_features)]
|
||||
|
||||
struct MyPointer;
|
||||
|
||||
impl std::ops::Deref for MyPointer {
|
||||
type Target = ();
|
||||
fn deref(&self) -> &() {
|
||||
&()
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// Test that we get a trait error if a user attempts implicit deref pats on their own impls.
|
||||
// FIXME(deref_patterns): there should be a special diagnostic for missing `DerefPure`.
|
||||
match MyPointer {
|
||||
() => {}
|
||||
//~^ the trait bound `MyPointer: DerefPure` is not satisfied
|
||||
_ => {}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
error[E0277]: the trait bound `MyPointer: DerefPure` is not satisfied
|
||||
--> $DIR/unsatisfied-bounds.rs:17:9
|
||||
|
|
||||
LL | () => {}
|
||||
| ^^ the trait `DerefPure` is not implemented for `MyPointer`
|
||||
|
||||
error: aborting due to 1 previous error
|
||||
|
||||
For more information about this error, try `rustc --explain E0277`.
|
Loading…
Add table
Add a link
Reference in a new issue