Skip to content

Commit 2e213f8

Browse files
committed
Convert rustc_pattern_analysis integration tests to unit tests.
Because (a) the vast majority of compiler tests are unit tests, and (b) this works better with `unused_crate_dependencies`.
1 parent d09117d commit 2e213f8

File tree

5 files changed

+290
-322
lines changed

5 files changed

+290
-322
lines changed

compiler/rustc_pattern_analysis/src/lib.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55
// tidy-alphabetical-start
66
#![allow(rustc::diagnostic_outside_of_impl)]
77
#![allow(rustc::untranslatable_diagnostic)]
8-
// FIXME
9-
#![allow(unused_crate_dependencies)]
108
#![cfg_attr(feature = "rustc", feature(let_chains))]
119
// tidy-alphabetical-end
1210

@@ -21,6 +19,9 @@ pub mod pat_column;
2119
pub mod rustc;
2220
pub mod usefulness;
2321

22+
#[cfg(test)]
23+
mod tests;
24+
2425
#[cfg(feature = "rustc")]
2526
rustc_fluent_macro::fluent_messages! { "../messages.ftl" }
2627

compiler/rustc_pattern_analysis/tests/common/mod.rs compiler/rustc_pattern_analysis/src/tests.rs

+287-15
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
use rustc_pattern_analysis::constructor::{
1+
use crate::constructor::{
22
Constructor, ConstructorSet, IntRange, MaybeInfiniteInt, RangeEnd, VariantVisibility,
33
};
4-
use rustc_pattern_analysis::usefulness::{PlaceValidity, UsefulnessReport};
5-
use rustc_pattern_analysis::{MatchArm, PatCx, PrivateUninhabitedField};
4+
use crate::pat::{DeconstructedPat, WitnessPat};
5+
use crate::usefulness::{PlaceValidity, UsefulnessReport};
6+
use crate::{MatchArm, PatCx, PrivateUninhabitedField};
67

78
/// Sets up `tracing` for easier debugging. Tries to look like the `rustc` setup.
89
fn init_tracing() {
@@ -24,7 +25,7 @@ fn init_tracing() {
2425
/// A simple set of types.
2526
#[allow(dead_code)]
2627
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
27-
pub(super) enum Ty {
28+
enum Ty {
2829
/// Booleans
2930
Bool,
3031
/// 8-bit unsigned integers
@@ -41,7 +42,7 @@ pub(super) enum Ty {
4142

4243
/// The important logic.
4344
impl Ty {
44-
pub(super) fn sub_tys(&self, ctor: &Constructor<Cx>) -> Vec<Self> {
45+
fn sub_tys(&self, ctor: &Constructor<Cx>) -> Vec<Self> {
4546
use Constructor::*;
4647
match (ctor, *self) {
4748
(Struct, Ty::Tuple(tys)) => tys.iter().copied().collect(),
@@ -120,24 +121,18 @@ impl Ty {
120121
}
121122

122123
/// Compute usefulness in our simple context (and set up tracing for easier debugging).
123-
pub(super) fn compute_match_usefulness<'p>(
124+
fn compute_match_usefulness<'p>(
124125
arms: &[MatchArm<'p, Cx>],
125126
ty: Ty,
126127
scrut_validity: PlaceValidity,
127128
complexity_limit: usize,
128129
) -> Result<UsefulnessReport<'p, Cx>, ()> {
129130
init_tracing();
130-
rustc_pattern_analysis::usefulness::compute_match_usefulness(
131-
&Cx,
132-
arms,
133-
ty,
134-
scrut_validity,
135-
complexity_limit,
136-
)
131+
crate::usefulness::compute_match_usefulness(&Cx, arms, ty, scrut_validity, complexity_limit)
137132
}
138133

139134
#[derive(Debug)]
140-
pub(super) struct Cx;
135+
struct Cx;
141136

142137
/// The context for pattern analysis. Forwards anything interesting to `Ty` methods.
143138
impl PatCx for Cx {
@@ -211,7 +206,7 @@ macro_rules! pats {
211206
// Parse `type; ..`
212207
($ty:expr; $($rest:tt)*) => {{
213208
#[allow(unused_imports)]
214-
use rustc_pattern_analysis::{
209+
use crate::{
215210
constructor::{Constructor, IntRange, MaybeInfiniteInt, RangeEnd},
216211
pat::DeconstructedPat,
217212
};
@@ -341,3 +336,280 @@ macro_rules! pats {
341336
pats!(@ctor($($args)*, idx:$idx) $($rest)*);
342337
}};
343338
}
339+
340+
fn check(
341+
patterns: &[DeconstructedPat<Cx>],
342+
complexity_limit: usize,
343+
) -> Result<UsefulnessReport<'_, Cx>, ()> {
344+
let ty = *patterns[0].ty();
345+
let arms: Vec<_> =
346+
patterns.iter().map(|pat| MatchArm { pat, has_guard: false, arm_data: () }).collect();
347+
compute_match_usefulness(arms.as_slice(), ty, PlaceValidity::ValidOnly, complexity_limit)
348+
}
349+
350+
//---------------------------------------------------------------------------
351+
// Test the pattern complexity limit.
352+
//---------------------------------------------------------------------------
353+
354+
/// Analyze a match made of these patterns. Ignore the report; we only care whether we exceeded the
355+
/// limit or not.
356+
fn check_complexity(patterns: &[DeconstructedPat<Cx>], complexity_limit: usize) -> Result<(), ()> {
357+
check(patterns, complexity_limit).map(|_report| ())
358+
}
359+
360+
/// Asserts that analyzing this match takes exactly `complexity` steps.
361+
#[track_caller]
362+
fn assert_complexity(patterns: Vec<DeconstructedPat<Cx>>, complexity: usize) {
363+
assert!(check_complexity(&patterns, complexity).is_ok());
364+
assert!(check_complexity(&patterns, complexity - 1).is_err());
365+
}
366+
367+
/// Construct a match like:
368+
/// ```ignore(illustrative)
369+
/// match ... {
370+
/// BigStruct { field01: true, .. } => {}
371+
/// BigStruct { field02: true, .. } => {}
372+
/// BigStruct { field03: true, .. } => {}
373+
/// BigStruct { field04: true, .. } => {}
374+
/// ...
375+
/// _ => {}
376+
/// }
377+
/// ```
378+
fn diagonal_match(arity: usize) -> Vec<DeconstructedPat<Cx>> {
379+
let struct_ty = Ty::BigStruct { arity, ty: &Ty::Bool };
380+
let mut patterns = vec![];
381+
for i in 0..arity {
382+
patterns.push(pat!(struct_ty; Struct { .i: true }));
383+
}
384+
patterns.push(pat!(struct_ty; _));
385+
patterns
386+
}
387+
388+
/// Construct a match like:
389+
/// ```ignore(illustrative)
390+
/// match ... {
391+
/// BigStruct { field01: true, .. } => {}
392+
/// BigStruct { field02: true, .. } => {}
393+
/// BigStruct { field03: true, .. } => {}
394+
/// BigStruct { field04: true, .. } => {}
395+
/// ...
396+
/// BigStruct { field01: false, .. } => {}
397+
/// BigStruct { field02: false, .. } => {}
398+
/// BigStruct { field03: false, .. } => {}
399+
/// BigStruct { field04: false, .. } => {}
400+
/// ...
401+
/// _ => {}
402+
/// }
403+
/// ```
404+
fn diagonal_exponential_match(arity: usize) -> Vec<DeconstructedPat<Cx>> {
405+
let struct_ty = Ty::BigStruct { arity, ty: &Ty::Bool };
406+
let mut patterns = vec![];
407+
for i in 0..arity {
408+
patterns.push(pat!(struct_ty; Struct { .i: true }));
409+
}
410+
for i in 0..arity {
411+
patterns.push(pat!(struct_ty; Struct { .i: false }));
412+
}
413+
patterns.push(pat!(struct_ty; _));
414+
patterns
415+
}
416+
417+
#[test]
418+
fn test_diagonal_struct_match() {
419+
// These cases are nicely linear: we check `arity` patterns with exactly one `true`, matching
420+
// in 2 branches each, and a final pattern with all `false`, matching only the `_` branch.
421+
assert_complexity(diagonal_match(20), 41);
422+
assert_complexity(diagonal_match(30), 61);
423+
// This case goes exponential.
424+
assert!(check_complexity(&diagonal_exponential_match(10), 10000).is_err());
425+
}
426+
427+
/// Construct a match like:
428+
/// ```ignore(illustrative)
429+
/// match ... {
430+
/// BigEnum::Variant1(_) => {}
431+
/// BigEnum::Variant2(_) => {}
432+
/// BigEnum::Variant3(_) => {}
433+
/// ...
434+
/// _ => {}
435+
/// }
436+
/// ```
437+
fn big_enum(arity: usize) -> Vec<DeconstructedPat<Cx>> {
438+
let enum_ty = Ty::BigEnum { arity, ty: &Ty::Bool };
439+
let mut patterns = vec![];
440+
for i in 0..arity {
441+
patterns.push(pat!(enum_ty; Variant.i));
442+
}
443+
patterns.push(pat!(enum_ty; _));
444+
patterns
445+
}
446+
447+
#[test]
448+
fn test_big_enum() {
449+
// We try 2 branches per variant.
450+
assert_complexity(big_enum(20), 40);
451+
}
452+
453+
//---------------------------------------------------------------------------
454+
// Test exhaustiveness checking.
455+
//---------------------------------------------------------------------------
456+
457+
/// Analyze a match made of these patterns.
458+
fn check_exhaustiveness(patterns: Vec<DeconstructedPat<Cx>>) -> Vec<WitnessPat<Cx>> {
459+
let report = check(&patterns, usize::MAX).unwrap();
460+
report.non_exhaustiveness_witnesses
461+
}
462+
463+
#[track_caller]
464+
fn assert_exhaustive(patterns: Vec<DeconstructedPat<Cx>>) {
465+
let witnesses = check_exhaustiveness(patterns);
466+
if !witnesses.is_empty() {
467+
panic!("non-exhaustive match: missing {witnesses:?}");
468+
}
469+
}
470+
471+
#[track_caller]
472+
fn assert_non_exhaustive(patterns: Vec<DeconstructedPat<Cx>>) {
473+
let witnesses = check_exhaustiveness(patterns);
474+
assert!(!witnesses.is_empty())
475+
}
476+
477+
#[test]
478+
fn test_int_ranges_exhaustiveness() {
479+
let ty = Ty::U8;
480+
assert_exhaustive(pats!(ty;
481+
0..=255,
482+
));
483+
assert_exhaustive(pats!(ty;
484+
0..,
485+
));
486+
assert_non_exhaustive(pats!(ty;
487+
0..255,
488+
));
489+
assert_exhaustive(pats!(ty;
490+
0..255,
491+
255,
492+
));
493+
assert_exhaustive(pats!(ty;
494+
..10,
495+
10..
496+
));
497+
}
498+
499+
#[test]
500+
fn test_nested_exhaustivenss() {
501+
let ty = Ty::BigStruct { arity: 2, ty: &Ty::BigEnum { arity: 2, ty: &Ty::Bool } };
502+
assert_non_exhaustive(pats!(ty;
503+
Struct(Variant.0, _),
504+
));
505+
assert_exhaustive(pats!(ty;
506+
Struct(Variant.0, _),
507+
Struct(Variant.1, _),
508+
));
509+
assert_non_exhaustive(pats!(ty;
510+
Struct(Variant.0, _),
511+
Struct(_, Variant.0),
512+
));
513+
assert_exhaustive(pats!(ty;
514+
Struct(Variant.0, _),
515+
Struct(_, Variant.0),
516+
Struct(Variant.1, Variant.1),
517+
));
518+
}
519+
520+
#[test]
521+
fn test_empty() {
522+
// `TY = Result<bool, !>`
523+
const TY: Ty = Ty::Enum(&[Ty::Bool, Ty::Enum(&[])]);
524+
assert_exhaustive(pats!(TY;
525+
Variant.0,
526+
));
527+
let ty = Ty::Tuple(&[Ty::Bool, TY]);
528+
assert_exhaustive(pats!(ty;
529+
(true, Variant.0),
530+
(false, Variant.0),
531+
));
532+
}
533+
534+
//---------------------------------------------------------------------------
535+
// Test the computation of arm intersections.
536+
//---------------------------------------------------------------------------
537+
538+
/// Analyze a match made of these patterns and returns the computed arm intersections.
539+
fn check_intersections(patterns: Vec<DeconstructedPat<Cx>>) -> Vec<Vec<usize>> {
540+
let report = check(&patterns, usize::MAX).unwrap();
541+
report.arm_intersections.into_iter().map(|bitset| bitset.iter().collect()).collect()
542+
}
543+
544+
#[track_caller]
545+
fn assert_intersects(patterns: Vec<DeconstructedPat<Cx>>, intersects: &[&[usize]]) {
546+
let computed_intersects = check_intersections(patterns);
547+
assert_eq!(computed_intersects, intersects);
548+
}
549+
550+
#[test]
551+
fn test_int_ranges_intersection() {
552+
let ty = Ty::U8;
553+
assert_intersects(
554+
pats!(ty;
555+
0..=100,
556+
100..,
557+
),
558+
&[&[], &[0]],
559+
);
560+
assert_intersects(
561+
pats!(ty;
562+
0..=101,
563+
100..,
564+
),
565+
&[&[], &[0]],
566+
);
567+
assert_intersects(
568+
pats!(ty;
569+
0..100,
570+
100..,
571+
),
572+
&[&[], &[]],
573+
);
574+
}
575+
576+
#[test]
577+
fn test_nested_intersection() {
578+
let ty = Ty::Tuple(&[Ty::Bool; 2]);
579+
assert_intersects(
580+
pats!(ty;
581+
(true, true),
582+
(true, _),
583+
(_, true),
584+
),
585+
&[&[], &[0], &[0, 1]],
586+
);
587+
// Here we shortcut because `(true, true)` is irrelevant, so we fail to detect the intersection.
588+
assert_intersects(
589+
pats!(ty;
590+
(true, _),
591+
(_, true),
592+
),
593+
&[&[], &[]],
594+
);
595+
let ty = Ty::Tuple(&[Ty::Bool; 3]);
596+
assert_intersects(
597+
pats!(ty;
598+
(true, true, _),
599+
(true, _, true),
600+
(false, _, _),
601+
),
602+
&[&[], &[], &[]],
603+
);
604+
let ty = Ty::Tuple(&[Ty::Bool, Ty::Bool, Ty::U8]);
605+
assert_intersects(
606+
pats!(ty;
607+
(true, _, _),
608+
(_, true, 0..10),
609+
(_, true, 10..),
610+
(_, true, 3),
611+
_,
612+
),
613+
&[&[], &[], &[], &[1], &[0, 1, 2, 3]],
614+
);
615+
}

0 commit comments

Comments
 (0)