Skip to content

Commit ccd1c1f

Browse files
committed
error early when mixing deref patterns with normal constructors
Without adding proper support for mixed exhaustiveness, mixing deref patterns with normal constructors would either violate `ConstructorSet::split`'s invariant 4 or 7. We'd either be ignoring rows with normal constructors or we'd have problems in unspecialization from non-disjoint constructors. Checking mixed exhaustivenss similarly to how unions are currently checked should work, but the diagnostics for unions are confusing. Since mixing deref patterns with normal constructors is pretty niche (currently it only makes sense for `Cow`), emitting an error lets us avoid committing to supporting mixed exhaustiveness without a good answer for the diagnostics.
1 parent cf4839d commit ccd1c1f

File tree

5 files changed

+157
-1
lines changed

5 files changed

+157
-1
lines changed

compiler/rustc_pattern_analysis/messages.ftl

+4
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ pattern_analysis_excluside_range_missing_max = exclusive range missing `{$max}`
66
.label = this range doesn't match `{$max}` because `..` is an exclusive range
77
.suggestion = use an inclusive range instead
88
9+
pattern_analysis_mixed_deref_pattern_constructors = mix of deref patterns and normal constructors
10+
.deref_pattern_label = matches on the result of dereferencing `{$smart_pointer_ty}`
11+
.normal_constructor_label = matches directly on `{$smart_pointer_ty}`
12+
913
pattern_analysis_non_exhaustive_omitted_pattern = some variants are not matched explicitly
1014
.help = ensure that all variants are matched explicitly by adding the suggested match arms
1115
.note = the matched value is of type `{$scrut_ty}` and the `non_exhaustive_omitted_patterns` attribute was found

compiler/rustc_pattern_analysis/src/errors.rs

+13-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use rustc_errors::{Diag, EmissionGuarantee, Subdiagnostic};
2-
use rustc_macros::{LintDiagnostic, Subdiagnostic};
2+
use rustc_macros::{Diagnostic, LintDiagnostic, Subdiagnostic};
33
use rustc_middle::ty::Ty;
44
use rustc_span::Span;
55

@@ -133,3 +133,15 @@ pub(crate) struct NonExhaustiveOmittedPatternLintOnArm {
133133
pub lint_level: &'static str,
134134
pub lint_name: &'static str,
135135
}
136+
137+
#[derive(Diagnostic)]
138+
#[diag(pattern_analysis_mixed_deref_pattern_constructors)]
139+
pub(crate) struct MixedDerefPatternConstructors<'tcx> {
140+
#[primary_span]
141+
pub spans: Vec<Span>,
142+
pub smart_pointer_ty: Ty<'tcx>,
143+
#[label(pattern_analysis_deref_pattern_label)]
144+
pub deref_pattern_label: Span,
145+
#[label(pattern_analysis_normal_constructor_label)]
146+
pub normal_constructor_label: Span,
147+
}

compiler/rustc_pattern_analysis/src/rustc.rs

