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.
89fn 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.
4344impl 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.
143138impl 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