Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rollup of 8 pull requests #122830

Merged
merged 32 commits into from
Mar 21, 2024
Merged
Changes from 1 commit
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
3dfd0fd
Report arm intersections
Nadrieril Mar 17, 2024
e4487ad
Improve the `WitnessPat: Debug` impl
Nadrieril Mar 4, 2024
d697dd4
Add a crate-custom test harness
Nadrieril Mar 4, 2024
099e716
Factor out `tt` pushes.
nnethercote Mar 19, 2024
b9ead99
Rename `Token::is_path`.
nnethercote Mar 19, 2024
d919dbe
Use `maybe_whole!` to streamline `parse_attr_item`.
nnethercote Mar 19, 2024
8ac16c6
Rewrite `parse_meta_item`.
nnethercote Mar 19, 2024
d4ad322
Use `maybe_whole!` to streamline `parse_item_common`.
nnethercote Mar 19, 2024
0de050b
Use `maybe_whole!` to streamline `parse_stmt_without_recovery`.
nnethercote Mar 19, 2024
0957222
Use better variable names in some `maybe_whole!` calls.
nnethercote Mar 19, 2024
c14d9ae
Fix some formatting.
nnethercote Mar 19, 2024
dbed10a
Fix out-of-date comment.
nnethercote Mar 19, 2024
b7f3b71
Remove non-useful code path.
nnethercote Mar 19, 2024
a94bb2a
Streamline `NamedMatch`.
nnethercote Mar 19, 2024
82a609f
Shrink the comment on `TokenTree`.
nnethercote Mar 19, 2024
23ee523
Remove `CodegenBackend::target_override`.
nnethercote Mar 20, 2024
5744be2
Rename some `target_cfg` variables as `target`.
nnethercote Mar 21, 2024
5568c56
Make `#[diagnostic::on_unimplemented]` format string parsing more robust
weiznich Mar 12, 2024
6623bdf
Strip placeholders from hidden types before remapping generic paramet…
oli-obk Mar 19, 2024
1e926b5
add some comments to hir::ModuleItems
RalfJung Mar 20, 2024
0dd8a83
rename items -> free_items
RalfJung Mar 20, 2024
abb59b2
Remove unnecessary braces from span_bug
compiler-errors Mar 21, 2024
2d63331
Implement macro-based deref!() syntax for deref patterns
compiler-errors Mar 20, 2024
a4db3ff
Don't suggest deref macro since it's unstable
compiler-errors Mar 20, 2024
2e41425
Rollup merge of #122402 - weiznich:fix/122391, r=compiler-errors
matthiaskrgr Mar 21, 2024
8d12621
Rollup merge of #122644 - Nadrieril:complexity-tests, r=compiler-errors
matthiaskrgr Mar 21, 2024
40c972e
Rollup merge of #122733 - oli-obk:error_prop, r=compiler-errors
matthiaskrgr Mar 21, 2024
8b13210
Rollup merge of #122752 - nnethercote:Interpolated-cleanups, r=petroc…
matthiaskrgr Mar 21, 2024
7938ce6
Rollup merge of #122771 - RalfJung:module-items, r=oli-obk
matthiaskrgr Mar 21, 2024
9cd11c4
Rollup merge of #122793 - compiler-errors:deref-pat-syntax, r=Nadrieril
matthiaskrgr Mar 21, 2024
55a9165
Rollup merge of #122810 - nnethercote:rm-target_override, r=WaffleLapkin
matthiaskrgr Mar 21, 2024
6ae51a5
Rollup merge of #122827 - compiler-errors:span-bugs, r=WaffleLapkin
matthiaskrgr Mar 21, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add a crate-custom test harness
Nadrieril committed Mar 19, 2024
commit d697dd44d18fbab9a28032d7b1ceba829600637e
2 changes: 2 additions & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
@@ -4440,6 +4440,8 @@ dependencies = [
"rustc_target",
"smallvec",
"tracing",
"tracing-subscriber",
"tracing-tree",
]

[[package]]
4 changes: 4 additions & 0 deletions compiler/rustc_pattern_analysis/Cargo.toml
Original file line number Diff line number Diff line change
@@ -22,6 +22,10 @@ smallvec = { version = "1.8.1", features = ["union"] }
tracing = "0.1"
# tidy-alphabetical-end

[dev-dependencies]
tracing-subscriber = { version = "0.3.3", default-features = false, features = ["fmt", "env-filter", "ansi"] }
tracing-tree = "0.2.0"

