Skip to content

Commit 36d13cb

Browse files
committed
Auto merge of #67343 - ecstatic-morse:qualif-structural-match, r=pnkfelix
Const qualification for `StructuralEq` Furthers #62411. Resolves #62614. The goal of this PR is to implement the logic in #67088 on the MIR instead of the HIR. It uses the `Qualif` trait to track `StructuralPartialEq`/`StructuralEq` in the final value of a `const`. Then, if we encounter a constant during HAIR lowering whose value may not be structurally matchable, we emit the `indirect_structural_match` lint. This PR contains all the tests present in #67088 and emits the proper warnings for the corner cases. This PR does not handle #65466, which would require that we be [more aggressive](https://github.com/rust-lang/rust/blob/42abbd8878d3b67238f3611b0587c704ba94f39c/src/librustc_mir_build/hair/pattern/const_to_pat.rs#L126-L130) when checking matched types for `PartialEq`. I think that should be done separately. Because this works on MIR and uses dataflow, this PR should accept more cases than #67088. Notably, the qualifs in the final value of a const are encoded cross-crate, so matching on a constant whose value is defined in another crate to be `Option::<TyWithCustomEqImpl>::None` should work. Additionally, if a `const` has branching/looping, we will only emit the warning if any possible control flow path could result in a type with a custom `PartialEq` impl ending up as the final value of a `const`. I'm not sure how #67088 handled this. AFAIK, it's not settled that these are the semantics we actually want: it's just how the `Qualif` framework happens to work. If the cross-crate part is undesirable, it would be quite easy to change the result of `mir_const_qualif().custom_eq` to `true` before encoding it in the crate metadata. This way, other crates would have to assume that all publicly exported constants may not be safe for matching. r? @pnkfelix cc @eddyb
2 parents e91aebc + e4c650c commit 36d13cb

26 files changed

+819
-87
lines changed

src/librustc_middle/mir/query.rs

+1
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ pub struct BorrowCheckResult<'tcx> {
8080
pub struct ConstQualifs {
8181
pub has_mut_interior: bool,
8282
pub needs_drop: bool,
83+
pub custom_eq: bool,
8384
}
8485

8586
/// After we borrow check a closure, we are left with various

src/librustc_mir/transform/check_consts/qualifs.rs

+46-6
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,19 @@
22
//!
33
//! See the `Qualif` trait for more info.
44
5+
use rustc_infer::infer::TyCtxtInferExt;
56
use rustc_middle::mir::*;
6-
use rustc_middle::ty::{self, AdtDef, Ty};
7+
use rustc_middle::ty::{self, subst::SubstsRef, AdtDef, Ty};
78
use rustc_span::DUMMY_SP;
9+
use rustc_trait_selection::traits;
810

911
use super::ConstCx;
1012

1113
pub fn in_any_value_of_ty(cx: &ConstCx<'_, 'tcx>, ty: Ty<'tcx>) -> ConstQualifs {
1214
ConstQualifs {
1315
has_mut_interior: HasMutInterior::in_any_value_of_ty(cx, ty),
1416
needs_drop: NeedsDrop::in_any_value_of_ty(cx, ty),
17+
custom_eq: CustomEq::in_any_value_of_ty(cx, ty),
1518
}
1619
}
1720

@@ -53,7 +56,11 @@ pub trait Qualif {
5356
/// with a custom `Drop` impl is inherently `NeedsDrop`.
5457
///
5558
/// Returning `true` for `in_adt_inherently` but `false` for `in_any_value_of_ty` is unsound.
56-
fn in_adt_inherently(cx: &ConstCx<'_, 'tcx>, adt: &AdtDef) -> bool;
59+
fn in_adt_inherently(
60+
cx: &ConstCx<'_, 'tcx>,
61+
adt: &'tcx AdtDef,
62+
substs: SubstsRef<'tcx>,
63+
) -> bool;
5764
}
5865

