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
Next Next commit
store the kind of pattern adjustments in pat_adjustments
This allows us to better distinguish builtin and overloaded implicit
dereferences.
  • Loading branch information
dianne committed Apr 13, 2025
commit f35eae780b492cca74d6ada59dc857959d937cd4
6 changes: 3 additions & 3 deletions compiler/rustc_hir_typeck/src/expr_use_visitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1227,9 +1227,9 @@ impl<'tcx, Cx: TypeInformationCtxt<'tcx>, D: Delegate<'tcx>> ExprUseVisitor<'tcx
// actually this is somewhat "disjoint" from the code below
// that aims to account for `ref x`.
if let Some(vec) = self.cx.typeck_results().pat_adjustments().get(pat.hir_id) {
if let Some(first_ty) = vec.first() {
debug!("pat_ty(pat={:?}) found adjusted ty `{:?}`", pat, first_ty);
return Ok(*first_ty);
if let Some(first_adjust) = vec.first() {
debug!("pat_ty(pat={:?}) found adjustment `{:?}`", pat, first_adjust);
return Ok(first_adjust.source);
}
} else if let PatKind::Ref(subpat, _) = pat.kind
&& self.cx.typeck_results().skipped_ref_pats().contains(pat.hir_id)
Expand Down
3 changes: 2 additions & 1 deletion compiler/rustc_hir_typeck/src/pat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ use rustc_trait_selection::infer::InferCtxtExt;
use rustc_trait_selection::traits::{ObligationCause, ObligationCauseCode};
use tracing::{debug, instrument, trace};
use ty::VariantDef;
use ty::adjustment::{PatAdjust, PatAdjustment};

use super::report_unexpected_variant_res;
use crate::expectation::Expectation;
Expand Down Expand Up @@ -415,7 +416,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
.pat_adjustments_mut()
.entry(pat.hir_id)
.or_default()
.push(expected);
.push(PatAdjustment { kind: PatAdjust::BuiltinDeref, source: expected });

let mut binding_mode = ByRef::Yes(match pat_info.binding_mode {
// If default binding mode is by value, make it `ref` or `ref mut`
Expand Down
22 changes: 22 additions & 0 deletions compiler/rustc_middle/src/ty/adjustment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,3 +214,25 @@ pub enum CustomCoerceUnsized {
/// Records the index of the field being coerced.
Struct(FieldIdx),
}

/// Represents an implicit coercion applied to the scrutinee of a match before testing a pattern
/// against it. Currently, this is used only for implicit dereferences.
#[derive(Clone, Copy, TyEncodable, TyDecodable, HashStable, TypeFoldable, TypeVisitable)]
pub struct PatAdjustment<'tcx> {
pub kind: PatAdjust,
/// The type of the scrutinee before the adjustment is applied, or the "adjusted type" of the
/// pattern.
pub source: Ty<'tcx>,
}

/// Represents implicit coercions of patterns' types, rather than values' types.
#[derive(Clone, Copy, PartialEq, Debug, TyEncodable, TyDecodable, HashStable)]
#[derive(TypeFoldable, TypeVisitable)]
pub enum PatAdjust {
/// An implicit dereference before matching, such as when matching the pattern `0` against a
/// scrutinee of type `&u8` or `&mut u8`.
BuiltinDeref,
/// An implicit call to `Deref(Mut)::deref(_mut)` before matching, such as when matching the
/// pattern `[..]` against a scrutinee of type `Vec<T>`.
OverloadedDeref,
}
6 changes: 6 additions & 0 deletions compiler/rustc_middle/src/ty/structural_impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@ impl<'tcx> fmt::Debug for ty::adjustment::Adjustment<'tcx> {
}
}

impl<'tcx> fmt::Debug for ty::adjustment::PatAdjustment<'tcx> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} -> {:?}", self.source, self.kind)
}
}

impl fmt::Debug for ty::BoundRegionKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Expand Down
10 changes: 7 additions & 3 deletions compiler/rustc_middle/src/ty/typeck_results.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ pub struct TypeckResults<'tcx> {
///
/// See:
/// <https://github.com/rust-lang/rfcs/blob/master/text/2005-match-ergonomics.md#definitions>
pat_adjustments: ItemLocalMap<Vec<Ty<'tcx>>>,
pat_adjustments: ItemLocalMap<Vec<ty::adjustment::PatAdjustment<'tcx>>>,

/// Set of reference patterns that match against a match-ergonomics inserted reference
/// (as opposed to against a reference in the scrutinee type).
Expand Down Expand Up @@ -403,11 +403,15 @@ impl<'tcx> TypeckResults<'tcx> {
LocalTableInContextMut { hir_owner: self.hir_owner, data: &mut self.pat_binding_modes }
}