+49
Original file line numberDiff line numberDiff line change
@@ -1105,6 +1105,14 @@ pub fn analyze_match<'p, 'tcx>(
11051105
scrut_ty: Ty<'tcx>,
11061106
) -> Result<UsefulnessReport<'p, 'tcx>, ErrorGuaranteed> {
11071107
let scrut_ty = tycx.reveal_opaque_ty(scrut_ty);
1108+
1109+
// The analysis doesn't support deref patterns mixed with normal constructors; error if present.
1110+
// FIXME(deref_patterns): This only needs to run when a deref pattern was found during lowering.
1111+
if tycx.tcx.features().deref_patterns() {
1112+
let pat_column = PatternColumn::new(arms);
1113+
detect_mixed_deref_pat_ctors(tycx, &pat_column)?;
1114+
}
1115+
11081116
let scrut_validity = PlaceValidity::from_bool(tycx.known_valid_scrutinee);
11091117
let report = compute_match_usefulness(
11101118
tycx,
@@ -1124,6 +1132,47 @@ pub fn analyze_match<'p, 'tcx>(
11241132
Ok(report)
11251133
}
11261134

1135+
fn detect_mixed_deref_pat_ctors<'p, 'tcx>(
1136+
cx: &RustcPatCtxt<'p, 'tcx>,
1137+
column: &PatternColumn<'p, RustcPatCtxt<'p, 'tcx>>,
1138+
) -> Result<(), ErrorGuaranteed> {
1139+
let Some(&ty) = column.head_ty() else {
1140+
return Ok(());
1141+
};
1142+
1143+
// Check for a mix of deref patterns and normal constructors.
1144+
let mut normal_ctor_span = None;
1145+
let mut deref_pat_span = None;
1146+
for pat in column.iter() {
1147+
match pat.ctor() {
1148+
// The analysis can handle mixing deref patterns with wildcards and opaque patterns.
1149+
Wildcard | Opaque(_) => {}
1150+
DerefPattern(_) => deref_pat_span = Some(pat.data().span),
1151+
// Nothing else can be compared to deref patterns in `Constructor::is_covered_by`.
1152+
_ => normal_ctor_span = Some(pat.data().span),
1153+
}
1154+
}
1155+
if let Some(normal_constructor_label) = normal_ctor_span
1156+
&& let Some(deref_pattern_label) = deref_pat_span
1157+
{
1158+
return Err(cx.tcx.dcx().emit_err(errors::MixedDerefPatternConstructors {
1159+
spans: vec![deref_pattern_label, normal_constructor_label],
1160+
smart_pointer_ty: ty.inner(),
1161+
deref_pattern_label,
1162+
normal_constructor_label,
1163+
}));
1164+
}
1165+
1166+
// Specialize and recurse into the patterns' fields.
1167+
let set = column.analyze_ctors(cx, &ty)?;
1168+
for ctor in set.present {
1169+
for specialized_column in column.specialize(cx, &ty, &ctor).iter() {
1170+
detect_mixed_deref_pat_ctors(cx, specialized_column)?;
1171+
}
1172+
}
1173+
Ok(())
1174+
}
1175+
11271176
struct RecursiveOpaque {
11281177
def_id: DefId,
11291178
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
//! Test matches with a mix of ADT constructors and deref patterns. Currently, usefulness analysis
2+
//! doesn't support this, so make sure we catch it beforehand. As a consequence, it takes priority
3+
//! over non-exhaustive match and unreachable pattern errors.
4+
#![feature(deref_patterns)]
5+
#![expect(incomplete_features)]
6+
#![deny(unreachable_patterns)]
7+
8+
use std::borrow::Cow;
9+
10+
fn main() {
11+
let cow: Cow<'static, bool> = Cow::Borrowed(&false);
12+
13+
match cow {
14+
true => {}
15+
//~v ERROR mix of deref patterns and normal constructors
16+
false => {}
17+
Cow::Borrowed(_) => {}
18+
}
19+
20+
match cow {
21+
Cow::Owned(_) => {}
22+
Cow::Borrowed(_) => {}
23+
true => {}
24+
//~^ ERROR mix of deref patterns and normal constructors
25+
}
26+
27+
match cow {
28+
_ => {}
29+
Cow::Owned(_) => {}
30+
false => {}
31+
//~^ ERROR mix of deref patterns and normal constructors
32+
}
33+
34+
match (cow, 0) {
35+
(Cow::Owned(_), 0) => {}
36+
(Cow::Borrowed(_), 0) => {}
37+
(true, 0) => {}
38+
//~^ ERROR mix of deref patterns and normal constructors
39+
}
40+
41+
match (0, cow) {
42+
(0, Cow::Owned(_)) => {}
43+
(0, Cow::Borrowed(_)) => {}
44+
_ => {}
45+
(0, true) => {}
46+
//~^ ERROR mix of deref patterns and normal constructors
47+
}
48+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
error: mix of deref patterns and normal constructors
2+
--> $DIR/mixed-constructors.rs:16:9
3+
|
4+
LL | false => {}
5+
| ^^^^^ matches on the result of dereferencing `Cow<'_, bool>`
6+
LL | Cow::Borrowed(_) => {}
7+
| ^^^^^^^^^^^^^^^^ matches directly on `Cow<'_, bool>`
8+
9+
error: mix of deref patterns and normal constructors
10+
--> $DIR/mixed-constructors.rs:22:9
11+
|
12+
LL | Cow::Borrowed(_) => {}
13+
| ^^^^^^^^^^^^^^^^ matches directly on `Cow<'_, bool>`
14+
LL | true => {}
15+
| ^^^^ matches on the result of dereferencing `Cow<'_, bool>`
16+
17+
error: mix of deref patterns and normal constructors
18+
--> $DIR/mixed-constructors.rs:29:9
19+
|
20+
LL | Cow::Owned(_) => {}
21+
| ^^^^^^^^^^^^^ matches directly on `Cow<'_, bool>`
22+
LL | false => {}
23+
| ^^^^^ matches on the result of dereferencing `Cow<'_, bool>`
24+
25+
error: mix of deref patterns and normal constructors
26+
--> $DIR/mixed-constructors.rs:36:10
27+
|
28+
LL | (Cow::Borrowed(_), 0) => {}
29+
| ^^^^^^^^^^^^^^^^ matches directly on `Cow<'_, bool>`
30+
LL | (true, 0) => {}
31+
| ^^^^ matches on the result of dereferencing `Cow<'_, bool>`
32+
33+
error: mix of deref patterns and normal constructors
34+
--> $DIR/mixed-constructors.rs:43:13
35+
|
36+
LL | (0, Cow::Borrowed(_)) => {}
37+
| ^^^^^^^^^^^^^^^^ matches directly on `Cow<'_, bool>`
38+
LL | _ => {}
39+
LL | (0, true) => {}
40+
| ^^^^ matches on the result of dereferencing `Cow<'_, bool>`
41+
42+
error: aborting due to 5 previous errors
43+

0 commit comments

Comments
 (0)