use myc::ast::nodes::TypedNode; use myc::ast::environment::Environment; use myc::ast::types::StaticType; // Helper: extracts the return type from a compiled node. // For a function type, returns the return type; otherwise returns the node type directly. fn get_ret_type(node: &TypedNode) -> StaticType { if let StaticType::Function(sig) = &node.ty { sig.ret.clone() } else { node.ty.clone() } } // --- Type checker errors --- #[test] fn test_destructuring_scalar_error() { let env = Environment::new(); let result = env.compile("(def [x] 5)").into_result(); assert!(result.is_err()); assert_eq!( result.unwrap_err(), "Cannot destructure type int as a tuple/vector" ); } #[test] fn test_call_argument_mismatch() { let env = Environment::new(); // fn takes 1 arg, called with 0 let result = env.compile("(do (def f (fn [x] x)) (f))").into_result(); assert!(result.is_err()); assert!( result .unwrap_err() .contains("no matching overload") ); } #[test] fn test_destructuring_vector_args() { let env = Environment::new(); // ((fn [[x y]] (+ x y)) [10 20]) -> 30 let result = env.compile("((fn [[x y]] (+ x y)) [10 20])").into_result(); assert!(result.is_ok()); assert_eq!(get_ret_type(&result.unwrap()), StaticType::Int); } // --- Error recovery (multi-pass diagnostics) --- #[test] fn test_error_recovery_parser() { let env = Environment::new(); // Syntax error: mismatched bracket/paren let source = "(do (def a [1 2 ) )"; let result = env.compile(source); assert!(result.diagnostics.has_errors(), "Expected parser errors"); let error_msgs: Vec<_> = result .diagnostics .items .iter() .map(|d| d.message.as_str()) .collect(); assert!( error_msgs.iter().any(|m| m.contains("RightParen")), "Expected error about RightParen" ); assert!( result.ast.is_some(), "AST should be partially built despite parser errors" ); } #[test] fn test_error_recovery_binder() { let env = Environment::new(); // Semantic error: undefined variable let source = "(do (def a 10) (def b unknown_var) (+ a 5))"; let result = env.compile(source); assert!(result.diagnostics.has_errors(), "Expected binder errors"); let error_msgs: Vec<_> = result .diagnostics .items .iter() .map(|d| d.message.as_str()) .collect(); assert!( error_msgs .iter() .any(|m| m.contains("Undefined variable 'unknown_var'")), "Expected undefined variable error" ); assert!(result.ast.is_some(), "AST should be built with Error nodes"); } #[test] fn test_error_recovery_type_checker() { let env = Environment::new(); // Semantic error: Type mismatch in function call let source = "(do (def a 10) (def b (not \"text\")) (- a 2))"; let result = env.compile(source); assert!( result.diagnostics.has_errors(), "Expected type checker errors" ); let error_msgs: Vec<_> = result .diagnostics .items .iter() .map(|d| d.message.as_str()) .collect(); assert!( error_msgs .iter() .any(|m| m.contains("no matching overload")), "Expected invalid arguments error" ); assert!(result.ast.is_some(), "AST should be built with Error nodes"); } #[test] fn test_error_recovery_multiple_errors() { let env = Environment::new(); // A script with multiple errors that should all be collected let source = r#" (do (def a undefined_var) (def b (not "text")) ) "#; let result = env.compile(source); assert!(result.diagnostics.has_errors()); assert!( result.diagnostics.items.len() >= 2, "Expected multiple errors to be collected" ); let error_msgs: Vec<_> = result .diagnostics .items .iter() .map(|d| d.message.as_str()) .collect(); assert!( error_msgs .iter() .any(|m| m.contains("Undefined variable 'undefined_var'")) ); assert!( error_msgs .iter() .any(|m| m.contains("no matching overload")) ); } #[test] fn test_again_type_mismatch() { let env = Environment::new(); // (again "text") should fail: parameter n is int (from call site), not string. // Without this check, the program would loop forever. let result = env.run_script( r#"(do (def f (fn [n] (if (= n 0) n (again "text")))) (f 3))"#, ); assert!( result.is_err(), "again with wrong type should be a compile error, not an infinite loop" ); }