[features]
default = ["rustc"]
rustc = [
6 changes: 6 additions & 0 deletions compiler/rustc_pattern_analysis/src/lib.rs
Original file line number Diff line number Diff line change
@@ -49,6 +49,12 @@ pub mod index {
}
}

impl<V> FromIterator<V> for IdxContainer<usize, V> {
fn from_iter<T: IntoIterator<Item = V>>(iter: T) -> Self {
Self(iter.into_iter().enumerate().collect())
}
}

#[derive(Debug)]
pub struct IdxSet<T>(pub rustc_hash::FxHashSet<T>);
impl<T: Idx> IdxSet<T> {
315 changes: 315 additions & 0 deletions compiler/rustc_pattern_analysis/tests/common/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,315 @@
use rustc_pattern_analysis::{
constructor::{
Constructor, ConstructorSet, IntRange, MaybeInfiniteInt, RangeEnd, VariantVisibility,
},
usefulness::{PlaceValidity, UsefulnessReport},
Captures, MatchArm, PatCx, PrivateUninhabitedField,
};

/// Sets up `tracing` for easier debugging. Tries to look like the `rustc` setup.
pub fn init_tracing() {
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
use tracing_subscriber::Layer;
let _ = tracing_tree::HierarchicalLayer::default()
.with_writer(std::io::stderr)
.with_indent_lines(true)
.with_ansi(true)
.with_targets(true)
.with_indent_amount(2)
.with_subscriber(
tracing_subscriber::Registry::default()
.with(tracing_subscriber::EnvFilter::from_default_env()),
)
.try_init();
}

/// A simple set of types.
#[allow(dead_code)]
#[derive(Debug, Copy, Clone)]
pub enum Ty {
/// Booleans
Bool,
/// 8-bit unsigned integers
U8,
/// Tuples.
Tuple(&'static [Ty]),
/// A struct with `arity` fields of type `ty`.
BigStruct { arity: usize, ty: &'static Ty },
/// A enum with `arity` variants of type `ty`.
BigEnum { arity: usize, ty: &'static Ty },
}

/// The important logic.
impl Ty {
pub fn sub_tys(&self, ctor: &Constructor<Cx>) -> Vec<Self> {
use Constructor::*;
match (ctor, *self) {
(Struct, Ty::Tuple(tys)) => tys.iter().copied().collect(),
(Struct, Ty::BigStruct { arity, ty }) => (0..arity).map(|_| *ty).collect(),
(Variant(_), Ty::BigEnum { ty, .. }) => vec![*ty],
(Bool(..) | IntRange(..) | NonExhaustive | Missing | Wildcard, _) => vec![],
_ => panic!("Unexpected ctor {ctor:?} for type {self:?}"),
}
}

pub fn ctor_set(&self) -> ConstructorSet<Cx> {
match *self {
Ty::Bool => ConstructorSet::Bool,
Ty::U8 => ConstructorSet::Integers {
range_1: IntRange::from_range(
MaybeInfiniteInt::new_finite_uint(0),
MaybeInfiniteInt::new_finite_uint(255),
RangeEnd::Included,
),
range_2: None,
},
Ty::Tuple(..) | Ty::BigStruct { .. } => ConstructorSet::Struct { empty: false },
Ty::BigEnum { arity, .. } => ConstructorSet::Variants {
variants: (0..arity).map(|_| VariantVisibility::Visible).collect(),
non_exhaustive: false,
},
}
}

pub fn write_variant_name(
&self,
f: &mut std::fmt::Formatter<'_>,
ctor: &Constructor<Cx>,
) -> std::fmt::Result {
match (*self, ctor) {
(Ty::Tuple(..), _) => Ok(()),
(Ty::BigStruct { .. }, _) => write!(f, "BigStruct"),
(Ty::BigEnum { .. }, Constructor::Variant(i)) => write!(f, "BigEnum::Variant{i}"),
_ => write!(f, "{:?}::{:?}", self, ctor),
}
}
}

/// Compute usefulness in our simple context (and set up tracing for easier debugging).
pub fn compute_match_usefulness<'p>(
arms: &[MatchArm<'p, Cx>],
ty: Ty,
scrut_validity: PlaceValidity,
complexity_limit: Option<usize>,
) -> Result<UsefulnessReport<'p, Cx>, ()> {
init_tracing();
rustc_pattern_analysis::usefulness::compute_match_usefulness(
&Cx,
arms,
ty,
scrut_validity,
complexity_limit,
)
}

#[derive(Debug)]
pub struct Cx;

/// The context for pattern analysis. Forwards anything interesting to `Ty` methods.
impl PatCx for Cx {
type Ty = Ty;
type Error = ();
type VariantIdx = usize;
type StrLit = ();
type ArmData = ();
type PatData = ();

fn is_exhaustive_patterns_feature_on(&self) -> bool {
false
}

fn is_min_exhaustive_patterns_feature_on(&self) -> bool {
false
}

fn ctor_arity(&self, ctor: &Constructor<Self>, ty: &Self::Ty) -> usize {
ty.sub_tys(ctor).len()
}

fn ctor_sub_tys<'a>(
&'a self,
ctor: &'a Constructor<Self>,
ty: &'a Self::Ty,
) -> impl Iterator<Item = (Self::Ty, PrivateUninhabitedField)> + ExactSizeIterator + Captures<'a>
{
ty.sub_tys(ctor).into_iter().map(|ty| (ty, PrivateUninhabitedField(false)))
}

fn ctors_for_ty(&self, ty: &Self::Ty) -> Result<ConstructorSet<Self>, Self::Error> {
Ok(ty.ctor_set())
}

fn write_variant_name(
f: &mut std::fmt::Formatter<'_>,
ctor: &Constructor<Self>,
ty: &Self::Ty,
) -> std::fmt::Result {
ty.write_variant_name(f, ctor)
}

fn bug(&self, fmt: std::fmt::Arguments<'_>) -> Self::Error {
panic!("{}", fmt)
}

/// Abort when reaching the complexity limit. This is what we'll check in tests.
fn complexity_exceeded(&self) -> Result<(), Self::Error> {
Err(())
}
}

/// Construct a single pattern; see `pats!()`.
#[allow(unused_macros)]
macro_rules! pat {
($($rest:tt)*) => {{
let mut vec = pats!($($rest)*);
vec.pop().unwrap()
}};
}

/// A macro to construct patterns. Called like `pats!(type_expr; pattern, pattern, ..)` and returns
/// a `Vec<DeconstructedPat>`. A pattern can be nested and looks like `Constructor(pat, pat)` or
/// `Constructor { .i: pat, .j: pat }`, where `Constructor` is `Struct`, `Variant.i` (with index
/// `i`), as well as booleans and integer ranges.
///
/// The general structure of the macro is a tt-muncher with several stages identified with
/// `@something(args)`. The args are a key-value list (the keys ensure we don't mix the arguments
/// around) which is passed down and modified as needed. We then parse token-trees from
/// left-to-right. Non-trivial recursion happens when we parse the arguments to a pattern: we
/// recurse to parse the tokens inside `{..}`/`(..)`, and then we continue parsing anything that
/// follows.
macro_rules! pats {
// Entrypoint
// Parse `type; ..`
($ty:expr; $($rest:tt)*) => {{
#[allow(unused_imports)]
use rustc_pattern_analysis::{
constructor::{Constructor, IntRange, MaybeInfiniteInt, RangeEnd},
pat::DeconstructedPat,
};
let ty = $ty;
// The heart of the macro is designed to push `IndexedPat`s into a `Vec`, so we work around
// that.
let sub_tys = ::std::iter::repeat(&ty);
let mut vec = Vec::new();
pats!(@ctor(vec:vec, sub_tys:sub_tys, idx:0) $($rest)*);
vec.into_iter().map(|ipat| ipat.pat).collect::<Vec<_>>()
}};

// Parse `constructor ..`

(@ctor($($args:tt)*) true $($rest:tt)*) => {{
let ctor = Constructor::Bool(true);
pats!(@pat($($args)*, ctor:ctor) $($rest)*)
}};
(@ctor($($args:tt)*) false $($rest:tt)*) => {{
let ctor = Constructor::Bool(false);
pats!(@pat($($args)*, ctor:ctor) $($rest)*)
}};
(@ctor($($args:tt)*) Struct $($rest:tt)*) => {{
let ctor = Constructor::Struct;
pats!(@pat($($args)*, ctor:ctor) $($rest)*)
}};
(@ctor($($args:tt)*) ( $($fields:tt)* ) $($rest:tt)*) => {{
let ctor = Constructor::Struct; // tuples
pats!(@pat($($args)*, ctor:ctor) ( $($fields)* ) $($rest)*)
}};
(@ctor($($args:tt)*) Variant.$variant:ident $($rest:tt)*) => {{
let ctor = Constructor::Variant($variant);
pats!(@pat($($args)*, ctor:ctor) $($rest)*)
}};
(@ctor($($args:tt)*) Variant.$variant:literal $($rest:tt)*) => {{
let ctor = Constructor::Variant($variant);
pats!(@pat($($args)*, ctor:ctor) $($rest)*)
}};
(@ctor($($args:tt)*) _ $($rest:tt)*) => {{
let ctor = Constructor::Wildcard;
pats!(@pat($($args)*, ctor:ctor) $($rest)*)
}};

// Integers and int ranges
(@ctor($($args:tt)*) $($start:literal)?..$end:literal $($rest:tt)*) => {{
let ctor = Constructor::IntRange(IntRange::from_range(
pats!(@rangeboundary- $($start)?),
pats!(@rangeboundary+ $end),
RangeEnd::Excluded,
));
pats!(@pat($($args)*, ctor:ctor) $($rest)*)
}};
(@ctor($($args:tt)*) $($start:literal)?.. $($rest:tt)*) => {{
let ctor = Constructor::IntRange(IntRange::from_range(
pats!(@rangeboundary- $($start)?),
pats!(@rangeboundary+),
RangeEnd::Excluded,
));
pats!(@pat($($args)*, ctor:ctor) $($rest)*)
}};
(@ctor($($args:tt)*) $($start:literal)?..=$end:literal $($rest:tt)*) => {{
let ctor = Constructor::IntRange(IntRange::from_range(
pats!(@rangeboundary- $($start)?),
pats!(@rangeboundary+ $end),
RangeEnd::Included,
));
pats!(@pat($($args)*, ctor:ctor) $($rest)*)
}};
(@ctor($($args:tt)*) $int:literal $($rest:tt)*) => {{
let ctor = Constructor::IntRange(IntRange::from_range(
pats!(@rangeboundary- $int),
pats!(@rangeboundary+ $int),
RangeEnd::Included,
));
pats!(@pat($($args)*, ctor:ctor) $($rest)*)
}};
// Utility to manage range boundaries.
(@rangeboundary $sign:tt $int:literal) => { MaybeInfiniteInt::new_finite_uint($int) };
(@rangeboundary -) => { MaybeInfiniteInt::NegInfinity };
(@rangeboundary +) => { MaybeInfiniteInt::PosInfinity };

// Parse subfields: `(..)` or `{..}`

// Constructor with no fields, e.g. `bool` or `Variant.1`.
(@pat($($args:tt)*) $(,)?) => {
pats!(@pat($($args)*) {})
};
(@pat($($args:tt)*) , $($rest:tt)*) => {
pats!(@pat($($args)*) {}, $($rest)*)
};
// `(..)` and `{..}` are treated the same.
(@pat($($args:tt)*) ( $($subpat:tt)* ) $($rest:tt)*) => {{
pats!(@pat($($args)*) { $($subpat)* } $($rest)*)
}};
(@pat(vec:$vec:expr, sub_tys:$sub_tys:expr, idx:$idx:expr, ctor:$ctor:expr) { $($fields:tt)* } $($rest:tt)*) => {{
let sub_tys = $sub_tys;
let index = $idx;
// Silly dance to work with both a vec and `iter::repeat()`.
let ty = *(&sub_tys).clone().into_iter().nth(index).unwrap();
let ctor = $ctor;
let ctor_sub_tys = &ty.sub_tys(&ctor);
#[allow(unused_mut)]
let mut fields = Vec::new();
// Parse subpatterns (note the leading comma).
pats!(@fields(idx:0, vec:fields, sub_tys:ctor_sub_tys) ,$($fields)*);
let arity = ctor_sub_tys.len();
let pat = DeconstructedPat::new(ctor, fields, arity, ty, ()).at_index(index);
$vec.push(pat);

// Continue parsing further patterns.
pats!(@fields(idx:index+1, vec:$vec, sub_tys:sub_tys) $($rest)*);
}};

// Parse fields one by one.

// No fields left.
(@fields($($args:tt)*) $(,)?) => {};
// `.i: pat` sets the current index to `i`.
(@fields(idx:$_idx:expr, $($args:tt)*) , .$idx:literal : $($rest:tt)*) => {{
pats!(@ctor($($args)*, idx:$idx) $($rest)*);
}};
(@fields(idx:$_idx:expr, $($args:tt)*) , .$idx:ident : $($rest:tt)*) => {{
pats!(@ctor($($args)*, idx:$idx) $($rest)*);
}};
// Field without an explicit index; we use the current index which gets incremented above.
(@fields(idx:$idx:expr, $($args:tt)*) , $($rest:tt)*) => {{
pats!(@ctor($($args)*, idx:$idx) $($rest)*);
}};
}
109 changes: 109 additions & 0 deletions compiler/rustc_pattern_analysis/tests/complexity.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
//! Test the pattern complexity limit.
use common::*;
use rustc_pattern_analysis::{pat::DeconstructedPat, usefulness::PlaceValidity, MatchArm};

#[macro_use]
mod common;

/// Analyze a match made of these patterns. Ignore the report; we only care whether we exceeded the
/// limit or not.
fn check(patterns: &[DeconstructedPat<Cx>], complexity_limit: usize) -> Result<(), ()> {
let ty = *patterns[0].ty();
let arms: Vec<_> =
patterns.iter().map(|pat| MatchArm { pat, has_guard: false, arm_data: () }).collect();
compute_match_usefulness(arms.as_slice(), ty, PlaceValidity::ValidOnly, Some(complexity_limit))
.map(|_report| ())
}

/// Asserts that analyzing this match takes exactly `complexity` steps.
#[track_caller]
fn assert_complexity(patterns: Vec<DeconstructedPat<Cx>>, complexity: usize) {
assert!(check(&patterns, complexity).is_ok());
assert!(check(&patterns, complexity - 1).is_err());
}

/// Construct a match like:
/// ```ignore(illustrative)
/// match ... {
/// BigStruct { field01: true, .. } => {}
/// BigStruct { field02: true, .. } => {}
/// BigStruct { field03: true, .. } => {}
/// BigStruct { field04: true, .. } => {}
/// ...
/// _ => {}
/// }
/// ```
fn diagonal_match(arity: usize) -> Vec<DeconstructedPat<Cx>> {
let struct_ty = Ty::BigStruct { arity, ty: &Ty::Bool };
let mut patterns = vec![];
for i in 0..arity {
patterns.push(pat!(struct_ty; Struct { .i: true }));
}
patterns.push(pat!(struct_ty; _));
patterns
}

/// Construct a match like:
/// ```ignore(illustrative)
/// match ... {
/// BigStruct { field01: true, .. } => {}
/// BigStruct { field02: true, .. } => {}
/// BigStruct { field03: true, .. } => {}
/// BigStruct { field04: true, .. } => {}
/// ...
/// BigStruct { field01: false, .. } => {}
/// BigStruct { field02: false, .. } => {}
/// BigStruct { field03: false, .. } => {}
/// BigStruct { field04: false, .. } => {}
/// ...
/// _ => {}
/// }
/// ```
fn diagonal_exponential_match(arity: usize) -> Vec<DeconstructedPat<Cx>> {
let struct_ty = Ty::BigStruct { arity, ty: &Ty::Bool };
let mut patterns = vec![];
for i in 0..arity {
patterns.push(pat!(struct_ty; Struct { .i: true }));
}
for i in 0..arity {
patterns.push(pat!(struct_ty; Struct { .i: false }));
}
patterns.push(pat!(struct_ty; _));
patterns
}

#[test]
fn test_diagonal_struct_match() {
// These cases are nicely linear: we check `arity` patterns with exactly one `true`, matching
// in 2 branches each, and a final pattern with all `false`, matching only the `_` branch.
assert_complexity(diagonal_match(20), 41);
assert_complexity(diagonal_match(30), 61);
// This case goes exponential.
assert!(check(&diagonal_exponential_match(10), 10000).is_err());
}

/// Construct a match like:
/// ```ignore(illustrative)
/// match ... {
/// BigEnum::Variant1(_) => {}
/// BigEnum::Variant2(_) => {}
/// BigEnum::Variant3(_) => {}
/// ...
/// _ => {}
/// }
/// ```
fn big_enum(arity: usize) -> Vec<DeconstructedPat<Cx>> {
let enum_ty = Ty::BigEnum { arity, ty: &Ty::Bool };
let mut patterns = vec![];
for i in 0..arity {
patterns.push(pat!(enum_ty; Variant.i));
}
patterns.push(pat!(enum_ty; _));
patterns
}

#[test]
fn test_big_enum() {
// We try 2 branches per variant.
assert_complexity(big_enum(20), 40);
}
77 changes: 77 additions & 0 deletions compiler/rustc_pattern_analysis/tests/exhaustiveness.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
//! Test exhaustiveness checking.
use common::*;
use rustc_pattern_analysis::{
pat::{DeconstructedPat, WitnessPat},
usefulness::PlaceValidity,
MatchArm,
};

#[macro_use]
mod common;

/// Analyze a match made of these patterns.
fn check(patterns: Vec<DeconstructedPat<Cx>>) -> Vec<WitnessPat<Cx>> {
let ty = *patterns[0].ty();
let arms: Vec<_> =
patterns.iter().map(|pat| MatchArm { pat, has_guard: false, arm_data: () }).collect();
let report =
compute_match_usefulness(arms.as_slice(), ty, PlaceValidity::ValidOnly, None).unwrap();
report.non_exhaustiveness_witnesses
}

#[track_caller]
fn assert_exhaustive(patterns: Vec<DeconstructedPat<Cx>>) {
let witnesses = check(patterns);
if !witnesses.is_empty() {
panic!("non-exaustive match: missing {witnesses:?}");
}
}

#[track_caller]
fn assert_non_exhaustive(patterns: Vec<DeconstructedPat<Cx>>) {
let witnesses = check(patterns);
assert!(!witnesses.is_empty())
}

#[test]
fn test_int_ranges() {
let ty = Ty::U8;
assert_exhaustive(pats!(ty;
0..=255,
));
assert_exhaustive(pats!(ty;
0..,
));
assert_non_exhaustive(pats!(ty;
0..255,
));
assert_exhaustive(pats!(ty;
0..255,
255,
));
assert_exhaustive(pats!(ty;
..10,
10..
));
}

#[test]
fn test_nested() {
let ty = Ty::BigStruct { arity: 2, ty: &Ty::BigEnum { arity: 2, ty: &Ty::Bool } };
assert_non_exhaustive(pats!(ty;
Struct(Variant.0, _),
));
assert_exhaustive(pats!(ty;
Struct(Variant.0, _),
Struct(Variant.1, _),
));
assert_non_exhaustive(pats!(ty;
Struct(Variant.0, _),
Struct(_, Variant.0),
));
assert_exhaustive(pats!(ty;
Struct(Variant.0, _),
Struct(_, Variant.0),
Struct(Variant.1, Variant.1),
));
}
69 changes: 69 additions & 0 deletions compiler/rustc_pattern_analysis/tests/intersection.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
//! Test the computation of arm intersections.
use common::*;
use rustc_pattern_analysis::{pat::DeconstructedPat, usefulness::PlaceValidity, MatchArm};

#[macro_use]
mod common;

/// Analyze a match made of these patterns and returns the computed arm intersections.
fn check(patterns: Vec<DeconstructedPat<Cx>>) -> Vec<Vec<usize>> {
let ty = *patterns[0].ty();
let arms: Vec<_> =
patterns.iter().map(|pat| MatchArm { pat, has_guard: false, arm_data: () }).collect();
let report =
compute_match_usefulness(arms.as_slice(), ty, PlaceValidity::ValidOnly, None).unwrap();
report.arm_intersections.into_iter().map(|bitset| bitset.iter().collect()).collect()
}

#[track_caller]
fn assert_intersects(patterns: Vec<DeconstructedPat<Cx>>, intersects: &[&[usize]]) {
let computed_intersects = check(patterns);
assert_eq!(computed_intersects, intersects);
}

#[test]
fn test_int_ranges() {
let ty = Ty::U8;
assert_intersects(
pats!(ty;
0..=100,
100..,
),
&[&[], &[0]],
);
assert_intersects(
pats!(ty;
0..=101,
100..,
),
&[&[], &[0]],
);
assert_intersects(
pats!(ty;
0..100,
100..,
),
&[&[], &[]],
);
}

#[test]
fn test_nested() {
let ty = Ty::Tuple(&[Ty::Bool; 2]);
assert_intersects(
pats!(ty;
(true, true),
(true, _),
(_, true),
),
&[&[], &[0], &[0, 1]],
);
// Here we shortcut because `(true, true)` is irrelevant, so we fail to detect the intersection.
assert_intersects(
pats!(ty;
(true, _),
(_, true),
),
&[&[], &[]],
);
}