Skip to content

Commit 377ed5e

Browse files
committed
Auto merge of rust-lang#140106 - dianne:deref-pat-usefulness, r=<try>
allow deref patterns to participate in exhaustiveness analysis Per [this proposal](https://hackmd.io/4qDDMcvyQ-GDB089IPcHGg#Exhaustiveness), this PR allows deref patterns to participate in exhaustiveness analysis. Currently all deref patterns enforce `DerefPure` bounds on their scrutinees, so this assumes all patterns it's analyzing are well-behaved. This also doesn't support [mixed exhaustiveness](https://hackmd.io/4qDDMcvyQ-GDB089IPcHGg#Mixed-exhaustiveness), and instead emits an error if deref patterns are used together with normal constructors. I think mixed exhaustiveness would be nice to have (especially if we eventually want to support arbitrary `Deref` impls[^1]), but it'd require more work to get reasonable diagnostics[^2]. Tracking issue for deref patterns: rust-lang#87121 r? `@Nadrieril` [^1]: Regardless of whether we support limited exhaustiveness checking for untrusted `Deref` or always require other arms to be exhaustive, I think it'd be useful to allow mixed matching for user-defined smart pointers. And it'd be strange if it worked there but not for `Cow`. [^2]: I think listing out witnesses of non-exhaustiveness can be confusing when they're not necessarily disjoint, and when you only need to cover some of them, so we'd probably want special formatting and/or explanatory subdiagnostics. And if it's implemented similarly to unions, we'd probably also want some way of merging witnesses; the way witnesses for unions can appear duplicated is pretty unfortunate. I'm not sure yet how the diagnostics should look, especially for deeply nested patterns.
2 parents 1a95cc6 + 1a76c9d commit 377ed5e

File tree

19 files changed

+475
-25
lines changed

19 files changed

+475
-25
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/constructor.rs

+22
Original file line numberDiff line numberDiff line change
@@ -696,6 +696,10 @@ pub enum Constructor<Cx: PatCx> {
696696
F128Range(IeeeFloat<QuadS>, IeeeFloat<QuadS>, RangeEnd),
697697
/// String literals. Strings are not quite the same as `&[u8]` so we treat them separately.
698698
Str(Cx::StrLit),
699+
/// Deref patterns (enabled by the `deref_patterns` feature) provide a way of matching on a
700+
/// smart pointer ADT through its pointee. They don't directly correspond to ADT constructors,
701+
/// and currently are not supported alongside them. Carries the type of the pointee.
702+
DerefPattern(Cx::Ty),
699703
/// Constants that must not be matched structurally. They are treated as black boxes for the
700704
/// purposes of exhaustiveness: we must not inspect them, and they don't count towards making a
701705
/// match exhaustive.
@@ -740,6 +744,7 @@ impl<Cx: PatCx> Clone for Constructor<Cx> {
740744
Constructor::F64Range(lo, hi, end) => Constructor::F64Range(*lo, *hi, *end),
741745
Constructor::F128Range(lo, hi, end) => Constructor::F128Range(*lo, *hi, *end),
742746
Constructor::Str(value) => Constructor::Str(value.clone()),
747+
Constructor::DerefPattern(ty) => Constructor::DerefPattern(ty.clone()),
743748
Constructor::Opaque(inner) => Constructor::Opaque(inner.clone()),
744749
Constructor::Or => Constructor::Or,
745750
Constructor::Never => Constructor::Never,
@@ -856,6 +861,10 @@ impl<Cx: PatCx> Constructor<Cx> {
856861
}
857862
(Slice(self_slice), Slice(other_slice)) => self_slice.is_covered_by(*other_slice),
858863

864+
// Deref patterns only interact with other deref patterns. Prior to usefulness analysis,
865+
// we ensure they don't appear alongside any other non-wild non-opaque constructors.
866+
(DerefPattern(_), DerefPattern(_)) => true,
867+
859868
// Opaque constructors don't interact with anything unless they come from the
860869
// syntactically identical pattern.
861870
(Opaque(self_id), Opaque(other_id)) => self_id == other_id,
@@ -932,6 +941,7 @@ impl<Cx: PatCx> Constructor<Cx> {
932941
F64Range(lo, hi, end) => write!(f, "{lo}{end}{hi}")?,
933942
F128Range(lo, hi, end) => write!(f, "{lo}{end}{hi}")?,
934943
Str(value) => write!(f, "{value:?}")?,
944+
DerefPattern(_) => write!(f, "deref!({:?})", fields.next().unwrap())?,
935945
Opaque(..) => write!(f, "<constant pattern>")?,
936946
Or => {
937947
for pat in fields {
@@ -1039,15 +1049,27 @@ impl<Cx: PatCx> ConstructorSet<Cx> {
10391049
let mut missing = Vec::new();
10401050
// Constructors in `ctors`, except wildcards and opaques.
10411051
let mut seen = Vec::new();
1052+
// If we see a deref pattern, it must be the only non-wildcard non-opaque constructor; we
1053+
// ensure this prior to analysis.
1054+
let mut deref_pat_present = false;
10421055
for ctor in ctors.cloned() {
10431056
match ctor {
1057+
DerefPattern(..) => {
1058+
if !deref_pat_present {
1059+
deref_pat_present = true;
1060+
present.push(ctor);
1061+
}
1062+
}
10441063
Opaque(..) => present.push(ctor),
10451064
Wildcard => {} // discard wildcards
10461065
_ => seen.push(ctor),
10471066
}
10481067
}
10491068

10501069
match self {
1070+
_ if deref_pat_present => {
1071+
// Deref patterns are the only constructor; nothing is missing.
1072+
}
10511073
ConstructorSet::Struct { empty } => {
10521074
if !seen.is_empty() {
10531075
present.push(Struct);

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

+60-6
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,7 @@ impl<'p, 'tcx: 'p> RustcPatCtxt<'p, 'tcx> {
269269
}
270270
_ => bug!("bad slice pattern {:?} {:?}", ctor, ty),
271271
},
272+
DerefPattern(pointee_ty) => reveal_and_alloc(cx, once(pointee_ty.inner())),
272273
Bool(..) | IntRange(..) | F16Range(..) | F32Range(..) | F64Range(..)
273274
| F128Range(..) | Str(..) | Opaque(..) | Never | NonExhaustive | Hidden | Missing
274275
| PrivateUninhabited | Wildcard => &[],
@@ -296,7 +297,7 @@ impl<'p, 'tcx: 'p> RustcPatCtxt<'p, 'tcx> {
296297
}
297298
_ => bug!("Unexpected type for constructor `{ctor:?}`: {ty:?}"),
298299
},
299-
Ref => 1,
300+
Ref | DerefPattern(_) => 1,
300301
Slice(slice) => slice.arity(),
301302
Bool(..) | IntRange(..) | F16Range(..) | F32Range(..) | F64Range(..)
302303
| F128Range(..) | Str(..) | Opaque(..) | Never | NonExhaustive | Hidden | Missing
@@ -493,11 +494,14 @@ impl<'p, 'tcx: 'p> RustcPatCtxt<'p, 'tcx> {
493494
),
494495
};
495496
}
496-
PatKind::DerefPattern { .. } => {
497-
// FIXME(deref_patterns): At least detect that `box _` is irrefutable.
498-
fields = vec![];
499-
arity = 0;
500-
ctor = Opaque(OpaqueId::new());
497+
PatKind::DerefPattern { subpattern, .. } => {
498+
// NB(deref_patterns): This assumes the deref pattern is matching on a trusted
499+
// `DerefPure` type. If the `Deref` impl isn't trusted, any deref pattern that can
500+
// fail (possibly due to expanding or-patterns inside it) must not influence
501+
// exhaustiveness analysis.
502+
fields = vec![self.lower_pat(subpattern).at_index(0)];
503+
arity = 1;
504+
ctor = DerefPattern(cx.reveal_opaque_ty(subpattern.ty));
501505
}
502506
PatKind::Leaf { subpatterns } | PatKind::Variant { subpatterns, .. } => {
503507
match ty.kind() {
@@ -874,6 +878,7 @@ impl<'p, 'tcx: 'p> RustcPatCtxt<'p, 'tcx> {
874878
print::write_ref_like(&mut s, pat.ty().inner(), &print(&pat.fields[0])).unwrap();
875879
s
876880
}
881+
DerefPattern(_) => format!("deref!({})", print(&pat.fields[0])),
877882
Slice(slice) => {
878883
let (prefix_len, has_dot_dot) = match slice.kind {
879884
SliceKind::FixedLen(len) => (len, false),
@@ -1100,6 +1105,14 @@ pub fn analyze_match<'p, 'tcx>(
11001105
scrut_ty: Ty<'tcx>,
11011106
) -> Result<UsefulnessReport<'p, 'tcx>, ErrorGuaranteed> {
11021107
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+
11031116
let scrut_validity = PlaceValidity::from_bool(tycx.known_valid_scrutinee);
11041117
let report = compute_match_usefulness(
11051118
tycx,
@@ -1119,6 +1132,47 @@ pub fn analyze_match<'p, 'tcx>(
11191132
Ok(report)
11201133
}
11211134

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+
11221176
struct RecursiveOpaque {
11231177
def_id: DefId,
11241178
}

compiler/rustc_pattern_analysis/src/usefulness.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -702,6 +702,7 @@
702702
//! - `ui/consts/const_in_pattern`
703703
//! - `ui/rfc-2008-non-exhaustive`
704704
//! - `ui/half-open-range-patterns`
705+
//! - `ui/pattern/deref-patterns`
705706
//! - probably many others
706707
//!
707708
//! I (Nadrieril) prefer to put new tests in `ui/pattern/usefulness` unless there's a specific
@@ -866,7 +867,8 @@ impl PlaceValidity {
866867
/// inside `&` and union fields where validity is reset to `MaybeInvalid`.
867868
fn specialize<Cx: PatCx>(self, ctor: &Constructor<Cx>) -> Self {
868869
// We preserve validity except when we go inside a reference or a union field.
869-
if matches!(ctor, Constructor::Ref | Constructor::UnionField) {
870+
if matches!(ctor, Constructor::Ref | Constructor::DerefPattern(_) | Constructor::UnionField)
871+
{
870872
// Validity of `x: &T` does not imply validity of `*x: T`.
871873
MaybeInvalid
872874
} else {

src/doc/unstable-book/src/language-features/deref-patterns.md

+1-2
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,7 @@ Like [`box_patterns`], deref patterns may move out of boxes:
6060
# #![feature(deref_patterns)]
6161
# #![allow(incomplete_features)]
6262
struct NoCopy;
63-
// Match exhaustiveness analysis is not yet implemented.
64-
let deref!(x) = Box::new(NoCopy) else { unreachable!() };
63+
let deref!(x) = Box::new(NoCopy);
6564
drop::<NoCopy>(x);
6665
```
6766

src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/match_check/pat_analysis.rs

+3
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,7 @@ impl<'db> MatchCheckCtx<'db> {
301301
// ignore this issue.
302302
Ref => PatKind::Deref { subpattern: subpatterns.next().unwrap() },
303303
Slice(_) => unimplemented!(),
304+
DerefPattern(_) => unimplemented!(),
304305
&Str(void) => match void {},
305306
Wildcard | NonExhaustive | Hidden | PrivateUninhabited => PatKind::Wild,
306307
Never => PatKind::Never,
@@ -351,6 +352,7 @@ impl PatCx for MatchCheckCtx<'_> {
351352
},
352353
Ref => 1,
353354
Slice(..) => unimplemented!(),
355+
DerefPattern(..) => unimplemented!(),
354356
Never | Bool(..) | IntRange(..) | F16Range(..) | F32Range(..) | F64Range(..)
355357
| F128Range(..) | Str(..) | Opaque(..) | NonExhaustive | PrivateUninhabited
356358
| Hidden | Missing | Wildcard => 0,
@@ -411,6 +413,7 @@ impl PatCx for MatchCheckCtx<'_> {
411413
}
412414
},
413415
Slice(_) => unreachable!("Found a `Slice` constructor in match checking"),
416+
DerefPattern(_) => unreachable!("Found a `DerefPattern` constructor in match checking"),
414417
Never | Bool(..) | IntRange(..) | F16Range(..) | F32Range(..) | F64Range(..)
415418
| F128Range(..) | Str(..) | Opaque(..) | NonExhaustive | PrivateUninhabited
416419
| Hidden | Missing | Wildcard => {

tests/ui/pattern/deref-patterns/bindings.rs

-2
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ fn simple_vec(vec: Vec<u32>) -> u32 {
1313
deref!([x]) => x,
1414
deref!([1, x]) => x + 200,
1515
deref!(ref slice) => slice.iter().sum(),
16-
_ => 2000,
1716
}
1817
}
1918

@@ -25,7 +24,6 @@ fn simple_vec(vec: Vec<u32>) -> u32 {
2524
[x] => x,
2625
[1, x] => x + 200,
2726
deref!(ref slice) => slice.iter().sum(),
28-
_ => 2000,
2927
}
3028
}
3129

tests/ui/pattern/deref-patterns/closure_capture.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ struct NoCopy;
99
fn main() {
1010
let b = Rc::new("aaa".to_string());
1111
let f = || {
12-
let deref!(ref s) = b else { unreachable!() };
12+
let deref!(ref s) = b;
1313
assert_eq!(s.len(), 3);
1414
};
1515
assert_eq!(b.len(), 3);
@@ -26,7 +26,7 @@ fn main() {
2626

2727
let mut b = "aaa".to_string();
2828
let mut f = || {
29-
let deref!(ref mut s) = b else { unreachable!() };
29+
let deref!(ref mut s) = b;
3030
s.make_ascii_uppercase();
3131
};
3232
f();
@@ -53,15 +53,15 @@ fn main() {
5353
let b = Box::new(NoCopy);
5454
let f = || {
5555
// this should move out of the box rather than borrow.
56-
let deref!(x) = b else { unreachable!() };
56+
let deref!(x) = b;
5757
drop::<NoCopy>(x);
5858
};
5959
f();
6060

6161
let b = Box::new((NoCopy,));
6262
let f = || {
6363
// this should move out of the box rather than borrow.
64-
let (x,) = b else { unreachable!() };
64+
let (x,) = b;
6565
drop::<NoCopy>(x);
6666
};
6767
f();

tests/ui/pattern/deref-patterns/deref-box.rs

+5-5
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,18 @@
66
#![expect(incomplete_features)]
77

88
fn unbox_1<T>(b: Box<T>) -> T {
9-
let deref!(x) = b else { unreachable!() };
9+
let deref!(x) = b;
1010
x
1111
}
1212

1313
fn unbox_2<T>(b: Box<(T,)>) -> T {
14-
let (x,) = b else { unreachable!() };
14+
let (x,) = b;
1515
x
1616
}
1717

1818
fn unbox_separately<T>(b: Box<(T, T)>) -> (T, T) {
19-
let (x, _) = b else { unreachable!() };
20-
let (_, y) = b else { unreachable!() };
19+
let (x, _) = b;
20+
let (_, y) = b;
2121
(x, y)
2222
}
2323

@@ -31,7 +31,7 @@ fn main() {
3131

3232
// test that borrowing from a box also works
3333
let mut b = "hi".to_owned().into_boxed_str();
34-
let deref!(ref mut s) = b else { unreachable!() };
34+
let deref!(ref mut s) = b;
3535
s.make_ascii_uppercase();
3636
assert_eq!(&*b, "HI");
3737
}

tests/ui/pattern/deref-patterns/implicit-cow-deref.rs

-4
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ fn main() {
1111

1212
match cow {
1313
[..] => {}
14-
_ => unreachable!(),
1514
}
1615

1716
match cow {
@@ -22,14 +21,12 @@ fn main() {
2221
match Rc::new(&cow) {
2322
Cow::Borrowed { 0: _ } => {}
2423
Cow::Owned { 0: _ } => unreachable!(),
25-
_ => unreachable!(),
2624
}
2725

2826
let cow_of_cow: Cow<'_, Cow<'static, [u8]>> = Cow::Owned(cow);
2927

3028
match cow_of_cow {
3129
[..] => {}
32-
_ => unreachable!(),
3330
}
3431

3532
// This matches on the outer `Cow` (the owned one).
@@ -41,6 +38,5 @@ fn main() {
4138
match Rc::new(&cow_of_cow) {
4239
Cow::Borrowed { 0: _ } => unreachable!(),
4340
Cow::Owned { 0: _ } => {}
44-
_ => unreachable!(),
4541
}
4642
}

0 commit comments

Comments
 (0)