pub fn pat_adjustments(&self) -> LocalTableInContext<'_, Vec<Ty<'tcx>>> {
pub fn pat_adjustments(
&self,
) -> LocalTableInContext<'_, Vec<ty::adjustment::PatAdjustment<'tcx>>> {
LocalTableInContext { hir_owner: self.hir_owner, data: &self.pat_adjustments }
}

pub fn pat_adjustments_mut(&mut self) -> LocalTableInContextMut<'_, Vec<Ty<'tcx>>> {
pub fn pat_adjustments_mut(
&mut self,
) -> LocalTableInContextMut<'_, Vec<ty::adjustment::PatAdjustment<'tcx>>> {
LocalTableInContextMut { hir_owner: self.hir_owner, data: &mut self.pat_adjustments }
}

Expand Down
8 changes: 4 additions & 4 deletions compiler/rustc_mir_build/src/thir/pattern/migration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use rustc_errors::MultiSpan;
use rustc_hir::{BindingMode, ByRef, HirId, Mutability};
use rustc_lint as lint;
use rustc_middle::span_bug;
use rustc_middle::ty::{self, Rust2024IncompatiblePatInfo, Ty, TyCtxt};
use rustc_middle::ty::{self, Rust2024IncompatiblePatInfo, TyCtxt};
use rustc_span::{Ident, Span};

use crate::errors::{Rust2024IncompatiblePat, Rust2024IncompatiblePatSugg};
Expand Down Expand Up @@ -93,10 +93,10 @@ impl<'a> PatMigration<'a> {
pub(super) fn visit_implicit_derefs<'tcx>(
&mut self,
pat_span: Span,
adjustments: &[Ty<'tcx>],
adjustments: &[ty::adjustment::PatAdjustment<'tcx>],
) -> Option<(Span, Mutability)> {
let implicit_deref_mutbls = adjustments.iter().map(|ref_ty| {
let &ty::Ref(_, _, mutbl) = ref_ty.kind() else {
let implicit_deref_mutbls = adjustments.iter().map(|adjust| {
let &ty::Ref(_, _, mutbl) = adjust.source.kind() else {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In a future PR, we could store the mutability here, which would remove the need for matching on the type here and in clippy.

span_bug!(pat_span, "pattern implicitly dereferences a non-ref type");
};
mutbl
Expand Down
9 changes: 5 additions & 4 deletions compiler/rustc_mir_build/src/thir/pattern/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use rustc_middle::mir::interpret::LitToConstInput;
use rustc_middle::thir::{
Ascription, FieldPat, LocalVarId, Pat, PatKind, PatRange, PatRangeBoundary,
};
use rustc_middle::ty::adjustment::PatAdjustment;
use rustc_middle::ty::layout::IntegerExt;
use rustc_middle::ty::{self, CanonicalUserTypeAnnotation, Ty, TyCtxt, TypingMode};
use rustc_middle::{bug, span_bug};
Expand Down Expand Up @@ -63,7 +64,7 @@ pub(super) fn pat_from_hir<'a, 'tcx>(

impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
fn lower_pattern(&mut self, pat: &'tcx hir::Pat<'tcx>) -> Box<Pat<'tcx>> {
let adjustments: &[Ty<'tcx>] =
let adjustments: &[PatAdjustment<'tcx>] =
self.typeck_results.pat_adjustments().get(pat.hir_id).map_or(&[], |v| &**v);

// Track the default binding mode for the Rust 2024 migration suggestion.
Expand Down Expand Up @@ -102,11 +103,11 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
_ => self.lower_pattern_unadjusted(pat),
};

let adjusted_pat = adjustments.iter().rev().fold(unadjusted_pat, |thir_pat, ref_ty| {
debug!("{:?}: wrapping pattern with type {:?}", thir_pat, ref_ty);
let adjusted_pat = adjustments.iter().rev().fold(unadjusted_pat, |thir_pat, adjust| {
debug!("{:?}: wrapping pattern with type {:?}", thir_pat, adjust);
Box::new(Pat {
span: thir_pat.span,
ty: *ref_ty,
ty: adjust.source,
kind: PatKind::Deref { subpattern: thir_pat },
})
});
Expand Down
2 changes: 1 addition & 1 deletion src/tools/clippy/clippy_lints/src/pattern_type_mismatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ fn find_first_mismatch(cx: &LateContext<'_>, pat: &Pat<'_>) -> Option<(Span, Mut
};
if let Some(adjustments) = cx.typeck_results().pat_adjustments().get(adjust_pat.hir_id) {
if let [first, ..] = **adjustments {
if let ty::Ref(.., mutability) = *first.kind() {
if let ty::Ref(.., mutability) = *first.source.kind() {
let level = if p.hir_id == pat.hir_id {
Level::Top
} else {
Expand Down