Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit adf98ab

Browse files
committed
Use precise errors during const to pat conversion instead of a catch-all on the main constant
1 parent aba5ea1 commit adf98ab

28 files changed

+240
-115
lines changed

compiler/rustc_mir_build/src/thir/pattern/const_to_pat.rs

Lines changed: 73 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ struct ConstToPat<'a, 'tcx> {
4343
span: Span,
4444
param_env: ty::ParamEnv<'tcx>,
4545

46-
// This tracks if we signal some hard error for a given const value, so that
46+
// This tracks if we saw some error or lint for a given const value, so that
4747
// we will not subsequently issue an irrelevant lint for the same const
4848
// value.
4949
saw_const_match_error: Cell<bool>,
@@ -103,7 +103,7 @@ impl<'a, 'tcx> ConstToPat<'a, 'tcx> {
103103
// once indirect_structural_match is a full fledged error, this
104104
// level of indirection can be eliminated
105105

106-
let inlined_const_as_pat = self.recur(cv);
106+
let inlined_const_as_pat = self.recur(cv, mir_structural_match_violation);
107107

108108
if self.include_lint_checks && !self.saw_const_match_error.get() {
109109
// If we were able to successfully convert the const to some pat,
@@ -216,7 +216,7 @@ impl<'a, 'tcx> ConstToPat<'a, 'tcx> {
216216
}
217217

218218
// Recursive helper for `to_pat`; invoke that (instead of calling this directly).
219-
fn recur(&self, cv: &'tcx ty::Const<'tcx>) -> Pat<'tcx> {
219+
fn recur(&self, cv: &'tcx ty::Const<'tcx>, mir_structural_match_violation: bool) -> Pat<'tcx> {
220220
let id = self.id;
221221
let span = self.span;
222222
let tcx = self.tcx();
@@ -227,7 +227,7 @@ impl<'a, 'tcx> ConstToPat<'a, 'tcx> {
227227
.enumerate()
228228
.map(|(idx, val)| {
229229
let field = Field::new(idx);
230-
FieldPat { field, pattern: self.recur(val) }
230+
FieldPat { field, pattern: self.recur(val, false) }
231231
})
232232
.collect()
233233
};
@@ -248,6 +248,21 @@ impl<'a, 'tcx> ConstToPat<'a, 'tcx> {
248248
tcx.sess.span_err(span, "cannot use unions in constant patterns");
249249
PatKind::Wild
250250
}
251+
ty::Adt(..)
252+
if !self.type_has_partial_eq_impl(cv.ty)
253+
// FIXME(#73448): Find a way to bring const qualification into parity with
254+
// `search_for_structural_match_violation` and then remove this condition.
255+
&& self.search_for_structural_match_violation(cv.ty).is_some() =>
256+
{
257+
let msg = format!(
258+
"to use a constant of type `{}` in a pattern, \
259+
`{}` must be annotated with `#[derive(PartialEq, Eq)]`",
260+
cv.ty, cv.ty,
261+
);
262+
self.saw_const_match_error.set(true);
263+
self.tcx().sess.span_err(self.span, &msg);
264+
PatKind::Wild
265+
}
251266
// If the type is not structurally comparable, just emit the constant directly,
252267
// causing the pattern match code to treat it opaquely.
253268
// FIXME: This code doesn't emit errors itself, the caller emits the errors.
@@ -258,6 +273,20 @@ impl<'a, 'tcx> ConstToPat<'a, 'tcx> {
258273
// Backwards compatibility hack because we can't cause hard errors on these
259274
// types, so we compare them via `PartialEq::eq` at runtime.
260275
ty::Adt(..) if !self.type_marked_structural(cv.ty) && self.behind_reference.get() => {
276+
if self.include_lint_checks && !self.saw_const_match_error.get() {
277+
self.saw_const_match_error.set(true);
278+
let msg = format!(
279+
"to use a constant of type `{}` in a pattern, \
280+
`{}` must be annotated with `#[derive(PartialEq, Eq)]`",
281+
cv.ty, cv.ty,
282+
);
283+
tcx.struct_span_lint_hir(
284+
lint::builtin::INDIRECT_STRUCTURAL_MATCH,
285+
id,
286+
span,
287+
|lint| lint.build(&msg).emit(),
288+
);
289+
}
261290
PatKind::Constant { value: cv }
262291
}
263292
ty::Adt(adt_def, _) if !self.type_marked_structural(cv.ty) => {
@@ -292,14 +321,18 @@ impl<'a, 'tcx> ConstToPat<'a, 'tcx> {
292321
.destructure_const(param_env.and(cv))
293322
.fields
294323
.iter()
295-
.map(|val| self.recur(val))
324+
.map(|val| self.recur(val, false))
296325
.collect(),
297326
slice: None,
298327
suffix: Vec::new(),
299328
},
300329
ty::Ref(_, pointee_ty, ..) => match *pointee_ty.kind() {
301330
// These are not allowed and will error elsewhere anyway.
302-
ty::Dynamic(..) => PatKind::Constant { value: cv },
331+
ty::Dynamic(..) => {
332+
self.saw_const_match_error.set(true);
333+
tcx.sess.span_err(span, &format!("`{}` cannot be used in patterns", cv.ty));
334+
PatKind::Wild
335+
}
303336
// `&str` and `&[u8]` are represented as `ConstValue::Slice`, let's keep using this
304337
// optimization for now.
305338
ty::Str => PatKind::Constant { value: cv },
@@ -321,7 +354,7 @@ impl<'a, 'tcx> ConstToPat<'a, 'tcx> {
321354
.destructure_const(param_env.and(array))
322355
.fields
323356
.iter()
324-
.map(|val| self.recur(val))
357+
.map(|val| self.recur(val, false))
325358
.collect(),
326359
slice: None,
327360
suffix: vec![],
@@ -333,16 +366,21 @@ impl<'a, 'tcx> ConstToPat<'a, 'tcx> {
333366
self.behind_reference.set(old);
334367
val
335368
}
336-
// Backwards compatibility hack. Don't take away the reference, since
337-
// `PartialEq::eq` takes a reference, this makes the rest of the matching logic
338-
// simpler.
369+
// Backwards compatibility hack: support references to non-structural types.
370+
// We'll lower
371+
// this pattern to a `PartialEq::eq` comparison and `PartialEq::eq` takes a
372+
// reference. This makes the rest of the matching logic simpler as it doesn't have
373+
// to figure out how to get a reference again.
339374
ty::Adt(..) if !self.type_marked_structural(pointee_ty) => {
340375
PatKind::Constant { value: cv }
341376
}
377+
// All other references are converted into deref patterns and then recursively
378+
// convert the dereferenced constant to a pattern that is the sub-pattern of the
379+
// deref pattern.
342380
_ => {
343381
let old = self.behind_reference.replace(true);
344382
let val = PatKind::Deref {
345-
subpattern: self.recur(tcx.deref_const(self.param_env.and(cv))),
383+
subpattern: self.recur(tcx.deref_const(self.param_env.and(cv)), false),
346384
};
347385
self.behind_reference.set(old);
348386
val
@@ -373,11 +411,34 @@ impl<'a, 'tcx> ConstToPat<'a, 'tcx> {
373411
PatKind::Constant { value: cv }
374412
}
375413
_ => {
376-
tcx.sess.delay_span_bug(span, &format!("cannot make a pattern out of {}", cv.ty));
414+
self.saw_const_match_error.set(true);
415+
tcx.sess.span_err(span, &format!("`{}` cannot be used in patterns", cv.ty));
377416
PatKind::Wild
378417
}
379418
};
380419

420+
if self.include_lint_checks
421+
&& !self.saw_const_match_error.get()
422+
&& mir_structural_match_violation
423+
// FIXME(#73448): Find a way to bring const qualification into parity with
424+
// `search_for_structural_match_violation` and then remove this condition.
425+
&& self.search_for_structural_match_violation(cv.ty).is_some()
426+
{
427+
self.saw_const_match_error.set(true);
428+
let msg = format!(
429+
"to use a constant of type `{}` in a pattern, \
430+
the constant's initializer must be trivial or all types \
431+
in the constant must be annotated with `#[derive(PartialEq, Eq)]`",
432+
cv.ty,
433+
);
434+
tcx.struct_span_lint_hir(
435+
lint::builtin::NONTRIVIAL_STRUCTURAL_MATCH,
436+
id,
437+
span,
438+
|lint| lint.build(&msg).emit(),
439+
);
440+
}
441+
381442
Pat { span, ty: cv.ty, kind: Box::new(kind) }
382443
}
383444
}

compiler/rustc_session/src/lint/builtin.rs

Lines changed: 52 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2138,22 +2138,16 @@ declare_lint! {
21382138
/// ```rust,compile_fail
21392139
/// #![deny(indirect_structural_match)]
21402140
///
2141-
/// struct Plus(i32, i32);
2142-
/// const ONE_PLUS_TWO: &&Plus = &&Plus(1, 2);
2143-
///
2144-
/// impl PartialEq for Plus {
2145-
/// fn eq(&self, other: &Self) -> bool {
2146-
/// self.0 + self.1 == other.0 + other.1
2147-
/// }
2148-
/// }
2149-
///
2150-
/// impl Eq for Plus {}
2151-
///
2141+
/// struct NoDerive(i32);
2142+
/// impl PartialEq for NoDerive { fn eq(&self, _: &Self) -> bool { false } }
2143+
/// impl Eq for NoDerive { }
2144+
/// #[derive(PartialEq, Eq)]
2145+
/// struct WrapParam<T>(T);
2146+
/// const WRAP_INDIRECT_PARAM: & &WrapParam<NoDerive> = & &WrapParam(NoDerive(0));
21522147
/// fn main() {
2153-
/// if let ONE_PLUS_TWO = &&Plus(3, 0) {
2154-
/// println!("semantic!");
2155-
/// } else {
2156-
/// println!("structural!");
2148+
/// match WRAP_INDIRECT_PARAM {
2149+
/// WRAP_INDIRECT_PARAM => { }
2150+
/// _ => { }
21572151
/// }
21582152
/// }
21592153
/// ```
@@ -2170,9 +2164,8 @@ declare_lint! {
21702164
/// [issue #62411]: https://github.com/rust-lang/rust/issues/62411
21712165
/// [future-incompatible]: ../index.md#future-incompatible-lints
21722166
pub INDIRECT_STRUCTURAL_MATCH,
2173-
// defaulting to allow until rust-lang/rust#62614 is fixed.
2174-
Allow,
2175-
"pattern with const indirectly referencing non-structural-match type",
2167+
Warn,
2168+
"constant used in pattern contains value of non-structural-match type in a field or a variant",
21762169
@future_incompatible = FutureIncompatibleInfo {
21772170
reference: "issue #62411 <https://github.com/rust-lang/rust/issues/62411>",
21782171
edition: None,
@@ -2223,6 +2216,46 @@ declare_lint! {
22232216
};
22242217
}
22252218

2219+
declare_lint! {
2220+
/// The `nontrivial_structural_match` lint detects constants that are used in patterns,
2221+
/// whose type is not structural-match and whose initializer body actually uses values
2222+
/// that are not structural-match. So `Option<NotStruturalMatch>` is ok if the constant
2223+
/// is just `None`.
2224+
///
2225+
/// ### Example
2226+
///
2227+
/// ```rust,compile_fail
2228+
/// #![deny(nontrivial_structural_match)]
2229+
///
2230+
/// struct Plus(i32, i32);
2231+
/// const ONE_PLUS_TWO: &&Plus = &&Plus(1, 2);
2232+
///
2233+
/// impl PartialEq for Plus {
2234+
/// fn eq(&self, other: &Self) -> bool {
2235+
/// self.0 + self.1 == other.0 + other.1
2236+
/// }
2237+
/// }
2238+
///
2239+
/// impl Eq for Plus {}
2240+
///
2241+
/// fn main() {
2242+
/// if let ONE_PLUS_TWO = &&Plus(3, 0) {
2243+
/// println!("semantic!");
2244+
/// } else {
2245+
/// println!("structural!");
2246+
/// }
2247+
/// }
2248+
/// ```
2249+
pub NONTRIVIAL_STRUCTURAL_MATCH,
2250+
Warn,
2251+
"constant used in pattern of non-structural-match type and the constant's initializer \
2252+
expression contains values of non-structural-match types",
2253+
@future_incompatible = FutureIncompatibleInfo {
2254+
reference: "issue #73448 <https://github.com/rust-lang/rust/issues/73448>",
2255+
edition: None,
2256+
};
2257+
}
2258+
22262259
declare_lint! {
22272260
/// The `ambiguous_associated_items` lint detects ambiguity between
22282261
/// [associated items] and [enum variants].
@@ -2657,6 +2690,7 @@ declare_lint_pass! {
26572690
MUTABLE_BORROW_RESERVATION_CONFLICT,
26582691
INDIRECT_STRUCTURAL_MATCH,
26592692
POINTER_STRUCTURAL_MATCH,
2693+
NONTRIVIAL_STRUCTURAL_MATCH,
26602694
SOFT_UNSTABLE,
26612695
INLINE_NO_SANITIZE,
26622696
ASM_SUB_REGISTER,

src/test/ui/consts/const_in_pattern/custom-eq-branch-warn.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
// check-pass
22

3-
#![warn(indirect_structural_match)]
4-
//~^ NOTE lint level is defined here
5-
63
struct CustomEq;
74

85
impl Eq for CustomEq {}
@@ -32,7 +29,8 @@ fn main() {
3229
BAR_BAZ => panic!(),
3330
//~^ WARN must be annotated with `#[derive(PartialEq, Eq)]`
3431
//~| WARN this was previously accepted
35-
//~| NOTE see issue #62411
32+
//~| NOTE see issue #73448
33+
//~| NOTE `#[warn(nontrivial_structural_match)]` on by default
3634
_ => {}
3735
}
3836
}
Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,12 @@
1-
warning: to use a constant of type `CustomEq` in a pattern, `CustomEq` must be annotated with `#[derive(PartialEq, Eq)]`
2-
--> $DIR/custom-eq-branch-warn.rs:32:9
1+
warning: to use a constant of type `Foo` in a pattern, the constant's initializer must be trivial or all types in the constant must be annotated with `#[derive(PartialEq, Eq)]`
2+
--> $DIR/custom-eq-branch-warn.rs:29:9
33
|
44
LL | BAR_BAZ => panic!(),
55
| ^^^^^^^
66
|
7-
note: the lint level is defined here
8-
--> $DIR/custom-eq-branch-warn.rs:3:9
9-
|
10-
LL | #![warn(indirect_structural_match)]
11-
| ^^^^^^^^^^^^^^^^^^^^^^^^^
7+
= note: `#[warn(nontrivial_structural_match)]` on by default
128
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
13-
= note: for more information, see issue #62411 <https://github.com/rust-lang/rust/issues/62411>
9+
= note: for more information, see issue #73448 <https://github.com/rust-lang/rust/issues/73448>
1410

1511
warning: 1 warning emitted
1612

src/test/ui/consts/const_in_pattern/issue-65466.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
// FIXME: This still ICEs.
2-
//
3-
// ignore-test
4-
51
#![deny(indirect_structural_match)]
62

3+
// check-pass
4+
75
#[derive(PartialEq, Eq)]
86
enum O<T> {
97
Some(*const T), // Can also use PhantomData<T>

src/test/ui/consts/const_in_pattern/issue-65466.stderr

Lines changed: 0 additions & 15 deletions
This file was deleted.

src/test/ui/consts/const_in_pattern/reject_non_partial_eq.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ fn main() {
2727
match None {
2828
NO_PARTIAL_EQ_NONE => println!("NO_PARTIAL_EQ_NONE"),
2929
//~^ ERROR must be annotated with `#[derive(PartialEq, Eq)]`
30+
//~| ERROR must be annotated with `#[derive(PartialEq, Eq)]`
3031
_ => panic!("whoops"),
3132
}
3233
}
Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
1-
error: to use a constant of type `NoPartialEq` in a pattern, `NoPartialEq` must be annotated with `#[derive(PartialEq, Eq)]`
1+
error: to use a constant of type `Option<NoPartialEq>` in a pattern, `Option<NoPartialEq>` must be annotated with `#[derive(PartialEq, Eq)]`
22
--> $DIR/reject_non_partial_eq.rs:28:9
33
|
44
LL | NO_PARTIAL_EQ_NONE => println!("NO_PARTIAL_EQ_NONE"),
55
| ^^^^^^^^^^^^^^^^^^
66

7-
error: aborting due to previous error
7+
error: to use a constant of type `Option<NoPartialEq>` in a pattern, `Option<NoPartialEq>` must be annotated with `#[derive(PartialEq, Eq)]`
8+
--> $DIR/reject_non_partial_eq.rs:28:9
9+
|
10+
LL | NO_PARTIAL_EQ_NONE => println!("NO_PARTIAL_EQ_NONE"),
11+
| ^^^^^^^^^^^^^^^^^^
12+
13+
error: aborting due to 2 previous errors
814

0 commit comments

Comments
 (0)