use crate::test_support::space_verbatim;
use crate::{FuncSpace, LANG, MetricsOptions, SpaceKind};
#[cfg(any(feature = "c", feature = "cpp", feature = "mozcpp", feature = "objc"))]
const SHARED_SHAPES: &[(&str, Option<&str>)] = &[
("int (*fp(int a, int b))(int c) { return 0; }", Some("fp")),
(
"int (__cdecl *w(int a, int b))(int c) { return 0; }",
Some("w"),
),
(
"void RUN_STATS_METHOD(allocate)(int a) { }",
Some("RUN_STATS_METHOD"),
),
("void MACRO(a, b)(int x) { }", Some("MACRO")),
("void A(b, c)(d)(int x, int y, int z) { }", Some("A")),
("int (*g(void))[4] { return 0; }", Some("g")),
("int plain(int a, int b) { return a; }", Some("plain")),
("FILE *ptr(int a) { return 0; }", Some("ptr")),
(
"TF_CAPI_EXPORT extern TF_ConcreteFunction* TF_GetFn(TF_SavedModel* m) { return 0; }",
Some("TF_GetFn"),
),
("int (fp)(int a) { return a; }", None),
];
#[cfg(any(feature = "c", feature = "cpp", feature = "mozcpp", feature = "objc"))]
const CPP_ONLY_SHAPES: &[(&str, Option<&str>)] = &[
("struct S { ~S() { } };", Some("~S")),
("void Foo::bar(int a) { }", Some("Foo::bar")),
(
"struct S { operator int() const { return 0; } };",
Some("operator int() const"),
),
(
"struct S { int operator+(int o) const { return o; } };",
Some("operator+"),
),
(
"Foo &Bar::get(int a) { static Foo f; return f; }",
Some("Bar::get"),
),
(
"template <typename T> T tfree(T a) { return a; }",
Some("tfree"),
),
(
"template <> void tspec<int (*)(int x, int y)>(int a) { }",
Some("tspec<int (*)(int x, int y)>"),
),
(
"void Foo::tmem<int (*)(int x, int y)>(int a) { }",
Some("Foo::tmem<int (*)(int x, int y)>"),
),
];
#[cfg(any(feature = "c", feature = "cpp", feature = "mozcpp", feature = "objc"))]
const FIXTURE_LINE: usize = 2;
#[cfg(any(feature = "c", feature = "cpp", feature = "mozcpp", feature = "objc"))]
fn pad(source: &str) -> String {
format!("// leading\n{source}\n// trailing\n")
}
#[cfg(any(feature = "c", feature = "cpp", feature = "mozcpp", feature = "objc"))]
fn function_spaces(space: &FuncSpace, found: &mut Vec<(Option<String>, usize, usize)>) {
if space.kind == SpaceKind::Function {
found.push((space.name.clone(), space.start_line, space.end_line));
}
for child in &space.spaces {
function_spaces(child, found);
}
}
#[cfg(any(feature = "c", feature = "cpp", feature = "mozcpp", feature = "objc"))]
fn check(lang: LANG, shapes: &[(&str, Option<&str>)], failures: &mut Vec<String>) {
for (source, expected) in shapes {
let root = space_verbatim(lang, pad(source).as_bytes(), MetricsOptions::default());
let mut found = Vec::new();
function_spaces(&root, &mut found);
let want = vec![(expected.map(str::to_owned), FIXTURE_LINE, FIXTURE_LINE)];
if found != want {
failures.push(format!(
"{lang:?}: {source:?}\n want {want:?}\n got {found:?}"
));
}
}
}
#[cfg(any(feature = "c", feature = "cpp", feature = "mozcpp", feature = "objc"))]
#[track_caller]
fn assert_all_matched(failures: &[String], checked: usize, what: &str) {
assert!(
failures.is_empty(),
"{}/{checked} {what}:\n{}",
failures.len(),
failures.join("\n")
);
assert!(checked > 0, "no C-family language was enabled");
}
#[cfg(any(feature = "c", feature = "cpp", feature = "mozcpp", feature = "objc"))]
#[test]
fn the_declarator_walk_names_the_function_space() {
let mut failures = Vec::new();
let mut checked = 0;
for lang in [LANG::C, LANG::Cpp, LANG::Mozcpp, LANG::Objc]
.into_iter()
.filter(LANG::is_enabled)
{
check(lang, SHARED_SHAPES, &mut failures);
checked += SHARED_SHAPES.len();
if matches!(lang, LANG::Cpp | LANG::Mozcpp) {
check(lang, CPP_ONLY_SHAPES, &mut failures);
checked += CPP_ONLY_SHAPES.len();
}
}
assert_all_matched(
&failures,
checked,
"declarator shapes named the wrong space",
);
}
#[cfg(feature = "c")]
#[test]
fn the_table_reports_a_name_that_does_not_match() {
let mut failures = Vec::new();
check(
LANG::C,
&[(
"int plain(int a, int b) { return a; }",
Some("deliberately_wrong"),
)],
&mut failures,
);
let [only] = failures.as_slice() else {
panic!("one wrong expectation must produce one failure, got {failures:?}");
};
assert!(
only.contains("deliberately_wrong") && only.contains("Some(\"plain\")"),
"the failure must name both the expectation and the space found: {only}"
);
}
#[cfg(any(feature = "c", feature = "cpp", feature = "mozcpp", feature = "objc"))]
#[test]
fn a_macro_where_an_attribute_belongs_divides_the_grammars() {
const SOURCE: &str = "int *f() TF_ATTRIBUTE_NOINLINE { return 0; }";
let mut failures = Vec::new();
let mut checked = 0;
for lang in [LANG::C, LANG::Cpp, LANG::Mozcpp, LANG::Objc]
.into_iter()
.filter(LANG::is_enabled)
{
let expected = matches!(lang, LANG::C | LANG::Mozcpp).then_some("f");
check(lang, &[(SOURCE, expected)], &mut failures);
checked += 1;
}
assert_all_matched(
&failures,
checked,
"grammars disagreed about the recovery shape",
);
}
#[cfg(any(feature = "c", feature = "cpp", feature = "mozcpp", feature = "objc"))]
#[test]
fn a_parenthesised_macro_takes_the_name_of_the_function_it_annotates() {
const SOURCE: &str = "int *f() TF_LOCKS_EXCLUDED(mu_) { return 0; }";
let mut failures = Vec::new();
let mut checked = 0;
for lang in [LANG::C, LANG::Cpp, LANG::Mozcpp, LANG::Objc]
.into_iter()
.filter(LANG::is_enabled)
{
let expected = if lang == LANG::C {
"f"
} else {
"TF_LOCKS_EXCLUDED"
};
check(lang, &[(SOURCE, Some(expected))], &mut failures);
checked += 1;
}
assert_all_matched(
&failures,
checked,
"grammars disagreed about the annotated recovery shape",
);
}