use nxc_frontend::{analyze, Lexer, Parser}; fn analyze_source(source: &str) -> nxc_frontend::SemanticResult { let lexed = Lexer::new(source).lex(); assert!( lexed.diagnostics.is_empty(), "unexpected lexer diagnostics: {:?}", lexed.diagnostics ); let parsed = Parser::new(lexed.tokens).parse_module(); assert!( parsed.diagnostics.is_empty(), "unexpected parser diagnostics: {:?}", parsed.diagnostics ); analyze(&parsed.module) } fn messages(result: &nxc_frontend::SemanticResult) -> Vec { result .diagnostics .iter() .map(|diagnostic| diagnostic.message.clone()) .collect() } #[test] fn rejects_duplicate_function_names() { let result = analyze_source( "\ fn main() -> Int: return 0 fn main() -> Int: return 1 ", ); assert!(messages(&result).iter().any(|msg| msg.contains("duplicate function `main`"))); } #[test] fn rejects_duplicate_parameter_names() { let result = analyze_source( "\ fn main(value: Int, value: Int) -> Int: return value ", ); assert!(messages(&result) .iter() .any(|msg| msg.contains("duplicate parameter `value`"))); } #[test] fn rejects_duplicate_local_bindings_in_same_scope() { let result = analyze_source( "\ fn main() -> Int: let value = 1 let value = 2 return value ", ); assert!(messages(&result) .iter() .any(|msg| msg.contains("duplicate binding `value`"))); } #[test] fn rejects_unknown_identifiers() { let result = analyze_source( "\ fn main() -> Int: return missing ", ); assert!(messages(&result) .iter() .any(|msg| msg.contains("unknown identifier `missing`"))); } #[test] fn rejects_unknown_function_calls() { let result = analyze_source( "\ fn main() -> Int: return missing(1) ", ); assert!(messages(&result) .iter() .any(|msg| msg.contains("unknown function `missing`"))); } #[test] fn rejects_invalid_unary_operator_types() { let result = analyze_source( "\ fn main() -> Int: return -true ", ); assert!(messages(&result) .iter() .any(|msg| msg.contains("unary `-` expects `Int` or `Float`"))); } #[test] fn rejects_invalid_binary_operator_types() { let result = analyze_source( "\ fn main() -> Int: return true + 1 ", ); assert!(messages(&result) .iter() .any(|msg| msg.contains("operator `+` expects numeric operands"))); } #[test] fn rejects_invalid_if_condition_types() { let result = analyze_source( "\ fn main() -> Int: if 1: return 0 else: return 1 ", ); assert!(messages(&result) .iter() .any(|msg| msg.contains("if condition must be `Bool`"))); } #[test] fn rejects_wrong_call_arity() { let result = analyze_source( "\ fn add(a: Int, b: Int) -> Int: return a + b fn main() -> Int: return add(1) ", ); assert!(messages(&result) .iter() .any(|msg| msg.contains("expects 2 argument(s), found 1"))); } #[test] fn rejects_wrong_call_argument_types() { let result = analyze_source( "\ fn negate(value: Int) -> Int: return -value fn main() -> Int: return negate(true) ", ); assert!(messages(&result) .iter() .any(|msg| msg.contains("parameter `value` expects `Int`, found `Bool`"))); } #[test] fn rejects_return_type_mismatch() { let result = analyze_source( "\ fn main() -> Int: return true ", ); assert!(messages(&result) .iter() .any(|msg| msg.contains("return type mismatch: expected `Int`, found `Bool`"))); } #[test] fn rejects_missing_return_in_non_void_function() { let result = analyze_source( "\ fn main() -> Int: let value = 1 ", ); assert!(messages(&result) .iter() .any(|msg| msg.contains("may exit without returning `Int`"))); } #[test] fn allows_shadowing_in_nested_scopes() { let result = analyze_source( "\ fn main(value: Int) -> Int: if true: let value = 2 return value else: return value ", ); assert!(result.diagnostics.is_empty(), "{:?}", result.diagnostics); }