use std::collections::HashMap;
use llmcc_rust::{build_llmcc_ir, collect_symbols, CompileCtxt, FnVisibility, LangRust, TypeExpr};
fn collect_functions(source: &str) -> HashMap<String, llmcc_rust::FunctionDescriptor> {
let sources = vec![source.as_bytes().to_vec()];
let cc = CompileCtxt::from_sources::<LangRust>(&sources);
let unit = cc.compile_unit(0);
build_llmcc_ir::<LangRust>(&cc).unwrap();
let globals = cc.create_globals();
collect_symbols(unit, globals)
.functions
.into_iter()
.map(|desc| (desc.fqn.clone(), desc))
.collect()
}
#[test]
fn detects_private_function() {
let map = collect_functions("fn foo() {}\n");
let foo = map.get("foo").unwrap();
assert_eq!(foo.visibility, FnVisibility::Private);
assert!(foo.parameters.is_empty());
assert!(foo.return_type.is_none());
}
#[test]
fn detects_public_visibility() {
let map = collect_functions("pub fn foo() {}\n");
assert_eq!(map.get("foo").unwrap().visibility, FnVisibility::Public);
}
#[test]
fn detects_pub_crate_visibility() {
let map = collect_functions("pub(crate) fn foo() {}\n");
assert_eq!(map.get("foo").unwrap().visibility, FnVisibility::Crate);
}
#[test]
fn captures_parameters_and_return_type() {
let source = r#"
fn transform(value: i32, label: Option<&str>) -> Result<i32, &'static str> {
Ok(value)
}
"#;
let map = collect_functions(source);
let desc = map.get("transform").unwrap();
assert_eq!(desc.parameters.len(), 2);
assert_eq!(desc.parameters[0].pattern, "value");
assert_eq!(desc.parameters[1].pattern, "label");
let param0 = desc.parameters[0].ty.as_ref().unwrap();
assert_path(param0, &["i32"]);
let param1 = desc.parameters[1].ty.as_ref().unwrap();
let generics = assert_path(param1, &["Option"]);
assert_eq!(generics.len(), 1);
let inner = &generics[0];
if let TypeExpr::Reference {
is_mut,
lifetime,
inner,
} = inner
{
assert!(!is_mut);
assert!(lifetime.is_none());
assert_path(inner, &["str"]);
} else {
panic!();
}
let return_type = desc.return_type.as_ref().unwrap();
let generics = assert_path(return_type, &["Result"]);
assert_eq!(generics.len(), 2);
assert_path(&generics[0], &["i32"]);
if let TypeExpr::Reference {
is_mut,
lifetime,
inner,
} = &generics[1]
{
assert!(!is_mut);
assert_eq!(lifetime.as_deref(), Some("'static"));
assert_path(inner, &["str"]);
} else {
panic!();
}
}
#[test]
fn captures_async_const_and_unsafe_flags() {
let source = r#"
async unsafe fn perform() {}
const fn build() -> i32 { 0 }
"#;
let map = collect_functions(source);
let perform = map.get("perform").unwrap();
assert!(perform.is_async);
assert!(perform.is_unsafe);
assert!(!perform.is_const);
let build = map.get("build").unwrap();
assert!(build.is_const);
assert!(!build.is_async);
assert!(!build.is_unsafe);
}
fn assert_path<'a>(expr: &'a TypeExpr, expected: &[&str]) -> &'a [TypeExpr] {
if let TypeExpr::Path { segments, generics } = expr {
let expected_vec: Vec<String> = expected.iter().map(|s| s.to_string()).collect();
assert_eq!(segments, &expected_vec);
generics
} else {
panic!();
}
}