From 923d95cc9f318a88cde9166dd44f785c8bcdadfb Mon Sep 17 00:00:00 2001 From: dianne Date: Sat, 8 Mar 2025 21:30:48 -0800 Subject: [PATCH] don't peel ADTs the pattern could match This is the use for the previous commits' refactors; see the messages there for more information. --- compiler/rustc_hir_typeck/src/pat.rs | 49 +++++++++++++------ .../deref-patterns/implicit-const-deref.rs | 19 +++++++ .../implicit-const-deref.stderr | 16 ++++++ .../deref-patterns/implicit-cow-deref.rs | 44 +++++++++++++++++ 4 files changed, 114 insertions(+), 14 deletions(-) create mode 100644 tests/ui/pattern/deref-patterns/implicit-const-deref.rs create mode 100644 tests/ui/pattern/deref-patterns/implicit-const-deref.stderr create mode 100644 tests/ui/pattern/deref-patterns/implicit-cow-deref.rs diff --git a/compiler/rustc_hir_typeck/src/pat.rs b/compiler/rustc_hir_typeck/src/pat.rs index da5153bed12..46916f22d0e 100644 --- a/compiler/rustc_hir_typeck/src/pat.rs +++ b/compiler/rustc_hir_typeck/src/pat.rs @@ -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, @@ -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 }, +} + +impl AdjustMode { + const fn peel_until_adt(opt_adt_def: Option) -> 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. @@ -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())) } } } @@ -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`, @@ -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. @@ -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`. diff --git a/tests/ui/pattern/deref-patterns/implicit-const-deref.rs b/tests/ui/pattern/deref-patterns/implicit-const-deref.rs new file mode 100644 index 00000000000..70f89629bc2 --- /dev/null +++ b/tests/ui/pattern/deref-patterns/implicit-const-deref.rs @@ -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 + _ => {} + } +} diff --git a/tests/ui/pattern/deref-patterns/implicit-const-deref.stderr b/tests/ui/pattern/deref-patterns/implicit-const-deref.stderr new file mode 100644 index 00000000000..21d09ec44c4 --- /dev/null +++ b/tests/ui/pattern/deref-patterns/implicit-const-deref.stderr @@ -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 + diff --git a/tests/ui/pattern/deref-patterns/implicit-cow-deref.rs b/tests/ui/pattern/deref-patterns/implicit-cow-deref.rs new file mode 100644 index 00000000000..6a363c58b63 --- /dev/null +++ b/tests/ui/pattern/deref-patterns/implicit-cow-deref.rs @@ -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!(), + } +}