use crate::test_support::{function_space, space_verbatim};
use crate::{LANG, Metric, MetricsOptions};
const SLOT: &str = "PRED";
#[track_caller]
fn assert_rows(lang: LANG, template: &str, rows: &[(&str, u64, u64)]) {
assert!(template.contains(SLOT), "template has no predicate slot");
let options = MetricsOptions::default().with_only(&[Metric::Abc, Metric::Cyclomatic]);
for &(predicate, conditions, decisions) in rows {
let source = template.replace(SLOT, predicate);
let root = space_verbatim(lang, source.as_bytes(), options);
let f = function_space(&root, "f");
assert_eq!(
(
f.metrics.abc.conditions(),
f.metrics.cyclomatic.cyclomatic() - 1
),
(conditions, decisions),
"(abc.conditions, decisions) of `{predicate}` in {lang:?}\n source: {source}"
);
}
}
#[cfg(any(
feature = "csharp",
feature = "groovy",
feature = "perl",
feature = "ruby",
feature = "elixir"
))]
#[track_caller]
fn assert_level_with_control(
lang: LANG,
[if_slot, chain_slot]: [&str; 2],
control: &str,
constructs: &[&str],
) {
let predicates = || std::iter::once(control).chain(constructs.iter().copied());
let in_if: Vec<_> = predicates().map(|p| (p, 1, 1)).collect();
assert_rows(lang, if_slot, &in_if);
let in_chain: Vec<_> = predicates().map(|p| (p, 2, 2)).collect();
assert_rows(lang, chain_slot, &in_chain);
}
#[test]
#[cfg(feature = "python")]
fn python_slots_pay_for_any_predicate() {
let if_rows = &[
("b", 1, 1),
("-b", 1, 1),
("~b", 1, 1),
("b + 1", 1, 1),
("lambda: b", 1, 1),
("(y := b)", 1, 1),
("(y := b > 1)", 1, 1),
("b > 1", 1, 1),
("not b", 1, 1),
("not (b > 1)", 1, 1),
("not a > b", 1, 1),
("not not b", 1, 1),
("not (not (b > 1))", 1, 1),
("not -b", 1, 1),
("not (a and b)", 2, 2),
("a and b", 2, 2),
("a and -b", 2, 2),
("-b or a", 2, 2),
("a or b + 1", 2, 2),
("a and (y := b > 1)", 2, 2),
];
assert_rows(
LANG::Python,
"def f(a, b):\n if PRED:\n pass\n",
if_rows,
);
assert_rows(
LANG::Python,
"def f(a, b):\n while PRED:\n pass\n",
&[("b", 1, 1), ("-b", 1, 1), ("b > 1", 1, 1)],
);
assert_rows(
LANG::Python,
"def f(a, b):\n y = PRED\n return y\n",
&[("b", 0, 0), ("not b", 1, 0), ("not (b > 1)", 1, 0)],
);
assert_rows(
LANG::Python,
"def f(a, b):\n return 1 if PRED else 2\n",
&[("b", 2, 1), ("-b", 2, 1), ("b > 1", 2, 1)],
);
assert_rows(
LANG::Python,
"def f(a, b):\n match a:\n case 1 if PRED:\n pass\n",
&[("b", 2, 2), ("-b", 2, 2), ("b > 1", 2, 2)],
);
}
#[test]
#[cfg(feature = "python")]
fn python_elif_is_an_else_and_a_slot() {
assert_rows(
LANG::Python,
"def f(a, b):\n if a:\n pass\n elif PRED:\n pass\n",
&[("b", 3, 2), ("-b", 3, 2), ("b > 1", 3, 2), ("not b", 3, 2)],
);
}
#[cfg(any(feature = "javascript", feature = "mozjs", feature = "typescript"))]
const JS_FAMILY_IF_ROWS: &[(&str, u64, u64)] = &[
("b", 1, 1),
("-b", 1, 1),
("!-b", 1, 1),
("this", 1, 1),
("!this", 1, 1),
("b + 1", 1, 1),
("typeof b", 1, 1),
("y = b", 1, 1),
("'k' in o", 1, 1),
("() => b", 1, 1),
("y = b > 1", 1, 1),
("a, b > 1", 1, 1),
("!(b > 1)", 1, 1),
("b > 1", 1, 1),
("!b", 1, 1),
("a && b", 2, 2),
("a && -b", 2, 2),
("-b || a", 2, 2),
("a && this", 2, 2),
("a && (y = b > 1)", 2, 2),
];
#[cfg(any(feature = "javascript", feature = "mozjs", feature = "typescript"))]
#[track_caller]
fn assert_js_family_slots(lang: LANG) {
let function = "function f(a, b, o, y) { PRED }";
assert_rows(
lang,
&function.replace(SLOT, "if (PRED) { g(); }"),
JS_FAMILY_IF_ROWS,
);
let other_slots = &[("b", 1, 1), ("-b", 1, 1), ("this", 1, 1), ("b > 1", 1, 1)];
for slot in [
"while (PRED) { g(); }",
"do { g(); } while (PRED);",
"for (; PRED; ) { g(); }",
] {
assert_rows(lang, &function.replace(SLOT, slot), other_slots);
}
assert_rows(
lang,
&function.replace(SLOT, "return PRED ? 1 : 2;"),
&[("b", 2, 1), ("-b", 2, 1), ("this", 2, 1), ("b > 1", 2, 1)],
);
assert_rows(
lang,
&function.replace(SLOT, "return PRED;"),
&[
("b", 0, 0),
("-b", 0, 0),
("!b", 1, 0),
("!this", 1, 0),
("!-b", 1, 0),
("!(b > 1)", 1, 0),
],
);
assert_rows(
lang,
&function.replace(SLOT, "g(PRED);"),
&[("b", 0, 0), ("!b", 1, 0), ("!this", 1, 0)],
);
}
#[test]
#[cfg(feature = "javascript")]
fn javascript_slots_pay_for_any_predicate() {
assert_js_family_slots(LANG::Javascript);
}
#[test]
#[cfg(feature = "mozjs")]
fn mozjs_slots_pay_for_any_predicate() {
assert_js_family_slots(LANG::Mozjs);
}
#[cfg(feature = "typescript")]
const TS_TYPE_WRAPPER_ROWS: &[(&str, u64, u64)] = &[
("b as any", 1, 1),
("b!", 1, 1),
("b satisfies boolean", 1, 1),
("(b > 1) as boolean", 1, 1),
("(b > 1)!", 1, 1),
("a && (b as any)", 2, 2),
("!(b as any)", 1, 1),
];
#[test]
#[cfg(feature = "typescript")]
fn typescript_slots_pay_for_any_predicate() {
assert_js_family_slots(LANG::Typescript);
let template = "function f(a: any, b: any) { if (PRED) { g(); } }";
assert_rows(LANG::Typescript, template, TS_TYPE_WRAPPER_ROWS);
assert_rows(
LANG::Typescript,
template,
&[("<boolean>b", 1, 1), ("<boolean>(b > 1)", 1, 1)],
);
}
#[test]
#[cfg(feature = "typescript")]
fn tsx_slots_pay_for_any_predicate() {
assert_js_family_slots(LANG::Tsx);
assert_rows(
LANG::Tsx,
"function f(a: any, b: any) { if (PRED) { g(); } }",
TS_TYPE_WRAPPER_ROWS,
);
}
#[cfg(any(feature = "c", feature = "cpp", feature = "mozcpp", feature = "objc"))]
const C_FAMILY_IF_ROWS: &[(&str, u64, u64)] = &[
("b", 1, 1),
("-b", 1, 1),
("~b", 1, 1),
("*p", 1, 1),
("&b", 1, 1),
("b + 1", 1, 1),
("b & 4", 1, 1),
("sizeof b", 1, 1),
("b++", 1, 1),
("y = b", 1, 1),
("y += 1", 1, 1),
("!-b", 1, 1),
("!*p", 1, 1),
("y = b > 1", 1, 1),
("(int)(b > 1)", 1, 1),
("(int)b", 1, 1),
("a, b > 1", 1, 1),
("!(b > 1)", 1, 1),
("b > 1", 1, 1),
("!b", 1, 1),
("!\"s\"", 1, 1),
("a ? b : y", 2, 2),
("a && b", 2, 2),
("a && -b", 2, 2),
("*p || a", 2, 2),
("a && (y = b > 1)", 2, 2),
];
#[cfg(any(feature = "c", feature = "cpp", feature = "mozcpp", feature = "objc"))]
#[track_caller]
fn assert_c_family_slots(lang: LANG) {
let function = "int f(int a, int b, int y, int *p) { PRED return 0; }";
assert_rows(
lang,
&function.replace(SLOT, "if (PRED) { g(); }"),
C_FAMILY_IF_ROWS,
);
let other_slots = &[("b", 1, 1), ("-b", 1, 1), ("*p", 1, 1), ("b > 1", 1, 1)];
for slot in [
"while (PRED) { g(); }",
"do { g(); } while (PRED);",
"for (; PRED; ) { g(); }",
] {
assert_rows(lang, &function.replace(SLOT, slot), other_slots);
}
assert_rows(
lang,
&function.replace(SLOT, "y = PRED ? 1 : 2;"),
&[("b", 2, 1), ("-b", 2, 1), ("*p", 2, 1), ("b > 1", 2, 1)],
);
assert_rows(
lang,
&function.replace(SLOT, "g(PRED);"),
&[
("b", 0, 0),
("-b", 0, 0),
("!b", 1, 0),
("!*p", 1, 0),
("!\"s\"", 1, 0),
("!(b > 1)", 1, 0),
],
);
}
#[test]
#[cfg(feature = "c")]
fn c_slots_pay_for_any_predicate() {
assert_c_family_slots(LANG::C);
}
#[cfg(any(feature = "cpp", feature = "mozcpp"))]
#[track_caller]
fn assert_cpp_slots(lang: LANG) {
assert_c_family_slots(lang);
assert_rows(
lang,
"struct S { bool f(int a, int b) { if (PRED) { g(); } return 0; } };",
&[
("this", 1, 1),
("nullptr", 1, 1),
("[] { return 1; }", 1, 1),
("not b", 1, 1),
("not -b", 1, 1),
("a and -b", 2, 2),
("auto q = g()", 1, 1),
("bool y = b > 1", 1, 1),
("bool y{b > 1}", 1, 1),
("bool y{b}", 1, 1),
("int x = g(); x", 1, 1),
("int x = g(); -x", 1, 1),
("int x = g(); x > 1", 1, 1),
],
);
let folds = [
("(... == a)", 1, 0),
("(a < ... < 0)", 1, 0),
("(... && a)", 2, 1),
("a && a", 2, 1),
("(a || ...)", 2, 1),
("a || a", 2, 1),
("(true && ... && a)", 2, 1),
("true && a", 2, 1),
("(... && !a)", 2, 1),
("!a && !a", 2, 1),
("(0 || ... || !a)", 2, 1),
("0 || !a", 2, 1),
("(... && (a > 0))", 2, 1),
("(a > 0) && (a > 0)", 2, 1),
];
assert_rows(
lang,
"template <class... T> bool f(T... a) { return PRED; }",
&folds,
);
let in_slot: Vec<_> = folds.iter().map(|&(p, c, d)| (p, c, d + 1)).collect();
assert_rows(
lang,
"template <class... T> bool f(T... a) { if (PRED) { g(); } return 0; }",
&in_slot,
);
let constraints = &[
("A<T> && B<T>", 0, 0),
("(A<T> && B<T>)", 0, 0),
("(A<T> || (B<T> && C<T>))", 0, 0),
("(!(A<T> && B<T>))", 0, 0),
("(!A<T>)", 0, 0),
("(... && A<T>)", 0, 0),
("(sizeof(T) > 4)", 0, 0),
("(X ? A<T> : B<T>)", 0, 0),
("requires(T b) { b && b; }", 0, 0),
];
assert_rows(
lang,
"template <class T> void f(T a) requires PRED {}",
constraints,
);
let with_body: Vec<_> = constraints.iter().map(|&(p, _, _)| (p, 2, 1)).collect();
assert_rows(
lang,
"template <class T> bool f(T a) requires PRED { return a && a; }",
&with_body,
);
}
#[test]
#[cfg(feature = "cpp")]
fn cpp_slots_pay_for_any_predicate() {
assert_cpp_slots(LANG::Cpp);
}
#[test]
#[cfg(feature = "mozcpp")]
fn mozcpp_slots_pay_for_any_predicate() {
assert_cpp_slots(LANG::Mozcpp);
}
#[test]
#[cfg(feature = "objc")]
fn objc_slots_pay_for_any_predicate() {
assert_c_family_slots(LANG::Objc);
assert_rows(
LANG::Objc,
"@implementation A\n- (int)f:(int)b { if (PRED) { g(); } return 0; }\n@end\n",
&[
("self", 1, 1),
("[self ok]", 1, 1),
("@available(iOS 13.0, *)", 1, 1),
("-b", 1, 1),
("b > 1", 1, 1),
],
);
}
#[test]
#[cfg(feature = "rust")]
fn rust_slots_pay_for_any_predicate() {
let rows = &[
("b", 1, 1),
("*p", 1, 1),
("!*p", 1, 1),
("a & b", 1, 1),
("a ^ b", 1, 1),
("{ a }", 1, 1),
("unsafe { a }", 1, 1),
("{ x > 1 }", 1, 1),
("match x { 1 => true, _ => false }", 2, 2),
("x > 1", 1, 1),
("!(x > 1)", 1, 1),
("let Some(z) = o", 1, 1),
("a && b", 2, 2),
("a && *p", 2, 2),
("a && b || *p", 3, 3),
("a && let Some(z) = o", 2, 2),
("let Some(z) = o && *p", 2, 2),
("let Some(z) = o && b && *p", 3, 3),
("a && let Some(z) = o && *p", 3, 3),
];
let function = "fn f(a: bool, b: bool, p: &bool, x: i32, o: Option<i32>) { PRED }";
assert_rows(
LANG::Rust,
&function.replace(SLOT, "if PRED { g(); }"),
rows,
);
assert_rows(
LANG::Rust,
&function.replace(SLOT, "while PRED { g(); }"),
&[("b", 1, 1), ("*p", 1, 1), ("a & b", 1, 1)],
);
assert_rows(
LANG::Rust,
&function.replace(SLOT, "match x { 1 if PRED => g(), _ => g() }"),
&[("b", 2, 2), ("*p", 2, 2), ("a & b", 2, 2), ("x > 1", 2, 2)],
);
assert_rows(
LANG::Rust,
&function.replace(SLOT, "g(PRED);"),
&[("*p", 0, 0), ("!*p", 1, 0), ("!(a & b)", 1, 0)],
);
}
#[test]
#[cfg(feature = "java")]
fn java_slots_pay_for_any_predicate() {
let function = "class C { boolean y; boolean f(boolean a, boolean b, Object o, int x) \
{ PRED return a; } }";
assert_rows(
LANG::Java,
&function.replace(SLOT, "if (PRED) { g(); }"),
&[
("b", 1, 1),
("y = b", 1, 1),
("y &= b", 1, 1),
("a & b", 1, 1),
("a ^ b", 1, 1),
("a | b", 1, 1),
("!(a & b)", 1, 1),
("switch (x) { case 1 -> true; default -> false; }", 2, 2),
("y = !b", 1, 1),
("y = x > 1", 1, 1),
("(boolean) (x > 1)", 1, 1),
("(Boolean) o", 1, 1),
("o instanceof String s", 1, 1),
("x > 1", 1, 1),
("!b", 1, 1),
("a && b", 2, 2),
("a && (y = b)", 2, 2),
("a && (a & b)", 2, 2),
("a && (y = !b)", 2, 2),
],
);
for slot in [
"while (PRED) { g(); }",
"do { g(); } while (PRED);",
"for (; PRED; ) { g(); }",
] {
assert_rows(
LANG::Java,
&function.replace(SLOT, slot),
&[("b", 1, 1), ("a & b", 1, 1), ("x > 1", 1, 1)],
);
}
assert_rows(
LANG::Java,
&function.replace(SLOT, "y = PRED ? true : false;"),
&[("b", 2, 1), ("a & b", 2, 1), ("x > 1", 2, 1)],
);
assert_rows(
LANG::Java,
"class C { void f(Object o, boolean a, boolean b) \
{ switch (o) { case Integer i when PRED -> g(); default -> g(); } } }",
&[
("b", 2, 2),
("a & b", 2, 2),
("/*c*/ a & b", 2, 2),
("a == b", 2, 2),
],
);
assert_rows(
LANG::Java,
&function.replace(SLOT, "g(PRED);"),
&[("a & b", 0, 0), ("!(a & b)", 1, 0)],
);
}
#[test]
#[cfg(feature = "kotlin")]
fn kotlin_slots_pay_for_any_predicate() {
let function = "fun f(a: Boolean, b: Boolean, x: Int, nb: Boolean?, o: Any) { PRED }";
let rows = &[
("b", 1, 1),
("true", 1, 1),
("when (x) { else -> true }", 1, 1),
("(when (x) { else -> true })", 1, 1),
("!when (x) { else -> true }", 1, 1),
("when (x) { 1 -> true else -> false }", 2, 2),
("when { a -> true else -> false }", 2, 2),
("if (a) b else false", 2, 2),
("try { b } catch (e: Exception) { false }", 2, 2),
("nb ?: false", 1, 2),
("x > 1", 1, 1),
("(x > 1)!!", 1, 1),
("o is String", 1, 1),
("o as? Boolean ?: false", 2, 2),
("a && b", 2, 2),
("a and b", 1, 1),
("-x > 0", 1, 1),
];
for slot in [
"if (PRED) { g() }",
"while (PRED) { g() }",
"do { g() } while (PRED)",
"when { PRED -> g() }",
] {
assert_rows(LANG::Kotlin, &function.replace(SLOT, slot), rows);
}
let in_chain: Vec<_> = rows.iter().map(|&(p, c, d)| (p, c + 1, d + 1)).collect();
assert_rows(
LANG::Kotlin,
&function.replace(SLOT, "if (a && (PRED)) { g() }"),
&in_chain,
);
}
#[test]
#[cfg(feature = "csharp")]
fn csharp_slots_pay_for_any_predicate() {
let function = "class C { bool y; bool f(bool a, bool b, object o, int x, bool? n) \
{ PRED return a; } }";
assert_rows(
LANG::Csharp,
&function.replace(SLOT, "if (PRED) { g(); }"),
&[
("b", 1, 1),
("y = b", 1, 1),
("y &= b", 1, 1),
("a & b", 1, 1),
("a ^ b", 1, 1),
("!(a & b)", 1, 1),
("x switch { 1 => true, _ => false }", 2, 2),
("y = !b", 1, 1),
("y = x > 1", 1, 1),
("(bool)(x > 1)", 1, 1),
("(bool)o", 1, 1),
("b!", 1, 1),
("o is string s", 1, 1),
("x > 1", 1, 1),
("!b", 1, 1),
("a && b", 2, 2),
("a && (y = b)", 2, 2),
("a && (a & b)", 2, 2),
],
);
for slot in [
"while (PRED) { g(); }",
"do { g(); } while (PRED);",
"for (; PRED; ) { g(); }",
] {
assert_rows(
LANG::Csharp,
&function.replace(SLOT, slot),
&[("b", 1, 1), ("a & b", 1, 1), ("x > 1", 1, 1)],
);
}
assert_rows(
LANG::Csharp,
&function.replace(SLOT, "y = PRED ? true : false;"),
&[("b", 2, 1), ("a & b", 2, 1), ("x > 1", 2, 1)],
);
assert_rows(
LANG::Csharp,
"class C { int f(object o, bool a, bool b) \
{ return o switch { int i when PRED => 1, _ => 0 }; } }",
&[
("b", 2, 2),
("a & b", 2, 2),
("/*c*/ a & b", 2, 2),
("a == b", 2, 2),
],
);
assert_rows(
LANG::Csharp,
&function.replace(SLOT, "y = PRED;"),
&[("a & b", 0, 0), ("!(a & b)", 1, 0)],
);
}
#[test]
#[cfg(feature = "csharp")]
fn csharp_1475_constructs_pay_their_slot() {
let function = "class C { bool y; bool f(bool a, bool b) { PRED return a; } }";
assert_level_with_control(
LANG::Csharp,
[
&function.replace(SLOT, "if (PRED) { g(); }"),
&function.replace(SLOT, "if (a && (PRED)) { g(); }"),
],
"b",
&[
"default(bool)",
"checked(b)",
"unchecked(b)",
"y = C()",
"new Wrapper()",
],
);
}
#[test]
#[cfg(feature = "php")]
fn php_slots_pay_for_any_predicate() {
let function = "<?php\nfunction f($a, $b, $x, $y) { PRED return 0; }\n";
assert_rows(
LANG::Php,
&function.replace(SLOT, "if (PRED) { g(); }"),
&[
("$b", 1, 1),
("-$x", 1, 1),
("!-$x", 1, 1),
("$x + 1", 1, 1),
("$a & $b", 1, 1),
("$y = $x", 1, 1),
("$y += 1", 1, 1),
("A::B", 1, 1),
("$a ?? $b", 1, 2),
("fn() => 1", 1, 1),
("@$a", 1, 1),
("print $a", 1, 1),
("$y = $x > 1", 1, 1),
("(int)($x > 1)", 1, 1),
("@($x > 1)", 1, 1),
("!($x > 1)", 1, 1),
("$x > 1", 1, 1),
("!$b", 1, 1),
("$a && $b", 2, 2),
("$a && -$x", 2, 2),
("$a and -$x", 2, 2),
("$a xor -$x", 1, 1),
("$a || ($y = $x > 1)", 2, 2),
("$a ? $b : $x", 2, 2),
],
);
for slot in [
"while (PRED) { g(); }",
"do { g(); } while (PRED);",
"for (; PRED; ) { g(); }",
] {
assert_rows(
LANG::Php,
&function.replace(SLOT, slot),
&[("$b", 1, 1), ("-$x", 1, 1), ("$x > 1", 1, 1)],
);
}
assert_rows(
LANG::Php,
&function.replace(SLOT, "$y = PRED ? 1 : 2;"),
&[("$b", 2, 1), ("-$x", 2, 1), ("$x > 1", 2, 1)],
);
for call in ["g(PRED);", "g(name: PRED);"] {
assert_rows(
LANG::Php,
&function.replace(SLOT, call),
&[("-$x", 0, 0), ("!-$x", 1, 0), ("!$b", 1, 0)],
);
}
}
#[test]
#[cfg(feature = "php")]
fn php_elseif_is_an_else_and_a_slot() {
let rows = &[("$b", 3, 2), ("-$x", 3, 2), ("$x > 1", 3, 2)];
for chain in [
"if ($a) { g(); } elseif (PRED) { g(); }",
"if ($a): g(); elseif (PRED): g(); endif;",
"if ($a) { g(); } else if (PRED) { g(); }",
] {
assert_rows(
LANG::Php,
&format!("<?php\nfunction f($a, $b, $x) {{ {chain} return 0; }}\n"),
rows,
);
}
}
#[test]
#[cfg(feature = "lua")]
fn lua_slots_pay_for_any_predicate() {
let function = "function f(a, b, x, t) PRED end\n";
let rows = &[
("b", 1, 1),
("-x", 1, 1),
("#t", 1, 1),
("x + 1", 1, 1),
("not -x", 1, 1),
("x > 1", 1, 1),
("not (x > 1)", 1, 1),
("a and b", 2, 2),
("a and -x", 2, 2),
("#t or a", 2, 2),
];
for slot in [
"if PRED then g() end",
"while PRED do g() end",
"repeat g() until PRED",
] {
assert_rows(LANG::Lua, &function.replace(SLOT, slot), rows);
}
for value in ["g(PRED)", "return PRED"] {
assert_rows(
LANG::Lua,
&function.replace(SLOT, value),
&[("-x", 0, 0), ("not -x", 1, 0), ("not b", 1, 0)],
);
}
}
#[test]
#[cfg(feature = "lua")]
fn lua_elseif_is_an_else_and_a_slot() {
assert_rows(
LANG::Lua,
"function f(a, b, x) if a then g() elseif PRED then g() end end\n",
&[("b", 3, 2), ("-x", 3, 2), ("x > 1", 3, 2), ("not b", 3, 2)],
);
}
#[test]
#[cfg(feature = "groovy")]
fn groovy_slots_pay_for_any_predicate() {
let function = "class C { def y\n def f(a, b, x, o, l) { PRED; return a } }\n";
assert_rows(
LANG::Groovy,
&function.replace(SLOT, "if (PRED) { g() }"),
&[
("b", 1, 1),
("-x", 1, 1),
("!-x", 1, 1),
("x + 1", 1, 1),
("(y = b)", 1, 1),
("a ==> b", 1, 1),
("{ -> 1 }", 1, 1),
("y = !b", 1, 1),
("y = x > 1", 1, 1),
("(boolean) (x > 1)", 1, 1),
("x > 1", 1, 1),
("a === b", 1, 1),
("x =~ /r/", 1, 1),
("x <=> 1", 1, 1),
("x in l", 1, 1),
("o instanceof String", 1, 1),
("a && b", 2, 2),
("a && -x", 2, 2),
("-x || a", 2, 2),
("a && (y = !b)", 2, 2),
],
);
for slot in [
"while (PRED) { g() }",
"do { g() } while (PRED)",
"for (; PRED; ) { g() }",
] {
assert_rows(
LANG::Groovy,
&function.replace(SLOT, slot),
&[("b", 1, 1), ("-x", 1, 1), ("x > 1", 1, 1)],
);
}
assert_rows(
LANG::Groovy,
&function.replace(SLOT, "y = PRED ? 1 : 2"),
&[("b", 2, 1), ("-x", 2, 1), ("x > 1", 2, 1)],
);
assert_rows(
LANG::Groovy,
&function.replace(SLOT, "y = PRED"),
&[("-x", 0, 0), ("!-x", 1, 0)],
);
}
#[test]
#[cfg(feature = "groovy")]
fn groovy_1475_constructs_pay_their_slot() {
let function = "class C { def f(a, b) { PRED; return a } }\n";
assert_level_with_control(
LANG::Groovy,
[
&function.replace(SLOT, "if (PRED) { g() }"),
&function.replace(SLOT, "if (a && (PRED)) { g() }"),
],
"b",
&["new Foo()", "a*.b"],
);
}
#[test]
#[cfg(feature = "perl")]
fn perl_slots_pay_for_any_predicate() {
let function = "sub f { my ($a, $b, $x, $y) = @_; PRED }\n";
assert_rows(
LANG::Perl,
&function.replace(SLOT, "if (PRED) { g(); }"),
&[
("$b", 1, 1),
("-$x", 1, 1),
("!-$x", 1, 1),
("not -$x", 1, 1),
("$x + 1", 1, 1),
("my $y = $x", 1, 1),
("$y += 1", 1, 1),
("my $y = $x > 1", 1, 1),
("$a, $x > 1", 1, 1),
("$x > 1", 1, 1),
("/re/", 1, 1),
("!/re/", 1, 1),
("$x =~ /re/", 1, 1),
("$a && $b", 2, 2),
("$a && -$x", 2, 2),
("$a or -$x", 2, 2),
("$a and $b", 2, 2),
("$a and -$x", 2, 2),
("$a ^ $b", 1, 1),
("$a xor $b", 1, 1),
("$a xor -$x", 1, 1),
],
);
for slot in [
"unless (PRED) { g(); }",
"while (PRED) { g(); }",
"until (PRED) { g(); }",
"for (my $i = 0; PRED; $i++) { g(); }",
"g() if PRED;",
"g() if (PRED);",
"g() unless PRED;",
"g() while PRED;",
] {
assert_rows(
LANG::Perl,
&function.replace(SLOT, slot),
&[("$b", 1, 1), ("-$x", 1, 1), ("$x > 1", 1, 1)],
);
}
assert_rows(
LANG::Perl,
&function.replace(SLOT, "g() if (PRED);"),
&[("", 1, 1)],
);
assert_rows(
LANG::Perl,
&function.replace(SLOT, "my $r = PRED ? 1 : 2;"),
&[("$b", 2, 1), ("-$x", 2, 1)],
);
for value in ["g(PRED);", "return PRED;"] {
assert_rows(
LANG::Perl,
&function.replace(SLOT, value),
&[("-$x", 0, 0), ("!-$x", 1, 0), ("!$b", 1, 0)],
);
}
}
#[test]
#[cfg(feature = "perl")]
fn perl_elsif_is_an_else_and_a_slot() {
assert_rows(
LANG::Perl,
"sub f { my ($a, $b, $x) = @_; if ($a) { g(); } elsif (PRED) { g(); } }\n",
&[("$b", 3, 2), ("-$x", 3, 2), ("$x > 1", 3, 2)],
);
}
#[test]
#[cfg(feature = "perl")]
fn perl_1475_constructs_pay_their_slot() {
let function = "sub f { my ($a, $b, $h) = @_; my @a; my %h; PRED }\n";
assert_level_with_control(
LANG::Perl,
[
&function.replace(SLOT, "if (PRED) { g(); }"),
&function.replace(SLOT, "if ($a && (PRED)) { g(); }"),
],
"$b",
&[
"s/x/y/",
"tr/a/b/",
"y/a/b/",
"$b =~ s/x/y/",
"$b !~ s/x/y/",
"$b =~ tr/a/b/",
"sub {1}",
"@a",
"@$b",
"%h",
"%$h",
],
);
}
#[test]
#[cfg(feature = "ruby")]
fn ruby_1475_constructs_pay_their_slot() {
assert_level_with_control(
LANG::Ruby,
[
"def f(a, b)\n if PRED\n g\n end\nend\n",
"def f(a, b)\n if a && (PRED)\n g\n end\nend\n",
],
"b",
&["lambda {}", "-> {}", "(1..2)"],
);
}
#[test]
#[cfg(feature = "elixir")]
fn elixir_1475_constructs_pay_their_slot() {
let function = "defmodule M do\n def f(a, b) do\n PRED\n end\nend\n";
assert_level_with_control(
LANG::Elixir,
[
&function.replace(SLOT, "if PRED do\n g()\n end"),
&function.replace(SLOT, "if a && (PRED) do\n g()\n end"),
],
"b",
&["fn -> 1 end", "&f/1"],
);
}
#[cfg(any(feature = "tcl", feature = "irules"))]
#[track_caller]
fn assert_tcl_family_slots(lang: LANG) {
let function = "proc f {a b x} { PRED }\n";
let rows = &[
("$b", 1, 1),
("-$x", 1, 1),
("!-$x", 1, 1),
("$x + 1", 1, 1),
("$x > 1", 1, 1),
("($x > 1)", 1, 1),
("$a ? $b : $x", 2, 2),
("$a && $b", 2, 2),
("$a && -$x", 2, 2),
];
for slot in ["if {PRED} { g }", "while {PRED} { g }"] {
assert_rows(lang, &function.replace(SLOT, slot), rows);
}
assert_rows(
lang,
&function.replace(SLOT, "if {$a} { g } elseif {PRED} { g }"),
&[("$b", 3, 2), ("-$x", 3, 2), ("$x > 1", 3, 2)],
);
assert_rows(
lang,
&function.replace(SLOT, "set r [expr {PRED ? 1 : 2}]"),
&[("$b", 2, 1), ("-$x", 2, 1)],
);
assert_rows(
lang,
&function.replace(SLOT, "set r [expr {$a ? PRED : 2}]"),
&[("-$x", 2, 1), ("!-$x", 3, 1)],
);
}
#[test]
#[cfg(feature = "tcl")]
fn tcl_slots_pay_for_any_predicate() {
assert_tcl_family_slots(LANG::Tcl);
}
#[test]
#[cfg(feature = "irules")]
fn irules_slots_pay_for_any_predicate() {
assert_tcl_family_slots(LANG::Irules);
assert_rows(
LANG::Irules,
"proc f {a b x} { if {PRED} { g } }\n",
&[
("$x contains \"a\"", 1, 1),
("$a and -$x", 2, 2),
("not ($x == 1)", 1, 1),
("not ($x contains \"a\")", 1, 1),
("not -$x", 1, 1),
],
);
}
#[test]
#[cfg(any(feature = "csharp", feature = "php", feature = "perl"))]
fn bare_return_scores_nothing() {
let mut ran = 0;
#[cfg(feature = "csharp")]
{
assert_rows(
LANG::Csharp,
"class A { void f(bool b) { if (b) { return; } var r = PRED; } }\n",
&[("!b", 2, 1), ("b", 1, 1)],
);
ran += 1;
}
#[cfg(feature = "php")]
{
assert_rows(
LANG::Php,
"<?php\nfunction f($b) {\n if ($b) { return; }\n return PRED;\n}\n",
&[("!$b", 2, 1), ("$b", 1, 1)],
);
ran += 1;
}
#[cfg(feature = "perl")]
{
assert_rows(
LANG::Perl,
"sub f { my ($b) = @_; if ($b) { return; } return PRED; }\n",
&[("!$b", 2, 1), ("$b", 1, 1)],
);
ran += 1;
}
assert!(ran > 0, "no language enabled; this test asserted nothing");
}