use super::restricted::{MatchArm, Pattern};
use super::*;
use proptest::prelude::*;
use rstest::*;
#[test]
fn test_restricted_ast_validation() {
let ast = RestrictedAst {
functions: vec![Function {
name: "main".to_string(),
params: vec![],
return_type: Type::Str,
body: vec![Stmt::Let {
name: "x".to_string(),
value: Expr::Literal(restricted::Literal::U32(42)),
declaration: true,
}],
}],
entry_point: "main".to_string(),
};
assert!(ast.validate().is_ok());
}
#[test]
fn test_missing_entry_point() {
let ast = RestrictedAst {
functions: vec![Function {
name: "helper".to_string(),
params: vec![],
return_type: Type::Str,
body: vec![Stmt::Let {
name: "x".to_string(),
value: Expr::Literal(restricted::Literal::U32(1)),
declaration: true,
}],
}],
entry_point: "main".to_string(),
};
assert!(ast.validate().is_err());
assert!(ast
.validate()
.unwrap_err()
.contains("Entry point function 'main' not found"));
}
#[test]
fn test_function_validation() {
let func = Function {
name: "test".to_string(),
params: vec![],
return_type: Type::Str,
body: vec![],
};
assert!(func.validate().is_ok());
}
#[test]
fn test_recursion_allowed() {
let ast = RestrictedAst {
functions: vec![Function {
name: "recursive".to_string(),
params: vec![],
return_type: Type::Str,
body: vec![Stmt::Expr(Expr::FunctionCall {
name: "recursive".to_string(),
args: vec![],
})],
}],
entry_point: "recursive".to_string(),
};
assert!(ast.validate().is_ok());
}
#[test]
fn test_indirect_recursion_allowed() {
let ast = RestrictedAst {
functions: vec![
Function {
name: "a".to_string(),
params: vec![],
return_type: Type::Str,
body: vec![Stmt::Expr(Expr::FunctionCall {
name: "b".to_string(),
args: vec![],
})],
},
Function {
name: "b".to_string(),
params: vec![],
return_type: Type::Str,
body: vec![Stmt::Expr(Expr::FunctionCall {
name: "a".to_string(),
args: vec![],
})],
},
],
entry_point: "a".to_string(),
};
assert!(ast.validate().is_ok());
}
#[rstest]
#[case(Type::Bool)]
#[case(Type::U32)]
#[case(Type::Str)]
fn test_allowed_types(#[case] typ: Type) {
assert!(typ.is_allowed());
}
#[test]
fn test_complex_types_allowed() {
let result_type = Type::Result {
ok_type: Box::new(Type::Str),
err_type: Box::new(Type::Str),
};
assert!(result_type.is_allowed());
let option_type = Type::Option {
inner_type: Box::new(Type::U32),
};
assert!(option_type.is_allowed());
}
#[test]
fn test_expression_validation() {
let valid_expr = Expr::Binary {
op: restricted::BinaryOp::Add,
left: Box::new(Expr::Literal(restricted::Literal::U32(1))),
right: Box::new(Expr::Literal(restricted::Literal::U32(2))),
};
assert!(valid_expr.validate().is_ok());
let function_call = Expr::FunctionCall {
name: "test".to_string(),
args: vec![
Expr::Literal(restricted::Literal::Str("hello".to_string())),
Expr::Variable("x".to_string()),
],
};
assert!(function_call.validate().is_ok());
}
#[test]
fn test_statement_validation() {
let let_stmt = Stmt::Let {
name: "x".to_string(),
value: Expr::Literal(restricted::Literal::U32(42)),
declaration: true,
};
assert!(let_stmt.validate().is_ok());
let if_stmt = Stmt::If {
condition: Expr::Literal(restricted::Literal::Bool(true)),
then_block: vec![Stmt::Expr(Expr::Literal(restricted::Literal::Str(
"then".to_string(),
)))],
else_block: Some(vec![Stmt::Expr(Expr::Literal(restricted::Literal::Str(
"else".to_string(),
)))]),
};
assert!(if_stmt.validate().is_ok());
}
#[test]
fn test_function_call_collection() {
let func = Function {
name: "main".to_string(),
params: vec![],
return_type: Type::Str,
body: vec![
Stmt::Expr(Expr::FunctionCall {
name: "helper1".to_string(),
args: vec![],
}),
Stmt::Let {
name: "x".to_string(),
value: Expr::FunctionCall {
name: "helper2".to_string(),
args: vec![],
},
declaration: true,
},
],
};
let mut calls = Vec::new();
func.collect_function_calls(&mut calls);
assert_eq!(calls.len(), 2);
assert!(calls.contains(&"helper1".to_string()));
assert!(calls.contains(&"helper2".to_string()));
}
proptest! {
#[test]
fn test_bool_literal_validation(value in prop::bool::ANY) {
let expr = Expr::Literal(restricted::Literal::Bool(value));
assert!(expr.validate().is_ok());
}
#[test]
fn test_u32_literal_validation(value in 0u32..1000u32) {
let expr = Expr::Literal(restricted::Literal::U32(value));
assert!(expr.validate().is_ok());
}
#[test]
fn test_string_literal_validation(value in "[^\0]*") {
let expr = Expr::Literal(restricted::Literal::Str(value));
assert!(expr.validate().is_ok());
}
#[test]
fn test_variable_names_are_valid_identifiers(name in "[a-zA-Z_][a-zA-Z0-9_]*") {
let expr = Expr::Variable(name);
assert!(expr.validate().is_ok());
}
}
#[test]
fn test_pattern_binds_variable() {
let pattern = Pattern::Variable("x".to_string());
assert!(pattern.binds_variable("x"));
assert!(!pattern.binds_variable("y"));
let tuple_pattern = Pattern::Tuple(vec![
Pattern::Variable("a".to_string()),
Pattern::Variable("b".to_string()),
]);
assert!(tuple_pattern.binds_variable("a"));
assert!(tuple_pattern.binds_variable("b"));
assert!(!tuple_pattern.binds_variable("c"));
let struct_pattern = Pattern::Struct {
name: "Point".to_string(),
fields: vec![
("x".to_string(), Pattern::Variable("x_val".to_string())),
("y".to_string(), Pattern::Variable("y_val".to_string())),
],
};
assert!(struct_pattern.binds_variable("x_val"));
assert!(struct_pattern.binds_variable("y_val"));
assert!(!struct_pattern.binds_variable("z_val"));
let literal_pattern = Pattern::Literal(restricted::Literal::U32(42));
assert!(!literal_pattern.binds_variable("x"));
let wildcard_pattern = Pattern::Wildcard;
assert!(!wildcard_pattern.binds_variable("x"));
}
#[test]
fn test_pattern_validation() {
let literal_pattern = Pattern::Literal(restricted::Literal::Bool(true));
assert!(literal_pattern.validate().is_ok());
let var_pattern = Pattern::Variable("x".to_string());
assert!(var_pattern.validate().is_ok());
let wildcard_pattern = Pattern::Wildcard;
assert!(wildcard_pattern.validate().is_ok());
let tuple_pattern = Pattern::Tuple(vec![
Pattern::Variable("a".to_string()),
Pattern::Literal(restricted::Literal::U32(42)),
Pattern::Wildcard,
]);
assert!(tuple_pattern.validate().is_ok());
let struct_pattern = Pattern::Struct {
name: "Point".to_string(),
fields: vec![
("x".to_string(), Pattern::Variable("x_val".to_string())),
("y".to_string(), Pattern::Wildcard),
],
};
assert!(struct_pattern.validate().is_ok());
}
#[test]
fn test_type_is_allowed() {
assert!(Type::Void.is_allowed());
assert!(Type::Bool.is_allowed());
assert!(Type::U32.is_allowed());
assert!(Type::Str.is_allowed());
let result_type = Type::Result {
ok_type: Box::new(Type::U32),
err_type: Box::new(Type::Str),
};
assert!(result_type.is_allowed());
let option_type = Type::Option {
inner_type: Box::new(Type::Bool),
};
assert!(option_type.is_allowed());
let nested_result = Type::Result {
ok_type: Box::new(Type::Option {
inner_type: Box::new(Type::U32),
}),
err_type: Box::new(Type::Str),
};
assert!(nested_result.is_allowed());
}
#[test]
fn test_expr_array_try_block_handling() {
let array_expr = Expr::Array(vec![
Expr::Literal(restricted::Literal::U32(1)),
Expr::Literal(restricted::Literal::U32(2)),
]);
assert!(array_expr.validate().is_ok());
let try_expr = Expr::Try {
expr: Box::new(Expr::Literal(restricted::Literal::U32(42))),
};
assert!(try_expr.validate().is_ok());
let block_expr = Expr::Block(vec![Stmt::Let {
name: "x".to_string(),
value: Expr::Literal(restricted::Literal::U32(42)),
declaration: true,
}]);
assert!(block_expr.validate().is_ok());
}
#[cfg(test)]
mod tests_tests_expr_collect {
use super::*;
include!("tests_tests_expr_collect.rs");
}