Skip to content

Commit fb261a1

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 cf43bba commit fb261a1

File tree

5 files changed

+161
-1
lines changed

5 files changed

+161
-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

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

1136+
// FIXME(deref_patterns): Currently it's the responsibility of the frontend (rustc or rust-analyzer)
1137+
// to ensure that deref patterns don't appear in the same column as normal constructors. Deref
1138+
// patterns aren't currently implemented in rust-analyzer, but should they be, the columnwise check
1139+
// here could be made generic and shared between frontends.
1140+
fn detect_mixed_deref_pat_ctors<'p, 'tcx>(
1141+
cx: &RustcPatCtxt<'p, 'tcx>,
1142+
column: &PatternColumn<'p, RustcPatCtxt<'p, 'tcx>>,
1143+
) -> Result<(), ErrorGuaranteed> {
1144+
let Some(&ty) = column.head_ty() else {
1145+
return Ok(());
1146+
};
1147+
1148+
// Check for a mix of deref patterns and normal constructors.
1149+
let mut normal_ctor_span = None;
1150+
let mut deref_pat_span = None;
1151+
for pat in column.iter() {
1152+
match pat.ctor() {
1153+
// The analysis can handle mixing deref patterns with wildcards and opaque patterns.
1154+
Wildcard | Opaque(_) => {}
1155+
DerefPattern(_) => deref_pat_span = Some(pat.data().span),
1156+
// Nothing else can be compared to deref patterns in `Constructor::is_covered_by`.
1157+
_ => normal_ctor_span = Some(pat.data().span),
1158+
}
1159+
}
1160+
if let Some(normal_constructor_label) = normal_ctor_span
1161+
&& let Some(deref_pattern_label) = deref_pat_span
1162+
{
1163+
return Err(cx.tcx.dcx().emit_err(errors::MixedDerefPatternConstructors {
1164+
spans: vec![deref_pattern_label, normal_constructor_label],
1165+
smart_pointer_ty: ty.inner(),
1166+
deref_pattern_label,
1167+
normal_constructor_label,
1168+
}));
1169+
}
1170+
1171+
// Specialize and recurse into the patterns' fields.
1172+
let set = column.analyze_ctors(cx, &ty)?;
1173+
for ctor in set.present {
1174+
for specialized_column in column.specialize(cx, &ty, &ctor).iter() {
1175+
detect_mixed_deref_pat_ctors(cx, specialized_column)?;
1176+
}
1177+
}
1178+
Ok(())
1179+
}
1180+
11281181
struct RecursiveOpaque {
11291182
def_id: DefId,
11301183
}
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+
(1, 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 | (1, 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)