5966
/// Constant containing interior mutability (`UnsafeCell<T>`).
@@ -74,7 +81,7 @@ impl Qualif for HasMutInterior {
7481
!ty.is_freeze(cx.tcx, cx.param_env, DUMMY_SP)
7582
}
7683

77-
fn in_adt_inherently(cx: &ConstCx<'_, 'tcx>, adt: &AdtDef) -> bool {
84+
fn in_adt_inherently(cx: &ConstCx<'_, 'tcx>, adt: &'tcx AdtDef, _: SubstsRef<'tcx>) -> bool {
7885
// Exactly one type, `UnsafeCell`, has the `HasMutInterior` qualif inherently.
7986
// It arises structurally for all other types.
8087
Some(adt.did) == cx.tcx.lang_items().unsafe_cell_type()
@@ -99,11 +106,44 @@ impl Qualif for NeedsDrop {
99106
ty.needs_drop(cx.tcx, cx.param_env)
100107
}
101108

102-
fn in_adt_inherently(cx: &ConstCx<'_, 'tcx>, adt: &AdtDef) -> bool {
109+
fn in_adt_inherently(cx: &ConstCx<'_, 'tcx>, adt: &'tcx AdtDef, _: SubstsRef<'tcx>) -> bool {
103110
adt.has_dtor(cx.tcx)
104111
}
105112
}
106113

114+
/// A constant that cannot be used as part of a pattern in a `match` expression.
115+
pub struct CustomEq;
116+
117+
impl Qualif for CustomEq {
118+
const ANALYSIS_NAME: &'static str = "flow_custom_eq";
119+
120+
fn in_qualifs(qualifs: &ConstQualifs) -> bool {
121+
qualifs.custom_eq
122+
}
123+
124+
fn in_any_value_of_ty(cx: &ConstCx<'_, 'tcx>, ty: Ty<'tcx>) -> bool {
125+
// If *any* component of a composite data type does not implement `Structural{Partial,}Eq`,
126+
// we know that at least some values of that type are not structural-match. I say "some"
127+
// because that component may be part of an enum variant (e.g.,
128+
// `Option::<NonStructuralMatchTy>::Some`), in which case some values of this type may be
129+
// structural-match (`Option::None`).
130+
let id = cx.tcx.hir().local_def_id_to_hir_id(cx.def_id.as_local().unwrap());
131+
traits::search_for_structural_match_violation(id, cx.body.span, cx.tcx, ty).is_some()
132+
}
133+
134+
fn in_adt_inherently(
135+
cx: &ConstCx<'_, 'tcx>,
136+
adt: &'tcx AdtDef,
137+
substs: SubstsRef<'tcx>,
138+
) -> bool {
139+
let ty = cx.tcx.mk_ty(ty::Adt(adt, substs));
140+
let id = cx.tcx.hir().local_def_id_to_hir_id(cx.def_id.as_local().unwrap());
141+
cx.tcx
142+
.infer_ctxt()
143+
.enter(|infcx| !traits::type_marked_structural(id, cx.body.span, &infcx, ty))
144+
}
145+
}
146+
107147
// FIXME: Use `mir::visit::Visitor` for the `in_*` functions if/when it supports early return.
108148

109149
/// Returns `true` if this `Rvalue` contains qualif `Q`.
@@ -147,8 +187,8 @@ where
147187
Rvalue::Aggregate(kind, operands) => {
148188
// Return early if we know that the struct or enum being constructed is always
149189
// qualified.
150-
if let AggregateKind::Adt(def, ..) = **kind {
151-
if Q::in_adt_inherently(cx, def) {
190+
if let AggregateKind::Adt(def, _, substs, ..) = **kind {
191+
if Q::in_adt_inherently(cx, def, substs) {
152192
return true;
153193
}
154194
}

src/librustc_mir/transform/check_consts/validation.rs

+27-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use std::borrow::Cow;
1717
use std::ops::Deref;
1818

1919
use super::ops::{self, NonConstOp};
20-
use super::qualifs::{self, HasMutInterior, NeedsDrop};
20+
use super::qualifs::{self, CustomEq, HasMutInterior, NeedsDrop};
2121
use super::resolver::FlowSensitiveAnalysis;
2222
use super::{is_lang_panic_fn, ConstCx, ConstKind, Qualif};
2323
use crate::const_eval::{is_const_fn, is_unstable_const_fn};
@@ -142,9 +142,35 @@ impl Qualifs<'mir, 'tcx> {
142142

143143
let return_loc = ccx.body.terminator_loc(return_block);
144144

145+
let custom_eq = match ccx.const_kind() {
146+
// We don't care whether a `const fn` returns a value that is not structurally
147+
// matchable. Functions calls are opaque and always use type-based qualification, so
148+
// this value should never be used.
149+
ConstKind::ConstFn => true,
150+
151+
// If we know that all values of the return type are structurally matchable, there's no
152+
// need to run dataflow.
153+
ConstKind::Const | ConstKind::Static | ConstKind::StaticMut
154+
if !CustomEq::in_any_value_of_ty(ccx, ccx.body.return_ty()) =>
155+
{
156+
false
157+
}
158+
159+
ConstKind::Const | ConstKind::Static | ConstKind::StaticMut => {
160+
let mut cursor = FlowSensitiveAnalysis::new(CustomEq, ccx)
161+
.into_engine(ccx.tcx, &ccx.body, ccx.def_id)
162+
.iterate_to_fixpoint()
163+
.into_results_cursor(&ccx.body);
164+
165+
cursor.seek_after(return_loc);
166+
cursor.contains(RETURN_PLACE)
167+
}
168+
};
169+
145170
ConstQualifs {
146171
needs_drop: self.needs_drop(ccx, RETURN_PLACE, return_loc),
147172
has_mut_interior: self.has_mut_interior(ccx, RETURN_PLACE, return_loc),
173+
custom_eq,
148174
}
149175
}
150176
}

src/librustc_mir_build/hair/pattern/const_to_pat.rs

+18-3
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,14 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
2222
cv: &'tcx ty::Const<'tcx>,
2323
id: hir::HirId,
2424
span: Span,
25+
mir_structural_match_violation: bool,
2526
) -> Pat<'tcx> {
2627
debug!("const_to_pat: cv={:#?} id={:?}", cv, id);
2728
debug!("const_to_pat: cv.ty={:?} span={:?}", cv.ty, span);
2829

2930
self.tcx.infer_ctxt().enter(|infcx| {
3031
let mut convert = ConstToPat::new(self, id, span, infcx);
31-
convert.to_pat(cv)
32+
convert.to_pat(cv, mir_structural_match_violation)
3233
})
3334
}
3435
}
@@ -81,7 +82,11 @@ impl<'a, 'tcx> ConstToPat<'a, 'tcx> {
8182
traits::type_marked_structural(self.id, self.span, &self.infcx, ty)
8283
}
8384

84-
fn to_pat(&mut self, cv: &'tcx ty::Const<'tcx>) -> Pat<'tcx> {
85+
fn to_pat(
86+
&mut self,
87+
cv: &'tcx ty::Const<'tcx>,
88+
mir_structural_match_violation: bool,
89+
) -> Pat<'tcx> {
8590
// This method is just a wrapper handling a validity check; the heavy lifting is
8691
// performed by the recursive `recur` method, which is not meant to be
8792
// invoked except by this method.
@@ -100,6 +105,11 @@ impl<'a, 'tcx> ConstToPat<'a, 'tcx> {
100105
"search_for_structural_match_violation cv.ty: {:?} returned: {:?}",
101106
cv.ty, structural
102107
);
108+
109+
if structural.is_none() && mir_structural_match_violation {
110+
bug!("MIR const-checker found novel structural match violation");
111+
}
112+
103113
if let Some(non_sm_ty) = structural {
104114
let adt_def = match non_sm_ty {
105115
traits::NonStructuralMatchTy::Adt(adt_def) => adt_def,
@@ -146,13 +156,18 @@ impl<'a, 'tcx> ConstToPat<'a, 'tcx> {
146156
if !ty_is_partial_eq {
147157
// span_fatal avoids ICE from resolution of non-existent method (rare case).
148158
self.tcx().sess.span_fatal(self.span, &make_msg());
149-
} else {
159+
} else if mir_structural_match_violation {
150160
self.tcx().struct_span_lint_hir(
151161
lint::builtin::INDIRECT_STRUCTURAL_MATCH,
152162
self.id,
153163
self.span,
154164
|lint| lint.build(&make_msg()).emit(),
155165
);
166+
} else {
167+
debug!(
168+
"`search_for_structural_match_violation` found one, but `CustomEq` was \
169+
not in the qualifs for that `const`"
170+
);
156171
}
157172
}
158173
}

src/librustc_mir_build/hair/pattern/mod.rs

+71-60
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res};
1515
use rustc_hir::pat_util::EnumerateAndAdjustIterator;
1616
use rustc_hir::RangeEnd;
1717
use rustc_index::vec::Idx;
18-
use rustc_middle::mir::interpret::{get_slice_bytes, sign_extend, ConstValue, ErrorHandled};
18+
use rustc_middle::mir::interpret::{get_slice_bytes, sign_extend, ConstValue};
1919
use rustc_middle::mir::interpret::{LitToConstError, LitToConstInput};
2020
use rustc_middle::mir::UserTypeProjection;
2121
use rustc_middle::mir::{BorrowKind, Field, Mutability};
@@ -762,69 +762,80 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
762762
fn lower_path(&mut self, qpath: &hir::QPath<'_>, id: hir::HirId, span: Span) -> Pat<'tcx> {
763763
let ty = self.tables.node_type(id);
764764
let res = self.tables.qpath_res(qpath, id);
765-
let is_associated_const = match res {
766-
Res::Def(DefKind::AssocConst, _) => true,
767-
_ => false,
765+
766+
let pat_from_kind = |kind| Pat { span, ty, kind: Box::new(kind) };
767+
768+
let (def_id, is_associated_const) = match res {
769+
Res::Def(DefKind::Const, def_id) => (def_id, false),
770+
Res::Def(DefKind::AssocConst, def_id) => (def_id, true),
771+
772+
_ => return pat_from_kind(self.lower_variant_or_leaf(res, id, span, ty, vec![])),
768773
};
769-
let kind = match res {
770-
Res::Def(DefKind::Const | DefKind::AssocConst, def_id) => {
771-
let substs = self.tables.node_substs(id);
772-
// Use `Reveal::All` here because patterns are always monomorphic even if their function isn't.
773-
match self.tcx.const_eval_resolve(
774-
self.param_env.with_reveal_all(),
775-
def_id,
776-
substs,
777-
None,
778-
Some(span),
779-
) {
780-
Ok(value) => {
781-
let const_ =
782-
ty::Const::from_value(self.tcx, value, self.tables.node_type(id));
783-
784-
let pattern = self.const_to_pat(&const_, id, span);
785-
if !is_associated_const {
786-
return pattern;
787-
}
788774

789-
let user_provided_types = self.tables().user_provided_types();
790-
return if let Some(u_ty) = user_provided_types.get(id) {
791-
let user_ty = PatTyProj::from_user_type(*u_ty);
792-
Pat {
793-
span,
794-
kind: Box::new(PatKind::AscribeUserType {
795-
subpattern: pattern,
796-
ascription: Ascription {
797-
/// Note that use `Contravariant` here. See the
798-
/// `variance` field documentation for details.
799-
variance: ty::Variance::Contravariant,
800-
user_ty,
801-
user_ty_span: span,
802-
},
803-
}),
804-
ty: const_.ty,
805-
}
806-
} else {
807-
pattern
808-
};
809-
}
810-
Err(ErrorHandled::TooGeneric) => {
811-
self.errors.push(if is_associated_const {
812-
PatternError::AssocConstInPattern(span)
813-
} else {
814-
PatternError::StaticInPattern(span)
815-
});
816-
PatKind::Wild
817-
}
818-
Err(_) => {
819-
self.tcx.sess.span_err(span, "could not evaluate constant pattern");
820-
PatKind::Wild
821-
}
822-
}
775+
// Use `Reveal::All` here because patterns are always monomorphic even if their function
776+
// isn't.
777+
let param_env_reveal_all = self.param_env.with_reveal_all();
778+
let substs = self.tables.node_substs(id);
779+
let instance = match ty::Instance::resolve(self.tcx, param_env_reveal_all, def_id, substs) {
780+
Ok(Some(i)) => i,
781+
Ok(None) => {
782+
self.errors.push(if is_associated_const {
783+
PatternError::AssocConstInPattern(span)
784+
} else {
785+
PatternError::StaticInPattern(span)
786+
});
787+
788+
return pat_from_kind(PatKind::Wild);
789+
}
790+
791+
Err(_) => {
792+
self.tcx.sess.span_err(span, "could not evaluate constant pattern");
793+
return pat_from_kind(PatKind::Wild);
823794
}
824-
_ => self.lower_variant_or_leaf(res, id, span, ty, vec![]),
825795
};
826796

827-
Pat { span, ty, kind: Box::new(kind) }
797+
// `mir_const_qualif` must be called with the `DefId` of the item where the const is
798+
// defined, not where it is declared. The difference is significant for associated
799+
// constants.
800+
let mir_structural_match_violation = self.tcx.mir_const_qualif(instance.def_id()).custom_eq;
801+
debug!("mir_structural_match_violation({:?}) -> {}", qpath, mir_structural_match_violation);
802+
803+
match self.tcx.const_eval_instance(param_env_reveal_all, instance, Some(span)) {
804+
Ok(value) => {
805+
let const_ = ty::Const::from_value(self.tcx, value, self.tables.node_type(id));
806+
807+
let pattern = self.const_to_pat(&const_, id, span, mir_structural_match_violation);
808+
809+
if !is_associated_const {
810+
return pattern;
811+
}
812+
813+
let user_provided_types = self.tables().user_provided_types();
814+
if let Some(u_ty) = user_provided_types.get(id) {
815+
let user_ty = PatTyProj::from_user_type(*u_ty);
816+
Pat {
817+
span,
818+
kind: Box::new(PatKind::AscribeUserType {
819+
subpattern: pattern,
820+
ascription: Ascription {
821+
/// Note that use `Contravariant` here. See the
822+
/// `variance` field documentation for details.
823+
variance: ty::Variance::Contravariant,
824+
user_ty,
825+
user_ty_span: span,
826+
},
827+
}),
828+
ty: const_.ty,
829+
}
830+
} else {
831+
pattern
832+
}
833+
}
834+
Err(_) => {
835+
self.tcx.sess.span_err(span, "could not evaluate constant pattern");
836+
pat_from_kind(PatKind::Wild)
837+
}
838+
}
828839
}
829840

830841
/// Converts literals, paths and negation of literals to patterns.
@@ -849,7 +860,7 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
849860

850861
let lit_input = LitToConstInput { lit: &lit.node, ty: self.tables.expr_ty(expr), neg };
851862
match self.tcx.at(expr.span).lit_to_const(lit_input) {
852-
Ok(val) => *self.const_to_pat(val, expr.hir_id, lit.span).kind,
863+
Ok(val) => *self.const_to_pat(val, expr.hir_id, lit.span, false).kind,
853864
Err(LitToConstError::UnparseableFloat) => {
854865
self.errors.push(PatternError::FloatBug);
855866
PatKind::Wild

0 commit comments

Comments
 (0)