Skip to content

deref patterns: implement implicit deref patterns #138528

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 13 commits into from
Apr 18, 2025
Merged
Prev Previous commit
Next Next commit
don't peel ADTs the pattern could match
This is the use for the previous commits' refactors; see the messages
there for more information.
  • Loading branch information
dianne committed Apr 16, 2025
commit 923d95cc9f318a88cde9166dd44f785c8bcdadfb
49 changes: 35 additions & 14 deletions compiler/rustc_hir_typeck/src/pat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use rustc_errors::{
Applicability, Diag, ErrorGuaranteed, MultiSpan, pluralize, struct_span_code_err,
};
use rustc_hir::def::{CtorKind, DefKind, Res};
use rustc_hir::def_id::DefId;
use rustc_hir::pat_util::EnumerateAndAdjustIterator;
use rustc_hir::{
self as hir, BindingMode, ByRef, ExprKind, HirId, LangItem, Mutability, Pat, PatExpr,
Expand Down Expand Up @@ -179,8 +180,16 @@ enum PeelKind {
/// 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,
Implicit { until_adt: Option<DefId> },
}

impl AdjustMode {
const fn peel_until_adt(opt_adt_def: Option<DefId>) -> AdjustMode {
AdjustMode::Peel { kind: PeelKind::Implicit { until_adt: opt_adt_def } }
}
const fn peel_all() -> AdjustMode {
AdjustMode::peel_until_adt(None)
}
}

/// `ref mut` bindings (explicit or match-ergonomics) are not allowed behind an `&` reference.
Expand Down Expand Up @@ -294,7 +303,7 @@ impl<'tcx> ResolvedPat<'tcx> {
// The remaining possible resolutions for path, struct, and tuple struct patterns are
// ADT constructors. As such, we may peel references freely, but we must not peel the
// ADT itself from the scrutinee if it's a smart pointer.
AdjustMode::Peel { kind: PeelKind::Implicit }
AdjustMode::peel_until_adt(self.ty.ty_adt_def().map(|adt| adt.did()))
}
}
}
Expand Down Expand Up @@ -521,14 +530,16 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
// 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
&& let AdjustMode::Peel { kind: PeelKind::Implicit { until_adt } } = 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()
&& let ty::Adt(scrutinee_adt, _) = *expected.kind()
// Don't peel if the pattern type already matches the scrutinee. E.g., stop here if
// matching on a `Cow<'a, T>` scrutinee with a `Cow::Owned(_)` pattern.
&& until_adt != Some(scrutinee_adt.did())
// 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`,
Expand Down Expand Up @@ -637,22 +648,22 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
match &pat.kind {
// Type checking these product-like types successfully always require
// that the expected type be of those types and not reference types.
PatKind::Struct(..)
| PatKind::TupleStruct(..)
| PatKind::Tuple(..)
PatKind::Tuple(..)
| PatKind::Range(..)
| PatKind::Slice(..) => AdjustMode::Peel { kind: PeelKind::Implicit },
| PatKind::Slice(..) => AdjustMode::peel_all(),
// 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 { kind: PeelKind::Implicit },
PatKind::Never => AdjustMode::peel_all(),
// For patterns with paths, how we peel the scrutinee depends on the path's resolution.
PatKind::Expr(PatExpr { kind: PatExprKind::Path(_), .. }) => {
PatKind::Struct(..)
| PatKind::TupleStruct(..)
| PatKind::Expr(PatExpr { kind: PatExprKind::Path(_), .. }) => {
// If there was an error resolving the path, default to peeling everything.
opt_path_res.unwrap().map_or(AdjustMode::Peel { kind: PeelKind::Implicit }, |pr| pr.adjust_mode())
opt_path_res.unwrap().map_or(AdjustMode::peel_all(), |pr| pr.adjust_mode())
}

// String and byte-string literals result in types `&str` and `&[u8]` respectively.
Expand All @@ -662,7 +673,17 @@ 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 { kind: PeelKind::Implicit },
_ => {
// Path patterns have already been handled, and inline const blocks currently
// aren't possible to write, so any handling for them would be untested.
if cfg!(debug_assertions)
&& self.tcx.features().deref_patterns()
&& !matches!(lt.kind, PatExprKind::Lit { .. })
{
span_bug!(lt.span, "FIXME(deref_patterns): adjust mode unimplemented for {:?}", lt.kind);
}
AdjustMode::peel_all()
}
},

// Ref patterns are complicated, we handle them in `check_pat_ref`.
Expand Down
19 changes: 19 additions & 0 deletions tests/ui/pattern/deref-patterns/implicit-const-deref.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//! Test that we get an error about structural equality rather than a type error when attempting to
//! use const patterns of library pointer types. Currently there aren't any smart pointers that can
//! be used in constant patterns, but we still need to make sure we don't implicitly dereference the
//! scrutinee and end up with a type error; this would prevent us from reporting that only constants
//! supporting structural equality can be used as patterns.
#![feature(deref_patterns)]
#![allow(incomplete_features)]

const EMPTY: Vec<()> = Vec::new();

fn main() {
// FIXME(inline_const_pat): if `inline_const_pat` is reinstated, there should be a case here for
// inline const block patterns as well; they're checked differently than named constants.
match vec![()] {
EMPTY => {}
//~^ ERROR: constant of non-structural type `Vec<()>` in a pattern
_ => {}
}
}
16 changes: 16 additions & 0 deletions tests/ui/pattern/deref-patterns/implicit-const-deref.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
error: constant of non-structural type `Vec<()>` in a pattern
--> $DIR/implicit-const-deref.rs:15:9
|
LL | const EMPTY: Vec<()> = Vec::new();
| -------------------- constant defined here
...
LL | EMPTY => {}
| ^^^^^ constant of non-structural type
--> $SRC_DIR/alloc/src/vec/mod.rs:LL:COL
|
= note: `Vec<()>` must be annotated with `#[derive(PartialEq)]` to be usable in patterns
|
= note: see https://doc.rust-lang.org/stable/std/marker/trait.StructuralPartialEq.html for details

error: aborting due to 1 previous error

44 changes: 44 additions & 0 deletions tests/ui/pattern/deref-patterns/implicit-cow-deref.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//@ run-pass
//! Test that implicit deref patterns interact as expected with `Cow` constructor patterns.
#![feature(deref_patterns)]
#![allow(incomplete_features)]

use std::borrow::Cow;

fn main() {
let cow: Cow<'static, [u8]> = Cow::from(&[1, 2, 3]);

match cow {
[..] => {}
_ => unreachable!(),
}

match cow {
Cow::Borrowed(_) => {}
Cow::Owned(_) => unreachable!(),
}

match Box::new(&cow) {
Cow::Borrowed { 0: _ } => {}
Cow::Owned { 0: _ } => unreachable!(),
_ => unreachable!(),
}

let cow_of_cow: Cow<'_, Cow<'static, [u8]>> = Cow::Owned(cow);

match cow_of_cow {
[..] => {}
_ => unreachable!(),
}

match cow_of_cow {
Cow::Borrowed(_) => unreachable!(),
Cow::Owned(_) => {}
}

match Box::new(&cow_of_cow) {
Cow::Borrowed { 0: _ } => unreachable!(),
Cow::Owned { 0: _ } => {}
_ => unreachable!(),
}
}