Fix DNF construction, add proptest

This commit is contained in:
Jonas Schievink 2021-08-30 22:26:35 +02:00
parent 20f3792d10
commit e6255356d2
5 changed files with 99 additions and 4 deletions

22
Cargo.lock generated
View file

@ -47,6 +47,15 @@ version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33954243bd79057c2de7338850b85983a44588021f8a5fee574a8888c6de4344" checksum = "33954243bd79057c2de7338850b85983a44588021f8a5fee574a8888c6de4344"
[[package]]
name = "arbitrary"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "577b08a4acd7b99869f863c50011b01eb73424ccc798ecd996f2e24817adfca7"
dependencies = [
"derive_arbitrary",
]
[[package]] [[package]]
name = "arrayvec" name = "arrayvec"
version = "0.7.1" version = "0.7.1"
@ -147,8 +156,10 @@ checksum = "e70cc2f62c6ce1868963827bd677764c62d07c3d9a3e1fb1177ee1a9ab199eb2"
name = "cfg" name = "cfg"
version = "0.0.0" version = "0.0.0"
dependencies = [ dependencies = [
"arbitrary",
"expect-test", "expect-test",
"mbe", "mbe",
"oorandom",
"rustc-hash", "rustc-hash",
"syntax", "syntax",
"tt", "tt",
@ -291,6 +302,17 @@ dependencies = [
"num_cpus", "num_cpus",
] ]
[[package]]
name = "derive_arbitrary"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b24629208e87a2d8b396ff43b15c4afb0a69cea3fbbaa9ed9b92b7c02f0aed73"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "dissimilar" name = "dissimilar"
version = "1.0.2" version = "1.0.2"

View file

@ -17,3 +17,5 @@ tt = { path = "../tt", version = "0.0.0" }
mbe = { path = "../mbe" } mbe = { path = "../mbe" }
syntax = { path = "../syntax" } syntax = { path = "../syntax" }
expect-test = "1.1" expect-test = "1.1"
arbitrary = { version = "1", features = ["derive"] }
oorandom = "11"

View file

@ -50,6 +50,7 @@ impl fmt::Display for CfgAtom {
} }
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(test, derive(arbitrary::Arbitrary))]
pub enum CfgExpr { pub enum CfgExpr {
Invalid, Invalid,
Atom(CfgAtom), Atom(CfgAtom),
@ -128,3 +129,17 @@ fn next_cfg_expr(it: &mut SliceIter<tt::TokenTree>) -> Option<CfgExpr> {
} }
Some(ret) Some(ret)
} }
#[cfg(test)]
impl arbitrary::Arbitrary<'_> for CfgAtom {
fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
match u.int_in_range(0..=1)? {
0 => Ok(CfgAtom::Flag(String::arbitrary(u)?.into())),
1 => Ok(CfgAtom::KeyValue {
key: String::arbitrary(u)?.into(),
value: String::arbitrary(u)?.into(),
}),
_ => unreachable!(),
}
}
}

View file

@ -255,11 +255,11 @@ impl Builder {
fn make_dnf(expr: CfgExpr) -> CfgExpr { fn make_dnf(expr: CfgExpr) -> CfgExpr {
match expr { match expr {
CfgExpr::Invalid | CfgExpr::Atom(_) | CfgExpr::Not(_) => expr, CfgExpr::Invalid | CfgExpr::Atom(_) | CfgExpr::Not(_) => expr,
CfgExpr::Any(e) => CfgExpr::Any(e.into_iter().map(make_dnf).collect()), CfgExpr::Any(e) => flatten(CfgExpr::Any(e.into_iter().map(make_dnf).collect())),
CfgExpr::All(e) => { CfgExpr::All(e) => {
let e = e.into_iter().map(make_nnf).collect::<Vec<_>>(); let e = e.into_iter().map(make_dnf).collect::<Vec<_>>();
CfgExpr::Any(distribute_conj(&e)) flatten(CfgExpr::Any(distribute_conj(&e)))
} }
} }
} }
@ -289,7 +289,7 @@ fn distribute_conj(conj: &[CfgExpr]) -> Vec<CfgExpr> {
} }
} }
let mut out = Vec::new(); let mut out = Vec::new(); // contains only `all()`
let mut with = Vec::new(); let mut with = Vec::new();
go(&mut out, &mut with, conj); go(&mut out, &mut with, conj);
@ -318,3 +318,28 @@ fn make_nnf(expr: CfgExpr) -> CfgExpr {
}, },
} }
} }
/// Collapses nested `any()` and `all()` predicates.
fn flatten(expr: CfgExpr) -> CfgExpr {
match expr {
CfgExpr::All(inner) => CfgExpr::All(
inner
.into_iter()
.flat_map(|e| match e {
CfgExpr::All(inner) => inner,
_ => vec![e],
})
.collect(),
),
CfgExpr::Any(inner) => CfgExpr::Any(
inner
.into_iter()
.flat_map(|e| match e {
CfgExpr::Any(inner) => inner,
_ => vec![e],
})
.collect(),
),
_ => expr,
}
}

View file

@ -1,3 +1,4 @@
use arbitrary::{Arbitrary, Unstructured};
use expect_test::{expect, Expect}; use expect_test::{expect, Expect};
use mbe::syntax_node_to_token_tree; use mbe::syntax_node_to_token_tree;
use syntax::{ast, AstNode}; use syntax::{ast, AstNode};
@ -130,6 +131,18 @@ fn nested() {
check_dnf("#![cfg(not(all(all(a, b))))]", expect![[r#"#![cfg(any(not(a), not(b)))]"#]]); check_dnf("#![cfg(not(all(all(a, b))))]", expect![[r#"#![cfg(any(not(a), not(b)))]"#]]);
} }
#[test]
fn regression() {
check_dnf("#![cfg(all(not(not(any(any(any()))))))]", expect![[r##"#![cfg(any())]"##]]);
check_dnf("#![cfg(all(any(all(any()))))]", expect![[r##"#![cfg(any())]"##]]);
check_dnf("#![cfg(all(all(any())))]", expect![[r##"#![cfg(any())]"##]]);
check_dnf("#![cfg(all(all(any(), x)))]", expect![[r##"#![cfg(any())]"##]]);
check_dnf("#![cfg(all(all(any()), x))]", expect![[r##"#![cfg(any())]"##]]);
check_dnf("#![cfg(all(all(any(x))))]", expect![[r##"#![cfg(x)]"##]]);
check_dnf("#![cfg(all(all(any(x), x)))]", expect![[r##"#![cfg(all(x, x))]"##]]);
}
#[test] #[test]
fn hints() { fn hints() {
let mut opts = CfgOptions::default(); let mut opts = CfgOptions::default();
@ -191,3 +204,21 @@ fn why_inactive() {
expect![["test and test2 are enabled and a is disabled"]], expect![["test and test2 are enabled and a is disabled"]],
); );
} }
#[test]
fn proptest() {
const REPEATS: usize = 512;
let mut rng = oorandom::Rand32::new(123456789);
let mut buf = Vec::new();
for _ in 0..REPEATS {
buf.clear();
while buf.len() < 512 {
buf.extend(rng.rand_u32().to_ne_bytes());
}
let mut u = Unstructured::new(&buf);
let cfg = CfgExpr::arbitrary(&mut u).unwrap();
DnfExpr::new(cfg);
}
}