use crate::frontend::ast::{Expr, ExprKind, Literal, StringPart};
pub fn uses_builtins(expr: &Expr) -> bool {
match &expr.kind {
ExprKind::Call { func, .. } => {
if let ExprKind::Identifier(name) = &func.kind {
matches!(name.as_str(), "println" | "print" | "eprintln" | "eprint")
} else {
false
}
}
ExprKind::Block(exprs) => exprs.iter().any(uses_builtins),
ExprKind::If {
condition,
then_branch,
else_branch,
} => {
uses_builtins(condition)
|| uses_builtins(then_branch)
|| else_branch.as_ref().is_some_and(|e| uses_builtins(e))
}
ExprKind::Let { value, body, .. } => uses_builtins(value) || uses_builtins(body),
ExprKind::Binary { left, right, .. } => uses_builtins(left) || uses_builtins(right),
ExprKind::StringInterpolation { parts } => parts.iter().any(|part| {
if let StringPart::Expr(e) | StringPart::ExprWithFormat { expr: e, .. } = part {
uses_builtins(e)
} else {
false
}
}),
ExprKind::Match { expr, arms } => {
uses_builtins(expr) || arms.iter().any(|arm| uses_builtins(&arm.body))
}
ExprKind::Function { body, .. } => uses_builtins(body),
ExprKind::Lambda { body, .. } => uses_builtins(body),
_ => false,
}
}
pub fn needs_memory(expr: &Expr) -> bool {
match &expr.kind {
ExprKind::Literal(Literal::String(_)) => true,
ExprKind::List(_) => true,
ExprKind::ArrayInit { .. } => true,
ExprKind::Tuple(_) => true, ExprKind::StructLiteral { .. } => true, ExprKind::Block(exprs) => exprs.iter().any(needs_memory),
ExprKind::Function { body, .. } => needs_memory(body),
ExprKind::Let { value, body, .. } => needs_memory(value) || needs_memory(body),
ExprKind::LetPattern { value, body, .. } => needs_memory(value) || needs_memory(body),
ExprKind::Binary { left, right, .. } => needs_memory(left) || needs_memory(right),
ExprKind::If {
condition,
then_branch,
else_branch,
} => {
needs_memory(condition)
|| needs_memory(then_branch)
|| else_branch.as_ref().is_some_and(|e| needs_memory(e))
}
_ => false,
}
}
pub fn has_main_function(expr: &Expr) -> bool {
match &expr.kind {
ExprKind::Function { name, .. } => name == "main",
ExprKind::Block(exprs) => exprs.iter().any(has_main_function),
_ => false,
}
}
pub fn has_return_with_value(expr: &Expr) -> bool {
match &expr.kind {
ExprKind::Return { value } => value.is_some(),
ExprKind::Block(exprs) => exprs.iter().any(has_return_with_value),
ExprKind::If {
condition,
then_branch,
else_branch,
} => {
has_return_with_value(condition)
|| has_return_with_value(then_branch)
|| else_branch
.as_ref()
.is_some_and(|e| has_return_with_value(e))
}
ExprKind::While {
condition, body, ..
} => has_return_with_value(condition) || has_return_with_value(body),
ExprKind::Function { .. } => false, ExprKind::Let { value, body, .. } => {
has_return_with_value(value) || has_return_with_value(body)
}
ExprKind::Binary { left, right, .. } => {
has_return_with_value(left) || has_return_with_value(right)
}
_ => false,
}
}
pub fn needs_locals(expr: &Expr) -> bool {
match &expr.kind {
ExprKind::Let { .. } => true,
ExprKind::Identifier(_) => true,
ExprKind::Function { .. } => true,
ExprKind::Block(exprs) => exprs.iter().any(needs_locals),
ExprKind::If {
condition,
then_branch,
else_branch,
} => {
needs_locals(condition)
|| needs_locals(then_branch)
|| else_branch.as_ref().is_some_and(|e| needs_locals(e))
}
ExprKind::While {
condition, body, ..
} => needs_locals(condition) || needs_locals(body),
ExprKind::Binary { left, right, .. } => needs_locals(left) || needs_locals(right),
_ => false,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Parser;
fn parse(code: &str) -> Expr {
let mut parser = Parser::new(code);
parser.parse().expect("Should parse")
}
#[test]
fn test_uses_builtins_println() {
let expr = parse(r#"println("hello")"#);
assert!(uses_builtins(&expr));
}
#[test]
fn test_uses_builtins_print() {
let expr = parse(r#"print("hello")"#);
assert!(uses_builtins(&expr));
}
#[test]
fn test_uses_builtins_eprintln() {
let expr = parse(r#"eprintln("error")"#);
assert!(uses_builtins(&expr));
}
#[test]
fn test_uses_builtins_eprint() {
let expr = parse(r#"eprint("error")"#);
assert!(uses_builtins(&expr));
}
#[test]
fn test_uses_builtins_custom_function() {
let expr = parse("my_func(42)");
assert!(!uses_builtins(&expr));
}
#[test]
fn test_uses_builtins_in_block() {
let expr = parse(r#"{ 1; println("hi") }"#);
assert!(uses_builtins(&expr));
}
#[test]
fn test_uses_builtins_in_let() {
let expr = parse(r#"let x = 1; println("hi")"#);
assert!(uses_builtins(&expr));
}
#[test]
fn test_uses_builtins_in_function() {
let expr = parse(r#"fun main() { println("hi") }"#);
assert!(uses_builtins(&expr));
}
#[test]
fn test_uses_builtins_literal_only() {
let expr = parse("42");
assert!(!uses_builtins(&expr));
}
#[test]
fn test_needs_memory_string() {
let expr = parse(r#""hello""#);
assert!(needs_memory(&expr));
}
#[test]
fn test_needs_memory_list() {
let expr = parse("[1, 2]");
assert!(needs_memory(&expr));
}
#[test]
fn test_needs_memory_tuple() {
let expr = parse("(1, 2)");
assert!(needs_memory(&expr));
}
#[test]
fn test_needs_memory_int() {
let expr = parse("42");
assert!(!needs_memory(&expr));
}
#[test]
fn test_needs_memory_in_block() {
let expr = parse(r#"{ 1; "hello" }"#);
assert!(needs_memory(&expr));
}
#[test]
fn test_needs_memory_in_function() {
let expr = parse(r#"fun main() { "hello" }"#);
assert!(needs_memory(&expr));
}
#[test]
fn test_needs_memory_in_let() {
let expr = parse(r#"let x = "hello"; 1"#);
assert!(needs_memory(&expr));
}
#[test]
fn test_has_main_function_true() {
let expr = parse("fun main() { 0 }");
assert!(has_main_function(&expr));
}
#[test]
fn test_has_main_function_false() {
let expr = parse("fun not_main() { 0 }");
assert!(!has_main_function(&expr));
}
#[test]
fn test_has_main_function_in_block() {
let expr = parse("{ fun helper() { 1 }; fun main() { 0 } }");
assert!(has_main_function(&expr));
}
#[test]
fn test_has_main_function_literal() {
let expr = parse("42");
assert!(!has_main_function(&expr));
}
#[test]
fn test_has_return_with_value_true() {
let expr = parse("return 42");
assert!(has_return_with_value(&expr));
}
#[test]
fn test_has_return_with_value_false() {
let expr = parse("return");
assert!(!has_return_with_value(&expr));
}
#[test]
fn test_has_return_with_value_in_block() {
let expr = parse("{ 1; return 42 }");
assert!(has_return_with_value(&expr));
}
#[test]
fn test_has_return_with_value_in_if() {
let expr = parse("if true { return 1 } else { return 2 }");
assert!(has_return_with_value(&expr));
}
#[test]
fn test_has_return_with_value_literal() {
let expr = parse("42");
assert!(!has_return_with_value(&expr));
}
#[test]
fn test_needs_locals_let() {
let expr = parse("let x = 1; 2");
assert!(needs_locals(&expr));
}
#[test]
fn test_needs_locals_identifier() {
let expr = parse("x");
assert!(needs_locals(&expr));
}
#[test]
fn test_needs_locals_function() {
let expr = parse("fun f() { 1 }");
assert!(needs_locals(&expr));
}
#[test]
fn test_needs_locals_literal() {
let expr = parse("42");
assert!(!needs_locals(&expr));
}
#[test]
fn test_needs_locals_in_block() {
let expr = parse("{ 1; let x = 2; 3 }");
assert!(needs_locals(&expr));
}
#[test]
fn test_needs_locals_in_if() {
let expr = parse("if true { let x = 2; 3 }");
assert!(needs_locals(&expr));
}
#[test]
fn test_needs_locals_in_binary() {
let expr = parse("x + 1");
assert!(needs_locals(&expr));
}
#[cfg(test)]
mod property_tests {
use super::*;
use proptest::prelude::*;
proptest! {
#![proptest_config(ProptestConfig::with_cases(100))]
#[test]
fn prop_uses_builtins_never_panics(n in -1000i64..1000) {
let expr = parse(&format!("{n}"));
let _ = uses_builtins(&expr);
}
#[test]
fn prop_needs_memory_never_panics(n in -1000i64..1000) {
let expr = parse(&format!("{n}"));
let _ = needs_memory(&expr);
}
#[test]
fn prop_has_main_function_never_panics(name in "[a-z]{4,10}") {
let expr = parse(&format!("fun {name}() {{ 0 }}"));
let _ = has_main_function(&expr);
}
#[test]
fn prop_has_return_with_value_never_panics(n in -1000i64..1000) {
let expr = parse(&format!("return {n}"));
let _ = has_return_with_value(&expr);
}
#[test]
fn prop_needs_locals_never_panics(name in "[a-z]{4,10}") {
let expr = parse(&name);
let _ = needs_locals(&expr);
}
#[test]
fn prop_main_function_detected(_dummy: u8) {
let expr = parse("fun main() { 0 }");
prop_assert!(has_main_function(&expr));
}
#[test]
fn prop_builtin_println_detected(_dummy: u8) {
let expr = parse(r#"println("test")"#);
prop_assert!(uses_builtins(&expr));
}
#[test]
fn prop_string_needs_memory(_dummy: u8) {
let expr = parse(r#""hello""#);
prop_assert!(needs_memory(&expr));
}
}
}
mod round_157_tests {
use super::*;
#[test]
fn test_uses_builtins_in_if_condition() {
let expr = parse(r#"if println("x") > 0 { 1 } else { 2 }"#);
assert!(uses_builtins(&expr));
}
#[test]
fn test_uses_builtins_in_if_then() {
let expr = parse(r#"if true { println("x") } else { 2 }"#);
assert!(uses_builtins(&expr));
}
#[test]
fn test_uses_builtins_in_if_else() {
let expr = parse(r#"if false { 1 } else { println("x") }"#);
assert!(uses_builtins(&expr));
}
#[test]
fn test_uses_builtins_no_else() {
let expr = parse(r#"if true { println("x") }"#);
assert!(uses_builtins(&expr));
}
#[test]
fn test_uses_builtins_binary_left() {
let expr = parse(r#"print("x") + 1"#);
assert!(uses_builtins(&expr));
}
#[test]
fn test_uses_builtins_binary_right() {
let expr = parse(r#"1 + print("x")"#);
assert!(uses_builtins(&expr));
}
#[test]
fn test_uses_builtins_nested_blocks() {
let expr = parse(r#"{ { { println("deep") } } }"#);
assert!(uses_builtins(&expr));
}
#[test]
fn test_uses_builtins_in_lambda() {
let expr = parse(r#"|x| println(x)"#);
assert!(uses_builtins(&expr));
}
#[test]
fn test_uses_builtins_false_with_similar_name() {
let expr = parse("printx(42)");
assert!(!uses_builtins(&expr));
}
#[test]
fn test_uses_builtins_method_call_not_builtin() {
let expr = parse("x + y");
assert!(!uses_builtins(&expr));
}
#[test]
fn test_needs_memory_array_init() {
let expr = parse("[0; 10]");
assert!(needs_memory(&expr));
}
#[test]
fn test_needs_memory_struct_literal() {
let expr = parse("Point { x: 1, y: 2 }");
assert!(needs_memory(&expr));
}
#[test]
fn test_needs_memory_in_if_condition() {
let expr = parse(r#"if "test" == x { 1 } else { 2 }"#);
assert!(needs_memory(&expr));
}
#[test]
fn test_needs_memory_in_if_then() {
let expr = parse(r#"if true { "result" } else { 0 }"#);
assert!(needs_memory(&expr));
}
#[test]
fn test_needs_memory_in_if_else() {
let expr = parse(r#"if false { 0 } else { "fallback" }"#);
assert!(needs_memory(&expr));
}
#[test]
fn test_needs_memory_binary_left() {
let expr = parse(r#""a" + "b""#);
assert!(needs_memory(&expr));
}
#[test]
fn test_needs_memory_binary_right() {
let expr = parse(r#"1 + [2]"#);
assert!(needs_memory(&expr));
}
#[test]
fn test_needs_memory_nested_let() {
let expr = parse(r#"let x = "hello"; let y = [1]; 0"#);
assert!(needs_memory(&expr));
}
#[test]
fn test_needs_memory_float() {
let expr = parse("3.14");
assert!(!needs_memory(&expr));
}
#[test]
fn test_needs_memory_bool() {
let expr = parse("true");
assert!(!needs_memory(&expr));
}
#[test]
fn test_has_main_function_nested_in_block() {
let expr = parse("{ { fun main() { 0 } } }");
assert!(has_main_function(&expr));
}
#[test]
fn test_has_main_function_multiple_functions() {
let expr = parse("{ fun foo() { 1 }; fun main() { 0 }; fun bar() { 2 } }");
assert!(has_main_function(&expr));
}
#[test]
fn test_has_main_function_main_substring() {
let expr = parse("fun main_helper() { 0 }");
assert!(!has_main_function(&expr));
}
#[test]
fn test_has_main_function_no_functions() {
let expr = parse("1 + 2 + 3");
assert!(!has_main_function(&expr));
}
#[test]
fn test_has_return_with_value_in_let_value() {
let expr = parse("let x = return 1; x");
assert!(has_return_with_value(&expr));
}
#[test]
fn test_has_return_with_value_in_binary_left() {
let expr = parse("(return 1) + 2");
assert!(has_return_with_value(&expr));
}
#[test]
fn test_has_return_with_value_in_binary_right() {
let expr = parse("1 + (return 2)");
assert!(has_return_with_value(&expr));
}
#[test]
fn test_has_return_with_value_function_is_separate() {
let expr = parse("fun foo() { return 42 }");
assert!(!has_return_with_value(&expr));
}
#[test]
fn test_has_return_with_value_nested_if() {
let expr = parse("if true { if false { return 1 } else { 2 } } else { 3 }");
assert!(has_return_with_value(&expr));
}
#[test]
fn test_has_return_with_value_while_condition() {
let expr = parse("while (return 1) { }");
assert!(has_return_with_value(&expr));
}
#[test]
fn test_has_return_with_value_while_body() {
let expr = parse("while true { return 1 }");
assert!(has_return_with_value(&expr));
}
#[test]
fn test_needs_locals_in_if_condition() {
let expr = parse("if x { 1 } else { 2 }");
assert!(needs_locals(&expr));
}
#[test]
fn test_needs_locals_in_if_then() {
let expr = parse("if true { let x = 1; x } else { 0 }");
assert!(needs_locals(&expr));
}
#[test]
fn test_needs_locals_in_if_else() {
let expr = parse("if false { 0 } else { y }");
assert!(needs_locals(&expr));
}
#[test]
fn test_needs_locals_while_condition() {
let expr = parse("while x { }");
assert!(needs_locals(&expr));
}
#[test]
fn test_needs_locals_while_body() {
let expr = parse("while true { let x = 1; x }");
assert!(needs_locals(&expr));
}
#[test]
fn test_needs_locals_binary_right() {
let expr = parse("1 + y");
assert!(needs_locals(&expr));
}
#[test]
fn test_needs_locals_no_vars() {
let expr = parse("1 + 2 + 3");
assert!(!needs_locals(&expr));
}
#[test]
fn test_needs_locals_nested_blocks() {
let expr = parse("{ { { x } } }");
assert!(needs_locals(&expr));
}
}
mod additional_coverage_tests {
use super::*;
#[test]
fn test_uses_builtins_match_expr() {
let expr = parse(r#"match println("x") { _ => 0 }"#);
assert!(uses_builtins(&expr));
}
#[test]
fn test_uses_builtins_match_arm_body() {
let expr = parse(r#"match 1 { x => println(x) }"#);
assert!(uses_builtins(&expr));
}
#[test]
fn test_uses_builtins_match_no_builtins() {
let expr = parse("match 1 { x => x + 1 }");
assert!(!uses_builtins(&expr));
}
#[test]
fn test_uses_builtins_let_value() {
let expr = parse(r#"let x = println("hi"); x"#);
assert!(uses_builtins(&expr));
}
#[test]
fn test_uses_builtins_let_body() {
let expr = parse(r#"let x = 1; println(x)"#);
assert!(uses_builtins(&expr));
}
#[test]
fn test_uses_builtins_deeply_nested_if() {
let expr = parse(r#"if true { if false { if true { println("deep") } } }"#);
assert!(uses_builtins(&expr));
}
#[test]
fn test_uses_builtins_function_with_if() {
let expr = parse(r#"fun f() { if true { println("hi") } else { 0 } }"#);
assert!(uses_builtins(&expr));
}
#[test]
fn test_needs_memory_let_pattern_value() {
let expr = parse(r#"let (a, b) = ("x", "y"); 0"#);
assert!(needs_memory(&expr));
}
#[test]
fn test_needs_memory_let_pattern_body() {
let expr = parse(r#"let (a, b) = (1, 2); "result""#);
assert!(needs_memory(&expr));
}
#[test]
fn test_needs_memory_if_no_else() {
let expr = parse(r#"if true { "hello" }"#);
assert!(needs_memory(&expr));
}
#[test]
fn test_needs_memory_deeply_nested_function() {
let expr = parse(r#"fun f() { fun g() { "inner" } }"#);
assert!(needs_memory(&expr));
}
#[test]
fn test_needs_memory_binary_both_sides() {
let expr = parse(r#""a" + "b""#);
assert!(needs_memory(&expr));
}
#[test]
fn test_needs_memory_binary_neither_side() {
let expr = parse("1 + 2");
assert!(!needs_memory(&expr));
}
#[test]
fn test_has_return_with_value_if_condition() {
let expr = parse("if (return 1) { 2 } else { 3 }");
assert!(has_return_with_value(&expr));
}
#[test]
fn test_has_return_with_value_if_then_only() {
let expr = parse("if true { return 1 }");
assert!(has_return_with_value(&expr));
}
#[test]
fn test_has_return_with_value_no_return() {
let expr = parse("1 + 2");
assert!(!has_return_with_value(&expr));
}
#[test]
fn test_has_return_with_value_let_body() {
let expr = parse("let x = 1; return x");
assert!(has_return_with_value(&expr));
}
#[test]
fn test_has_return_with_value_binary() {
let expr = parse("(return 1) * (return 2)");
assert!(has_return_with_value(&expr));
}
#[test]
fn test_needs_locals_empty_block() {
let expr = parse("{ 1 + 2 }");
assert!(!needs_locals(&expr));
}
#[test]
fn test_needs_locals_while_neither() {
let expr = parse("while true { 1 + 2 }");
assert!(!needs_locals(&expr));
}
#[test]
fn test_needs_locals_if_no_else() {
let expr = parse("if x { y }");
assert!(needs_locals(&expr));
}
#[test]
fn test_needs_locals_if_else_only() {
let expr = parse("if true { 1 } else { x }");
assert!(needs_locals(&expr));
}
#[test]
fn test_has_main_function_empty_block() {
let expr = parse("{ 1 }");
assert!(!has_main_function(&expr));
}
#[test]
fn test_has_main_function_identifier() {
let expr = parse("main");
assert!(!has_main_function(&expr));
}
#[test]
fn test_all_utils_on_complex_expr() {
let expr = parse(r#"fun main() { let x = "hello"; println(x); return 0 }"#);
assert!(has_main_function(&expr));
assert!(uses_builtins(&expr));
assert!(needs_memory(&expr));
assert!(needs_locals(&expr));
assert!(!has_return_with_value(&expr));
}
#[test]
fn test_all_utils_on_simple_expr() {
let expr = parse("42");
assert!(!has_main_function(&expr));
assert!(!uses_builtins(&expr));
assert!(!needs_memory(&expr));
assert!(!needs_locals(&expr));
assert!(!has_return_with_value(&expr));
}
#[test]
fn test_uses_builtins_identifier_not_builtin() {
let expr = parse("println_custom");
assert!(!uses_builtins(&expr));
}
#[test]
fn test_needs_memory_empty_list() {
let expr = parse("[]");
assert!(needs_memory(&expr));
}
#[test]
fn test_needs_memory_empty_tuple() {
let expr = parse("()");
assert!(!needs_memory(&expr));
}
#[test]
fn test_has_return_with_value_empty_return() {
let expr = parse("return");
assert!(!has_return_with_value(&expr));
}
#[test]
fn test_needs_locals_binary_left_only() {
let expr = parse("x + 1");
assert!(needs_locals(&expr));
}
#[test]
fn test_needs_locals_binary_both() {
let expr = parse("x + y");
assert!(needs_locals(&expr));
}
}
}