#![allow(clippy::unwrap_used)]
#![allow(clippy::expect_used)]
use super::parser::parse;
use crate::ast::restricted::{Expr, Literal, Stmt, Type};
#[test]
fn test_format_escaped_double_braces() {
let ast = parse(r#"fn main() { println!("a {{ b }}"); }"#).unwrap();
match &ast.functions[0].body[0] {
Stmt::Expr(Expr::FunctionCall { name, args }) => {
assert_eq!(name, "rash_println");
assert!(matches!(&args[0], Expr::Literal(Literal::Str(s)) if s.contains('{')));
}
_ => panic!("Expected rash_println"),
}
}
#[test]
fn test_format_with_format_specifier() {
let ast = parse(r#"fn main() { println!("{:>10}", x); }"#).unwrap();
match &ast.functions[0].body[0] {
Stmt::Expr(Expr::FunctionCall { name, args }) => {
assert_eq!(name, "rash_println");
assert_eq!(args.len(), 1);
}
_ => panic!("Expected rash_println"),
}
}
#[test]
fn test_format_multiple_placeholders() {
let ast = parse(r#"fn main() { println!("{} + {} = {}", a, b, c); }"#).unwrap();
match &ast.functions[0].body[0] {
Stmt::Expr(Expr::FunctionCall { name, args }) => {
assert_eq!(name, "rash_println");
match &args[0] {
Expr::FunctionCall { name, args } => {
assert_eq!(name, "__format_concat");
assert!(args.len() >= 5);
}
_ => panic!("Expected __format_concat"),
}
}
_ => panic!("Expected rash_println"),
}
}
#[test]
fn test_format_single_placeholder_no_text() {
let ast = parse(r#"fn main() { println!("{}", x); }"#).unwrap();
match &ast.functions[0].body[0] {
Stmt::Expr(Expr::FunctionCall { name, args }) => {
assert_eq!(name, "rash_println");
assert!(matches!(&args[0], Expr::Variable(n) if n == "x"));
}
_ => panic!("Expected rash_println with variable"),
}
}
#[test]
fn test_println_nested_parens_in_args() {
let ast = parse(r#"fn main() { println!("result: {}", foo(1, 2)); }"#).unwrap();
match &ast.functions[0].body[0] {
Stmt::Expr(Expr::FunctionCall { name, args }) => {
assert_eq!(name, "rash_println");
match &args[0] {
Expr::FunctionCall { name, args } => {
assert_eq!(name, "__format_concat");
assert_eq!(args.len(), 2); }
_ => panic!("Expected __format_concat"),
}
}
_ => panic!("Expected rash_println"),
}
}
#[test]
fn test_println_string_with_comma_inside() {
let ast = parse(r#"fn main() { println!("hello, world"); }"#).unwrap();
match &ast.functions[0].body[0] {
Stmt::Expr(Expr::FunctionCall { name, args }) => {
assert_eq!(name, "rash_println");
assert!(matches!(&args[0], Expr::Literal(Literal::Str(s)) if s == "hello, world"));
}
_ => panic!("Expected rash_println"),
}
}
#[test]
fn test_format_macro_multi_arg() {
let ast = parse(r#"fn main() { let s = format!("x={}", v); }"#).unwrap();
match &ast.functions[0].body[0] {
Stmt::Let {
value: Expr::FunctionCall { name, .. },
..
} => {
assert_eq!(name, "__format_concat");
}
_ => panic!("Expected format! -> __format_concat"),
}
}
#[test]
fn test_format_macro_single_arg_is_literal() {
let ast = parse(r#"fn main() { let s = format!("hello"); }"#).unwrap();
assert!(matches!(
&ast.functions[0].body[0],
Stmt::Let { value: Expr::Literal(Literal::Str(s)), .. } if s == "hello"
));
}
#[test]
fn test_index_with_parenthesized_expr() {
let ast = parse(r#"fn main() { let arr = [1]; arr[(0)] = 5; }"#).unwrap();
match &ast.functions[0].body[1] {
Stmt::Let {
name, declaration, ..
} => {
assert!(!declaration);
assert_eq!(name, "arr_0");
}
_ => panic!("Expected arr_0"),
}
}
#[test]
fn test_index_with_method_call_suffix() {
let ast = parse(r#"fn main() { let arr = [1, 2]; let s = "hi"; arr[s.len()] = 9; }"#).unwrap();
match &ast.functions[0].body[2] {
Stmt::Let {
name, declaration, ..
} => {
assert!(!declaration);
assert!(
name.contains("s_len"),
"Expected s_len in name, got {}",
name
);
}
_ => panic!("Expected index assignment"),
}
}
#[test]
fn test_index_with_unary_minus() {
let ast = parse(r#"fn main() { let arr = [1]; let v = arr[-1]; }"#).unwrap();
match &ast.functions[0].body[1] {
Stmt::Let {
value: Expr::Index { .. },
..
} => {}
_ => panic!("Expected Index expression"),
}
}
#[test]
fn test_index_with_function_call() {
let ast = parse(r#"fn main() { let arr = [1]; arr[hash(key)] = 5; }"#).unwrap();
match &ast.functions[0].body[1] {
Stmt::Let { name, .. } => {
assert!(name.contains("hash"), "Expected hash in name, got {}", name);
assert!(name.contains("key"), "Expected key in name, got {}", name);
}
_ => panic!("Expected index assignment"),
}
}
#[test]
fn test_index_with_function_call_no_args() {
let ast = parse(r#"fn main() { let arr = [1]; arr[f()] = 5; }"#).unwrap();
match &ast.functions[0].body[1] {
Stmt::Let { name, .. } => {
assert!(name.contains("f"), "Expected f in name, got {}", name);
}
_ => panic!("Expected index assignment"),
}
}
#[test]
fn test_field_access_expr() {
let src = r#"
struct Point { x: u32, y: u32 }
fn main() { let p = Point { x: 1, y: 2 }; let v = p.x; }
"#;
let ast = parse(src).unwrap();
match &ast.functions[0].body[1] {
Stmt::Let {
value: Expr::Index { index, .. },
..
} => {
assert!(matches!(**index, Expr::Literal(Literal::I32(0))));
}
_ => panic!("Expected Index from field access"),
}
}
#[test]
fn test_return_no_value_in_expr_produces_block() {
let ast = parse(r#"fn main() { let f = |x| { return; }; }"#).unwrap();
match &ast.functions[0].body[0] {
Stmt::Let {
value: Expr::Block(stmts),
..
} => {
assert!(matches!(&stmts[0], Stmt::Return(None)));
}
_ => panic!("Expected Block with Return(None) from closure"),
}
}
#[test]
fn test_type_tuple_param() {
let ast = parse(r#"#[bashrs::main] fn f(t: (u32, u32)) { let x = t; }"#).unwrap();
assert!(matches!(ast.functions[0].params[0].param_type, Type::Str));
}
#[test]
fn test_type_array_param() {
let ast = parse(r#"#[bashrs::main] fn f(a: [u32; 3]) { let x = a; }"#).unwrap();
assert!(matches!(ast.functions[0].params[0].param_type, Type::Str));
}
#[test]
fn test_type_slice_reference() {
let ast = parse(r#"#[bashrs::main] fn f(s: &[u32]) { let x = s; }"#).unwrap();
assert!(matches!(ast.functions[0].params[0].param_type, Type::Str));
}
#[test]
fn test_type_unknown_path_defaults_to_str() {
let ast = parse(r#"#[bashrs::main] fn f(v: Vec<String>) { let x = v; }"#).unwrap();
assert!(matches!(ast.functions[0].params[0].param_type, Type::Str));
}
#[test]
fn test_let_tuple_without_init_is_error() {
assert!(parse(r#"fn main() { let (a, b); }"#).is_err());
}
#[test]
fn test_complex_param_pattern_is_error() {
assert!(parse(r#"fn main((a, b): (u32, u32)) { let x = a; }"#).is_err());
}
#[test]
fn test_match_as_expression() {
let ast = parse(r#"fn main() { let x = match v { 0 => 1, _ => 2 }; }"#).unwrap();
match &ast.functions[0].body[0] {
Stmt::Let {
value: Expr::Block(stmts),
..
} => {
assert!(matches!(&stmts[0], Stmt::Match { .. }));
}
_ => panic!("Expected Block with Match from match expression"),
}
}
#[test]
fn test_compound_assign_on_field() {
let src = r#"
struct S { v: u32 }
impl S { fn inc(&mut self) { self.v += 1; } }
fn main() { let x = 0; }
"#;
let ast = parse(src).unwrap();
let inc_fn = ast.functions.iter().find(|f| f.name == "inc").unwrap();
match &inc_fn.body[0] {
Stmt::Let {
name, declaration, ..
} => {
assert_eq!(name, "v");
assert!(!declaration);
}
_ => panic!("Expected field compound assignment"),
}
}
#[test]
fn test_compound_assign_on_index() {
let ast = parse(r#"fn main() { let mut arr = [1, 2]; arr[0] += 5; }"#).unwrap();
match &ast.functions[0].body[1] {
Stmt::Let {
name, declaration, ..
} => {
assert_eq!(name, "arr_0");
assert!(!declaration);
}
_ => panic!("Expected arr_0 compound assignment"),
}
}
#[test]
fn test_compound_assign_on_deref() {
let ast = parse(r#"fn main() { let mut x = 0; let p = &mut x; *p += 1; }"#).unwrap();
match &ast.functions[0].body[2] {
Stmt::Let {
name, declaration, ..
} => {
assert_eq!(name, "p");
assert!(!declaration);
}
_ => panic!("Expected deref compound assignment"),
}
}
#[test]
fn test_let_expr_with_variable_pattern() {
let ast = parse(r#"fn main() { let opt = 1; if let x = opt { let a = x; } }"#).unwrap();
assert!(matches!(&ast.functions[0].body[1], Stmt::If { .. }));
}
#[test]
fn test_char_literal_is_error() {
assert!(parse(r#"fn main() { let c = 'a'; }"#).is_err());
}
#[test]
fn test_rash_main_attribute() {
let ast = parse(r#"#[rash::main] fn my_entry() { let x = 1; }"#).unwrap();
assert_eq!(ast.entry_point, "my_entry");
}
#[test]
fn test_unnamed_field_assignment_in_compound() {
let src = r#"
struct Wrapper(u32);
impl Wrapper { fn bump(&mut self) { self.0 += 1; } }
fn main() { let x = 0; }
"#;
let ast = parse(src).unwrap();
let bump_fn = ast.functions.iter().find(|f| f.name == "bump").unwrap();
match &bump_fn.body[0] {
Stmt::Let { name, .. } => {
assert!(
name.starts_with("field_"),
"Expected field_ prefix, got {}",
name
);
}
_ => panic!("Expected unnamed field compound assignment"),
}
}
#[test]
fn test_enum_item_skipped() {
let src = r#"enum Color { Red, Green } fn main() { let x = 0; }"#;
let ast = parse(src).unwrap();
assert_eq!(ast.entry_point, "main");
}
#[test]
fn test_closure_expr_body() {
let ast = parse(r#"fn main() { let f = |x| x * 2; }"#).unwrap();
match &ast.functions[0].body[0] {
Stmt::Let {
value: Expr::Binary { .. },
..
} => {}
_ => panic!("Expected Binary from closure expression body"),
}
}
#[test]
fn test_index_read_expr() {
let ast = parse(r#"fn main() { let arr = [10, 20]; let v = arr[1]; }"#).unwrap();
match &ast.functions[0].body[1] {
Stmt::Let {
value: Expr::Index { object, index },
..
} => {
assert!(matches!(**object, Expr::Variable(ref n) if n == "arr"));
assert!(matches!(**index, Expr::Literal(Literal::U32(1))));
}
_ => panic!("Expected Index read"),
}
}
#[test]
fn test_negative_range_pattern() {
let src = r#"fn main() { match x { -10..=-1 => { let a = 1; } _ => {} } }"#;
let ast = parse(src).unwrap();
match &ast.functions[0].body[0] {
Stmt::Match { arms, .. } => match &arms[0].pattern {
crate::ast::restricted::Pattern::Range {
start,
end,
inclusive,
} => {
assert!(matches!(start, Literal::I32(-10)));
assert!(matches!(end, Literal::I32(-1)));
assert!(*inclusive);
}
_ => panic!("Expected Range pattern"),
},
_ => panic!("Expected Match"),
}
}