#[path = "support/same.rs"]
mod same;
use same::same;
use std::fmt::Write as _;
use syn::visit_mut::VisitMut as _;
fn form(source: &str) -> syn_canon::CanonicalForm {
syn_canon::canonicalize(syn::parse_str(source).unwrap())
}
fn failing(source: &str) -> syn_canon::CanonicalForm {
syn_canon::canonicalize_failing(syn::parse_str(source).unwrap())
}
fn separate(left: &str, right: &str) {
assert_ne!(form(left), form(right), "{left}\n{right}");
assert_ne!(failing(left), failing(right));
}
fn names(count: usize) -> Vec<String> {
(0..count).map(|index| format!("a{index}")).collect()
}
fn params(names: &[&str]) -> String {
names
.iter()
.map(|name| format!("{name}: bool"))
.collect::<Vec<_>>()
.join(", ")
}
fn chain(names: &[&str]) -> String {
names.join(" && ")
}
fn deep_if(leaves: usize) -> String {
let ns = names(leaves);
let refs: Vec<&str> = ns.iter().map(String::as_str).collect();
format!(
"fn f({}) -> bool {{ if true {{ {} }} else {{ false }} }}",
params(&refs),
chain(&refs)
)
}
fn deep_bare(leaves: usize) -> String {
let ns = names(leaves);
let refs: Vec<&str> = ns.iter().map(String::as_str).collect();
format!("fn g({}) -> bool {{ {} }}", params(&refs), chain(&refs))
}
fn inversion(body: &str, negated: bool) -> String {
let (condition, then, otherwise) = if negated {
("!p", body, "false")
} else {
("p", "false", body)
};
format!("fn f(p:bool,q:bool)->bool{{if {condition}{{{then}}}else{{{otherwise}}}}}")
}
fn balanced(count: usize) -> String {
if count == 1 {
return "q".to_owned();
}
format!(
"({} && {})",
balanced(count / 2),
balanced(count - count / 2)
)
}
struct GroupCond {
negated: bool,
attributed: bool,
}
impl syn::visit_mut::VisitMut for GroupCond {
fn visit_expr_if_mut(&mut self, r#if: &mut syn::ExprIf) {
let inner: syn::Expr = syn::parse_str("p && q").unwrap();
let attrs = if self.attributed {
let attr: syn::Attribute = syn::parse_quote!(#[cfg(all())]);
vec![attr]
} else {
Vec::new()
};
let group = syn::Expr::Group(syn::ExprGroup {
attrs,
group_token: syn::token::Group::default(),
expr: Box::new(inner),
});
let expr = if self.negated {
syn::Expr::Unary(syn::ExprUnary {
attrs: Vec::new(),
op: syn::UnOp::Not(syn::token::Not::default()),
expr: Box::new(group),
})
} else {
group
};
*r#if.cond = expr;
}
}
fn grouped(
negated: bool,
attributed: bool,
) -> (syn_canon::CanonicalForm, syn_canon::CanonicalForm) {
let make = |file: &mut syn::File| {
GroupCond {
negated,
attributed,
}
.visit_file_mut(file);
};
let mut file: syn::File =
syn::parse_str("fn f(p: bool, q: bool) -> bool { if p { true } else { false } }").unwrap();
make(&mut file);
let passing = syn_canon::canonicalize(file);
let mut file: syn::File =
syn::parse_str("fn f(p: bool, q: bool) -> bool { if p { true } else { false } }").unwrap();
make(&mut file);
let failing = syn_canon::canonicalize_failing(file);
(passing, failing)
}
fn same_forms(left: (syn_canon::CanonicalForm, syn_canon::CanonicalForm), right: &str) {
let (a, a_failing) = left;
let b = form(right);
assert_eq!(a, b, "{right}");
assert_eq!(a.body_units(), b.body_units());
assert_eq!(a.leaf_tokens(), b.leaf_tokens());
assert_ne!(a_failing, failing(right));
}
fn separate_forms(
left: &(syn_canon::CanonicalForm, syn_canon::CanonicalForm),
right: &(syn_canon::CanonicalForm, syn_canon::CanonicalForm),
) {
assert_ne!(left.0, right.0);
assert_ne!(left.1, right.1);
}
#[test]
fn literal_negation_folds_primitives_and_compositions() {
same("fn f() -> bool { !true }", "fn g() -> bool { false }");
same("fn f() -> bool { !false }", "fn g() -> bool { true }");
same(
"fn f(p: bool) -> bool { !true && p }",
"fn g(p: bool) -> bool { false && p }",
);
same(
"fn f(p: bool) -> bool { !(true && p) }",
"fn g(p: bool) -> bool { false || !p }",
);
separate("fn f() -> bool { !true }", "fn g() -> bool { true }");
separate(
"fn f(p: bool) -> bool { !true && p }",
"fn g(p: bool) -> bool { true && p }",
);
}
#[test]
fn grouped_total_conditions_fold_like_source_parens() {
same(
"fn f(p: bool, q: bool) -> bool { if (p && q) { true } else { false } }",
"fn g(x: bool, y: bool) -> bool { x && y }",
);
same_forms(
grouped(false, false),
"fn g(x: bool, y: bool) -> bool { x && y }",
);
same_forms(
grouped(true, false),
"fn g(x: bool, y: bool) -> bool { !x || !y }",
);
separate_forms(&grouped(true, true), &grouped(true, false));
}
#[test]
fn active_attributes_keep_the_condition_retained() {
separate(
"fn f(p: bool) -> bool { #[cfg(all())] if p { true } else { false } }",
"fn g(x: bool) -> bool { x }",
);
separate(
"fn f(p: bool) -> bool { if !p { #[cfg(all())] true } else { false } }",
"fn g(x: bool) -> bool { if x { false } else { true } }",
);
separate(
"fn f(p: bool) -> u8 { if !(#[cfg(all())] p) { 1u8 } else { 2u8 } }",
"fn g(x: bool) -> u8 { if x { 2u8 } else { 1u8 } }",
);
separate(
"fn f(p: bool) -> bool { if p { #[cfg(all())] true } else { false } }",
"fn g(x: bool) -> bool { x }",
);
}
#[test]
fn branch_local_items_keep_the_condition_retained() {
separate(
"fn f(p: bool) -> bool { if p { fn inner(q: bool) -> bool { q } inner(p) } else { false } }",
"fn g(x: bool) -> bool { x }",
);
let left = "fn f(p: bool) -> bool { if p { fn inner(q: bool) -> bool { q } inner(p) } else { false } }";
let right = "fn g(p: bool) -> bool { if p { fn local(r: bool) -> bool { r } local(p) } else { false } }";
assert_eq!(form(left), form(right));
assert_eq!(failing(left), failing(right));
separate(
"fn f(p: bool) -> bool { if p { const LIM: i32 = 3; p } else { false } }",
"fn g(x: bool) -> bool { x }",
);
}
#[test]
fn branch_block_depth_bounds_retain_the_complete_conditional() {
for (nested, leaves, admitted) in [
(63, 1, true),
(64, 1, false),
(63, 64, true),
(63, 65, false),
] {
let mut body = vec!["q"; leaves].join(" && ");
for _ in 0..nested {
body = format!("{{{body}}}");
}
let original = inversion(&body, true);
let oriented = inversion(&body, false);
if admitted {
same(&original, &oriented);
} else {
separate(&original, &oriented);
}
}
}
#[test]
fn branch_node_bounds_retain_the_complete_conditional() {
let statements = |count| format!("{}q", "0u8;".repeat(count));
let items = |count| {
let mut body = String::new();
for index in 0..count {
write!(body, "const C{index}:u8=0;").unwrap();
}
body.push('q');
body
};
let expressions = |count| format!("{}const C:u8=0;q", "0u8;".repeat(count));
for body in [statements(8190), items(5460), expressions(8188)] {
same(&inversion(&body, true), &inversion(&body, false));
}
for body in [statements(8193), items(5462), expressions(8191)] {
separate(&inversion(&body, true), &inversion(&body, false));
}
}
#[test]
fn unit_branch_final_items_obey_the_shared_node_bound() {
for (count, admitted) in [(8189, true), (8190, true), (8191, false)] {
let padding = "0u8;".repeat(count);
let original = format!("fn f(p:bool){{if !p{{{padding}}}else{{0u8;struct Marker;}}}}");
let oriented = format!("fn g(x:bool){{if x{{0u8;struct Marker;}}else{{{padding}}}}}");
if admitted {
same(&original, &oriented);
} else {
separate(&original, &oriented);
}
}
}
#[test]
fn deep_boolean_tails_fold_up_to_the_expression_bound() {
same(&deep_if(64), &deep_bare(64));
separate(&deep_if(65), &deep_bare(65));
}
#[test]
fn branch_expression_depth_bounds_retain_the_complete_conditional() {
let body = |count| vec!["q"; count].join(" && ");
same(&inversion(&body(64), true), &inversion(&body(64), false));
separate(&inversion(&body(65), true), &inversion(&body(65), false));
}
#[test]
fn wide_boolean_conditions_retain_the_complete_conditional_at_the_node_bound() {
for (count, admitted) in [(8191, true), (8192, false)] {
let condition = balanced(count);
let original = format!("fn f(q:bool)->bool{{if {condition}{{true}}else{{false}}}}");
let folded = format!("fn g(q:bool)->bool{{{condition}}}");
if admitted {
same(&original, &folded);
} else {
separate(&original, &folded);
}
}
}
#[test]
fn oriented_comparisons_reorient_in_every_condition() {
same(
"fn f(p: i32, q: i32) -> bool { if q > p { true } else { false } }",
"fn g(x: i32, y: i32) -> bool { x < y }",
);
same(
"fn f(p: i32, q: i32) -> bool { if q >= p { true } else { false } }",
"fn g(x: i32, y: i32) -> bool { x <= y }",
);
same(
"fn f(p: i32, q: i32) -> u32 { if q > p { 1u32 } else { 2u32 } }",
"fn g(p: i32, q: i32) -> u32 { if p < q { 1u32 } else { 2u32 } }",
);
}
#[test]
fn negated_future_reads_share_their_contexts() {
same(
"fn f(p: bool, q: bool) -> (bool, bool) { let a = p; let b = q; println!(\"barrier\"); let c = !(a && b); let d = q; (c, d) }",
"fn g(x: bool, y: bool) -> (bool, bool) { let a = x; let b = y; println!(\"barrier\"); let c = !a || !b; let d = y; (c, d) }",
);
same(
"fn f(p: bool, q: bool) -> (bool, bool) { let a = p; let b = q; println!(\"barrier\"); let c = !!a; let d = b; (c, d) }",
"fn g(p: bool, q: bool) -> (bool, bool) { let a = p; let b = q; println!(\"barrier\"); let c = a; let d = b; (c, d) }",
);
separate(
"fn f(p: bool, q: bool) -> (bool, bool) { let a = p; let b = q; println!(\"barrier\"); let c = !a; let d = b; (c, d) }",
"fn g(p: bool, q: bool) -> (bool, bool) { let a = p; let b = q; println!(\"barrier\"); let c = a; let d = b; (c, d) }",
);
}