use rustc_hir as hir; use rustc_hir::def_id::DefId; use rustc_index::Idx; use rustc_infer::infer::{InferCtxt, TyCtxtInferExt}; use rustc_infer::traits::Obligation; use rustc_middle::mir; use rustc_middle::thir::{FieldPat, Pat, PatKind}; use rustc_middle::ty::{self, Ty, TyCtxt, ValTree}; use rustc_session::lint; use rustc_span::{ErrorGuaranteed, Span}; use rustc_target::abi::{FieldIdx, VariantIdx}; use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt; use rustc_trait_selection::traits::{self, ObligationCause}; use std::cell::Cell; use super::PatCtxt; use crate::errors::{ FloatPattern, IndirectStructuralMatch, InvalidPattern, NonPartialEqMatch, NontrivialStructuralMatch, PointerPattern, TypeNotStructural, UnionPattern, UnsizedPattern, }; impl<'a, 'tcx> PatCtxt<'a, 'tcx> { /// Converts an evaluated constant to a pattern (if possible). /// This means aggregate values (like structs and enums) are converted /// to a pattern that matches the value (as if you'd compared via structural equality). /// /// `cv` must be a valtree or a `mir::ConstValue`. #[instrument(level = "debug", skip(self), ret)] pub(super) fn const_to_pat( &self, cv: mir::Const<'tcx>, id: hir::HirId, span: Span, check_body_for_struct_match_violation: Option, ) -> Box> { let infcx = self.tcx.infer_ctxt().build(); let mut convert = ConstToPat::new(self, id, span, infcx); convert.to_pat(cv, check_body_for_struct_match_violation) } } struct ConstToPat<'tcx> { id: hir::HirId, span: Span, param_env: ty::ParamEnv<'tcx>, // This tracks if we emitted some hard error for a given const value, so that // we will not subsequently issue an irrelevant lint for the same const // value. saw_const_match_error: Cell>, // This tracks if we emitted some diagnostic for a given const value, so that // we will not subsequently issue an irrelevant lint for the same const // value. saw_const_match_lint: Cell, // For backcompat we need to keep allowing non-structurally-eq types behind references. // See also all the `cant-hide-behind` tests. behind_reference: Cell, // inference context used for checking `T: Structural` bounds. infcx: InferCtxt<'tcx>, treat_byte_string_as_slice: bool, } /// This error type signals that we encountered a non-struct-eq situation. /// We will fall back to calling `PartialEq::eq` on such patterns, /// and exhaustiveness checking will consider them as matching nothing. #[derive(Debug)] struct FallbackToOpaqueConst; impl<'tcx> ConstToPat<'tcx> { fn new( pat_ctxt: &PatCtxt<'_, 'tcx>, id: hir::HirId, span: Span, infcx: InferCtxt<'tcx>, ) -> Self { trace!(?pat_ctxt.typeck_results.hir_owner); ConstToPat { id, span, infcx, param_env: pat_ctxt.param_env, saw_const_match_error: Cell::new(None), saw_const_match_lint: Cell::new(false), behind_reference: Cell::new(false), treat_byte_string_as_slice: pat_ctxt .typeck_results .treat_byte_string_as_slice .contains(&id.local_id), } } fn tcx(&self) -> TyCtxt<'tcx> { self.infcx.tcx } fn type_marked_structural(&self, ty: Ty<'tcx>) -> bool { ty.is_structural_eq_shallow(self.infcx.tcx) } fn to_pat( &mut self, cv: mir::Const<'tcx>, check_body_for_struct_match_violation: Option, ) -> Box> { trace!(self.treat_byte_string_as_slice); // This method is just a wrapper handling a validity check; the heavy lifting is // performed by the recursive `recur` method, which is not meant to be // invoked except by this method. // // once indirect_structural_match is a full fledged error, this // level of indirection can be eliminated let mir_structural_match_violation = check_body_for_struct_match_violation.map(|def_id| { // `mir_const_qualif` must be called with the `DefId` of the item where the const is // defined, not where it is declared. The difference is significant for associated // constants. self.tcx().mir_const_qualif(def_id).custom_eq }); debug!(?check_body_for_struct_match_violation, ?mir_structural_match_violation); let have_valtree = matches!(cv, mir::Const::Ty(c) if matches!(c.kind(), ty::ConstKind::Value(_))); let inlined_const_as_pat = match cv { mir::Const::Ty(c) => match c.kind() { ty::ConstKind::Param(_) | ty::ConstKind::Infer(_) | ty::ConstKind::Bound(_, _) | ty::ConstKind::Placeholder(_) | ty::ConstKind::Unevaluated(_) | ty::ConstKind::Error(_) | ty::ConstKind::Expr(_) => { span_bug!(self.span, "unexpected const in `to_pat`: {:?}", c.kind()) } ty::ConstKind::Value(valtree) => self .recur(valtree, cv.ty(), mir_structural_match_violation.unwrap_or(false)) .unwrap_or_else(|_: FallbackToOpaqueConst| { Box::new(Pat { span: self.span, ty: cv.ty(), kind: PatKind::Constant { value: cv }, }) }), }, mir::Const::Unevaluated(_, _) => { span_bug!(self.span, "unevaluated const in `to_pat`: {cv:?}") } mir::Const::Val(_, _) => Box::new(Pat { span: self.span, ty: cv.ty(), kind: PatKind::Constant { value: cv }, }), }; if self.saw_const_match_error.get().is_none() { // If we were able to successfully convert the const to some pat (possibly with some // lints, but no errors), double-check that all types in the const implement // `Structural` and `PartialEq`. let structural = traits::search_for_structural_match_violation(self.span, self.tcx(), cv.ty()); debug!( "search_for_structural_match_violation cv.ty: {:?} returned: {:?}", cv.ty(), structural ); // This can occur because const qualification treats all associated constants as // opaque, whereas `search_for_structural_match_violation` tries to monomorphize them // before it runs. // // FIXME(#73448): Find a way to bring const qualification into parity with // `search_for_structural_match_violation`. if structural.is_none() && mir_structural_match_violation.unwrap_or(false) { warn!("MIR const-checker found novel structural match violation. See #73448."); return inlined_const_as_pat; } if let Some(non_sm_ty) = structural { if !self.type_has_partial_eq_impl(cv.ty()) { let e = if let ty::Adt(def, ..) = non_sm_ty.kind() { if def.is_union() { let err = UnionPattern { span: self.span }; self.tcx().sess.emit_err(err) } else { // fatal avoids ICE from resolution of nonexistent method (rare case). self.tcx() .sess .emit_fatal(TypeNotStructural { span: self.span, non_sm_ty }) } } else { let err = InvalidPattern { span: self.span, non_sm_ty }; self.tcx().sess.emit_err(err) }; // All branches above emitted an error. Don't print any more lints. // We errored. Signal that in the pattern, so that follow up errors can be silenced. let kind = PatKind::Error(e); return Box::new(Pat { span: self.span, ty: cv.ty(), kind }); } else if let ty::Adt(..) = cv.ty().kind() && matches!(cv, mir::Const::Val(..)) { // This branch is only entered when the current `cv` is `mir::Const::Val`. // This is because `mir::Const::ty` has already been handled by `Self::recur` // and the invalid types may be ignored. let err = TypeNotStructural { span: self.span, non_sm_ty }; let e = self.tcx().sess.emit_err(err); let kind = PatKind::Error(e); return Box::new(Pat { span: self.span, ty: cv.ty(), kind }); } else if !self.saw_const_match_lint.get() { if let Some(mir_structural_match_violation) = mir_structural_match_violation { match non_sm_ty.kind() { ty::Adt(..) if mir_structural_match_violation => { self.tcx().emit_spanned_lint( lint::builtin::INDIRECT_STRUCTURAL_MATCH, self.id, self.span, IndirectStructuralMatch { non_sm_ty }, ); } _ => { debug!( "`search_for_structural_match_violation` found one, but `CustomEq` was \ not in the qualifs for that `const`" ); } } } } } else if !have_valtree && !self.saw_const_match_lint.get() { // The only way valtree construction can fail without the structural match // checker finding a violation is if there is a pointer somewhere. self.tcx().emit_spanned_lint( lint::builtin::POINTER_STRUCTURAL_MATCH, self.id, self.span, PointerPattern, ); } // Always check for `PartialEq`, even if we emitted other lints. (But not if there were // any errors.) This ensures it shows up in cargo's future-compat reports as well. if !self.type_has_partial_eq_impl(cv.ty()) { self.tcx().emit_spanned_lint( lint::builtin::CONST_PATTERNS_WITHOUT_PARTIAL_EQ, self.id, self.span, NonPartialEqMatch { non_peq_ty: cv.ty() }, ); } } inlined_const_as_pat } #[instrument(level = "trace", skip(self), ret)] fn type_has_partial_eq_impl(&self, ty: Ty<'tcx>) -> bool { // double-check there even *is* a semantic `PartialEq` to dispatch to. // // (If there isn't, then we can safely issue a hard // error, because that's never worked, due to compiler // using `PartialEq::eq` in this scenario in the past.) let partial_eq_trait_id = self.tcx().require_lang_item(hir::LangItem::PartialEq, Some(self.span)); let partial_eq_obligation = Obligation::new( self.tcx(), ObligationCause::dummy(), self.param_env, ty::TraitRef::new(self.tcx(), partial_eq_trait_id, [ty, ty]), ); // This *could* accept a type that isn't actually `PartialEq`, because region bounds get // ignored. However that should be pretty much impossible since consts that do not depend on // generics can only mention the `'static` lifetime, and how would one have a type that's // `PartialEq` for some lifetime but *not* for `'static`? If this ever becomes a problem // we'll need to leave some sort of trace of this requirement in the MIR so that borrowck // can ensure that the type really implements `PartialEq`. self.infcx.predicate_must_hold_modulo_regions(&partial_eq_obligation) } fn field_pats( &self, vals: impl Iterator, Ty<'tcx>)>, ) -> Result>, FallbackToOpaqueConst> { vals.enumerate() .map(|(idx, (val, ty))| { let field = FieldIdx::new(idx); // Patterns can only use monomorphic types. let ty = self.tcx().normalize_erasing_regions(self.param_env, ty); Ok(FieldPat { field, pattern: self.recur(val, ty, false)? }) }) .collect() } // Recursive helper for `to_pat`; invoke that (instead of calling this directly). #[instrument(skip(self), level = "debug")] fn recur( &self, cv: ValTree<'tcx>, ty: Ty<'tcx>, mir_structural_match_violation: bool, ) -> Result>, FallbackToOpaqueConst> { let id = self.id; let span = self.span; let tcx = self.tcx(); let param_env = self.param_env; let kind = match ty.kind() { ty::Float(_) => { self.saw_const_match_lint.set(true); tcx.emit_spanned_lint( lint::builtin::ILLEGAL_FLOATING_POINT_LITERAL_PATTERN, id, span, FloatPattern, ); return Err(FallbackToOpaqueConst); } // If the type is not structurally comparable, just emit the constant directly, // causing the pattern match code to treat it opaquely. // FIXME: This code doesn't emit errors itself, the caller emits the errors. // So instead of specific errors, you just get blanket errors about the whole // const type. See // https://github.com/rust-lang/rust/pull/70743#discussion_r404701963 for // details. // Backwards compatibility hack because we can't cause hard errors on these // types, so we compare them via `PartialEq::eq` at runtime. ty::Adt(..) if !self.type_marked_structural(ty) && self.behind_reference.get() => { if self.saw_const_match_error.get().is_none() && !self.saw_const_match_lint.get() { self.saw_const_match_lint.set(true); tcx.emit_spanned_lint( lint::builtin::INDIRECT_STRUCTURAL_MATCH, id, span, IndirectStructuralMatch { non_sm_ty: ty }, ); } // Since we are behind a reference, we can just bubble the error up so we get a // constant at reference type, making it easy to let the fallback call // `PartialEq::eq` on it. return Err(FallbackToOpaqueConst); } ty::FnDef(..) => { let e = tcx.sess.emit_err(InvalidPattern { span, non_sm_ty: ty }); self.saw_const_match_error.set(Some(e)); // We errored. Signal that in the pattern, so that follow up errors can be silenced. PatKind::Error(e) } ty::Adt(adt_def, _) if !self.type_marked_structural(ty) => { debug!("adt_def {:?} has !type_marked_structural for cv.ty: {:?}", adt_def, ty,); let err = TypeNotStructural { span, non_sm_ty: ty }; let e = tcx.sess.emit_err(err); self.saw_const_match_error.set(Some(e)); // We errored. Signal that in the pattern, so that follow up errors can be silenced. PatKind::Error(e) } ty::Adt(adt_def, args) if adt_def.is_enum() => { let (&variant_index, fields) = cv.unwrap_branch().split_first().unwrap(); let variant_index = VariantIdx::from_u32(variant_index.unwrap_leaf().try_to_u32().ok().unwrap()); PatKind::Variant { adt_def: *adt_def, args, variant_index, subpatterns: self.field_pats( fields.iter().copied().zip( adt_def.variants()[variant_index] .fields .iter() .map(|field| field.ty(self.tcx(), args)), ), )?, } } ty::Tuple(fields) => PatKind::Leaf { subpatterns: self .field_pats(cv.unwrap_branch().iter().copied().zip(fields.iter()))?, }, ty::Adt(def, args) => { assert!(!def.is_union()); // Valtree construction would never succeed for unions. PatKind::Leaf { subpatterns: self.field_pats( cv.unwrap_branch().iter().copied().zip( def.non_enum_variant() .fields .iter() .map(|field| field.ty(self.tcx(), args)), ), )?, } } ty::Slice(elem_ty) => PatKind::Slice { prefix: cv .unwrap_branch() .iter() .map(|val| self.recur(*val, *elem_ty, false)) .collect::>()?, slice: None, suffix: Box::new([]), }, ty::Array(elem_ty, _) => PatKind::Array { prefix: cv .unwrap_branch() .iter() .map(|val| self.recur(*val, *elem_ty, false)) .collect::>()?, slice: None, suffix: Box::new([]), }, ty::Ref(_, pointee_ty, ..) => match *pointee_ty.kind() { // `&str` is represented as a valtree, let's keep using this // optimization for now. ty::Str => { PatKind::Constant { value: mir::Const::Ty(ty::Const::new_value(tcx, cv, ty)) } } // Backwards compatibility hack: support references to non-structural types, // but hard error if we aren't behind a double reference. We could just use // the fallback code path below, but that would allow *more* of this fishy // code to compile, as then it only goes through the future incompat lint // instead of a hard error. ty::Adt(_, _) if !self.type_marked_structural(*pointee_ty) => { if self.behind_reference.get() { if self.saw_const_match_error.get().is_none() && !self.saw_const_match_lint.get() { self.saw_const_match_lint.set(true); tcx.emit_spanned_lint( lint::builtin::INDIRECT_STRUCTURAL_MATCH, self.id, span, IndirectStructuralMatch { non_sm_ty: *pointee_ty }, ); } return Err(FallbackToOpaqueConst); } else { if let Some(e) = self.saw_const_match_error.get() { // We already errored. Signal that in the pattern, so that follow up errors can be silenced. PatKind::Error(e) } else { let err = TypeNotStructural { span, non_sm_ty: *pointee_ty }; let e = tcx.sess.emit_err(err); self.saw_const_match_error.set(Some(e)); // We errored. Signal that in the pattern, so that follow up errors can be silenced. PatKind::Error(e) } } } // All other references are converted into deref patterns and then recursively // convert the dereferenced constant to a pattern that is the sub-pattern of the // deref pattern. _ => { if !pointee_ty.is_sized(tcx, param_env) && !pointee_ty.is_slice() { let err = UnsizedPattern { span, non_sm_ty: *pointee_ty }; let e = tcx.sess.emit_err(err); // We errored. Signal that in the pattern, so that follow up errors can be silenced. PatKind::Error(e) } else { let old = self.behind_reference.replace(true); // `b"foo"` produces a `&[u8; 3]`, but you can't use constants of array type when // matching against references, you can only use byte string literals. // The typechecker has a special case for byte string literals, by treating them // as slices. This means we turn `&[T; N]` constants into slice patterns, which // has no negative effects on pattern matching, even if we're actually matching on // arrays. let pointee_ty = match *pointee_ty.kind() { ty::Array(elem_ty, _) if self.treat_byte_string_as_slice => { Ty::new_slice(tcx, elem_ty) } _ => *pointee_ty, }; // References have the same valtree representation as their pointee. let subpattern = self.recur(cv, pointee_ty, false)?; self.behind_reference.set(old); PatKind::Deref { subpattern } } } }, ty::Bool | ty::Char | ty::Int(_) | ty::Uint(_) | ty::RawPtr(..) => { // The raw pointers we see here have been "vetted" by valtree construction to be // just integers, so we simply allow them. PatKind::Constant { value: mir::Const::Ty(ty::Const::new_value(tcx, cv, ty)) } } ty::FnPtr(..) => { // Valtree construction would never succeed for these, so this is unreachable. unreachable!() } _ => { let err = InvalidPattern { span, non_sm_ty: ty }; let e = tcx.sess.emit_err(err); self.saw_const_match_error.set(Some(e)); // We errored. Signal that in the pattern, so that follow up errors can be silenced. PatKind::Error(e) } }; if self.saw_const_match_error.get().is_none() && !self.saw_const_match_lint.get() && mir_structural_match_violation // FIXME(#73448): Find a way to bring const qualification into parity with // `search_for_structural_match_violation` and then remove this condition. // Obtain the actual type that isn't annotated. If we just looked at `cv.ty` we // could get `Option`, even though `Option` is annotated with derive. && let Some(non_sm_ty) = traits::search_for_structural_match_violation(span, tcx, ty) { self.saw_const_match_lint.set(true); tcx.emit_spanned_lint( lint::builtin::NONTRIVIAL_STRUCTURAL_MATCH, id, span, NontrivialStructuralMatch { non_sm_ty }, ); } Ok(Box::new(Pat { span, ty, kind })) } }