use super::*;
use t_parser_c::parse;
use t_ree::resolve::resolve_module;
use t_ree::{
declaration::{FunctionDefinition, Newtype},
expression::{Literal, MatchArm, MatchPattern},
operator::{ArithmeticOperator, BinaryOperator},
types::FunctionSignature,
};
fn i32_type() -> Type {
Type::Int(IntWidth::W32, Signedness::Signed)
}
fn literal_int(value: u128) -> Expression {
Expression::new(
ExpressionKind::Literal(Literal::Integer(value)),
Some(i32_type()),
)
}
fn compile_function(function: FunctionDefinition) -> String {
compile(&vec![Declaration::Function(function)])
}
fn simple_function(name: &str, body: Block) -> FunctionDefinition {
FunctionDefinition {
name: name.into(),
parameters: Vec::new(),
return_type: i32_type(),
body,
public: false,
span: Default::default(),
}
}
#[test]
fn empty_function() {
let output = compile_function(simple_function("main", Block::empty()));
assert!(output.contains("int32_t main(void) {\n return 0;\n}"));
}
#[test]
fn function_with_return() {
let body = Block::expression(literal_int(0));
let output = compile_function(simple_function("main", body));
assert!(output.contains("return 0;"));
}
#[test]
fn newtype_tuple_emits_struct() {
let module = vec![
Declaration::Type(Newtype {
name: "x".into(),
inner_type: Type::Float(FloatWidth::W64),
public: false,
}),
Declaration::Type(Newtype {
name: "y".into(),
inner_type: Type::Float(FloatWidth::W64),
public: false,
}),
Declaration::Type(Newtype {
name: "Point".into(),
inner_type: Type::Tuple(vec![Type::Named("x".into()), Type::Named("y".into())]),
public: false,
}),
];
let output = compile(&module);
assert!(output.contains("struct Point {"));
assert!(output.contains("x x;"));
assert!(output.contains("y y;"));
}
#[test]
fn newtype_primitive_emits_typedef() {
let module = vec![
Declaration::Type(Newtype {
name: "Byte".into(),
inner_type: Type::Int(IntWidth::W8, Signedness::Unsigned),
public: false,
}),
Declaration::Function(FunctionDefinition {
name: "f".into(),
parameters: Vec::new(),
return_type: Type::unit(),
body: Block {
statements: vec![Statement::Let {
name: "b".into(),
binding: Binding::Value,
declared_type: Some(Type::Named("Byte".into())),
value: literal_int(42),
}],
result: None,
},
public: false,
span: Default::default(),
}),
];
let output = compile(&module);
assert!(output.contains("typedef uint8_t Byte;"));
assert!(output.contains("const Byte b = 42"));
assert!(
!output.contains("struct Byte"),
"primitive newtype should not use struct prefix"
);
}
#[test]
fn binary_operation() {
let body = Block::expression(Expression::new(
ExpressionKind::BinaryOperation(
BinaryOperator::Arithmetic(ArithmeticOperator::Add),
Box::new(literal_int(1)),
Box::new(literal_int(2)),
),
Some(i32_type()),
));
let output = compile_function(simple_function("f", body));
assert!(output.contains("(1 + 2)"));
}
#[test]
fn if_ternary_optimization() {
let body = Block::expression(Expression::new(
ExpressionKind::If {
condition: Box::new(Expression::new(
ExpressionKind::Literal(Literal::Bool(true)),
Some(Type::Bool),
)),
then_branch: Block::expression(literal_int(1)),
else_branch: Some(Block::expression(literal_int(2))),
},
Some(i32_type()),
));
let output = compile_function(simple_function("f", body));
assert!(
output.contains("(true ? 1 : 2)"),
"simple if/else should use ternary: {output}"
);
}
#[test]
fn array_declaration() {
let body = Block {
statements: vec![Statement::Let {
name: "arr".into(),
binding: Binding::Variable,
declared_type: Some(Type::Array(Box::new(i32_type()), 3)),
value: Expression::new(
ExpressionKind::ArrayLiteral(vec![literal_int(1), literal_int(2), literal_int(3)]),
Some(Type::Array(Box::new(i32_type()), 3)),
),
}],
result: None,
};
let output = compile_function(simple_function("f", body));
assert!(
output.contains("int32_t arr[3] = {1, 2, 3}"),
"array decl should use C array syntax: {output}"
);
}
#[test]
fn auto_ref_variable_emits_address() {
let body = Block::expression(Expression::new(
ExpressionKind::Variable("x".into()),
Some(Type::Pointer(Mutability::Mutable, Box::new(i32_type()))),
));
let function = FunctionDefinition {
name: "f".into(),
parameters: Vec::new(),
return_type: i32_type(),
body: Block {
statements: vec![Statement::Let {
name: "x".into(),
binding: Binding::Variable,
declared_type: Some(i32_type()),
value: literal_int(10),
}],
result: body.result,
},
public: false,
span: Default::default(),
};
let output = compile_function(function);
assert!(
output.contains("(&x)"),
"auto-ref var should emit (&x): {output}"
);
}
#[test]
fn auto_ref_deref_cancels() {
let function = FunctionDefinition {
name: "f".into(),
parameters: Vec::new(),
return_type: i32_type(),
body: Block {
statements: vec![Statement::Let {
name: "x".into(),
binding: Binding::Variable,
declared_type: Some(i32_type()),
value: literal_int(10),
}],
result: Some(Box::new(Expression::new(
ExpressionKind::Dereference(Box::new(Expression::new(
ExpressionKind::Variable("x".into()),
Some(Type::Pointer(Mutability::Mutable, Box::new(i32_type()))),
))),
Some(i32_type()),
))),
},
public: false,
span: Default::default(),
};
let output = compile_function(function);
assert!(
output.contains("return x;"),
"deref of auto-ref should cancel to just x: {output}"
);
}
#[test]
fn print_generates_printf() {
let body = Block::expression(Expression::new(
ExpressionKind::Print(vec![literal_int(42)]),
Some(Type::unit()),
));
let output = compile_function(simple_function("f", body));
assert!(output.contains("#include <stdio.h>"));
assert!(output.contains("printf(\"%d\\n\", 42)"));
}
#[test]
fn print_bool_format() {
let body = Block::expression(Expression::new(
ExpressionKind::Print(vec![Expression::new(
ExpressionKind::Literal(Literal::Bool(true)),
Some(Type::Bool),
)]),
Some(Type::unit()),
));
let output = compile_function(simple_function("f", body));
assert!(output.contains("\"true\" : \"false\""));
}
#[test]
fn match_generates_switch() {
let body = Block::expression(Expression::new(
ExpressionKind::Match {
value: Box::new(literal_int(1)),
arms: vec![
MatchArm {
pattern: MatchPattern::Integer(0),
body: Block::expression(literal_int(10)),
},
MatchArm {
pattern: MatchPattern::Wildcard,
body: Block::expression(literal_int(20)),
},
],
},
Some(i32_type()),
));
let output = compile_function(simple_function("f", body));
assert!(output.contains("switch (1)"));
assert!(output.contains("case 0:"));
assert!(output.contains("default:"));
}
#[test]
fn jump_uses_temp_variables() {
let body = Block {
statements: vec![
Statement::Label {
name: "loop".into(),
parameters: vec![
Parameter {
name: "a".into(),
parameter_type: Some(i32_type()),
},
Parameter {
name: "b".into(),
parameter_type: Some(i32_type()),
},
],
initial_arguments: vec![literal_int(0), literal_int(1)],
},
Statement::Jump {
label: "loop".into(),
arguments: vec![
Expression::new(ExpressionKind::Variable("b".into()), Some(i32_type())),
Expression::new(ExpressionKind::Variable("a".into()), Some(i32_type())),
],
},
],
result: None,
};
let output = compile_function(simple_function("f", body));
assert!(
output.contains("_tmp_0") && output.contains("_tmp_1"),
"jump should use temp vars for parallel assignment: {output}"
);
}
#[test]
fn string_literal_cast() {
let body = Block::expression(Expression::new(
ExpressionKind::Literal(Literal::String(b"hello\n".to_vec())),
Some(Type::Pointer(
Mutability::Shared,
Box::new(Type::Int(IntWidth::W8, Signedness::Unsigned)),
)),
));
let output = compile_function(simple_function("f", body));
assert!(output.contains("(const uint8_t *)\"hello\\n\""));
}
#[test]
fn pointer_types() {
let module = vec![Declaration::Function(FunctionDefinition {
name: "f".into(),
parameters: vec![Parameter {
name: "p".into(),
parameter_type: Some(Type::Pointer(Mutability::Mutable, Box::new(i32_type()))),
}],
return_type: Type::unit(),
body: Block::empty(),
public: false,
span: Default::default(),
})];
let output = compile(&module);
assert!(output.contains("int32_t * p"));
}
#[test]
fn function_type() {
let function_type = Type::Function(FunctionSignature {
parameters: vec![i32_type()],
return_type: Box::new(i32_type()),
});
let mut emitter = Emitter::new();
emitter.emit_type(&function_type);
assert_eq!(emitter.output, "int32_t (*)(int32_t)");
}
#[test]
fn no_stdio_without_print() {
let body = Block::expression(literal_int(0));
let output = compile_function(simple_function("main", body));
assert!(
!output.contains("stdio.h"),
"should not include stdio.h without print"
);
}
#[test]
fn type_construction_tuple_emits_compound_literal() {
let module = vec![
Declaration::Type(Newtype {
name: "x".into(),
inner_type: Type::Float(FloatWidth::W64),
public: false,
}),
Declaration::Type(Newtype {
name: "y".into(),
inner_type: Type::Float(FloatWidth::W64),
public: false,
}),
Declaration::Type(Newtype {
name: "Point".into(),
inner_type: Type::Tuple(vec![Type::Named("x".into()), Type::Named("y".into())]),
public: false,
}),
Declaration::Function(FunctionDefinition {
name: "f".into(),
parameters: Vec::new(),
return_type: Type::Named("Point".into()),
body: Block::expression(Expression::new(
ExpressionKind::TypeConstruction(
"Point".into(),
vec![
(
"x".into(),
Expression::new(
ExpressionKind::Literal(Literal::Float(1.0)),
Some(Type::Named("x".into())),
),
),
(
"y".into(),
Expression::new(
ExpressionKind::Literal(Literal::Float(2.0)),
Some(Type::Named("y".into())),
),
),
],
),
Some(Type::Named("Point".into())),
)),
public: false,
span: Default::default(),
}),
];
let output = compile(&module);
assert!(output.contains("(struct Point){.x = 1"));
assert!(output.contains(".y = 2"));
}
#[test]
fn type_construction_primitive_emits_cast() {
let module = vec![
Declaration::Type(Newtype {
name: "Meters".into(),
inner_type: Type::Float(FloatWidth::W64),
public: false,
}),
Declaration::Function(FunctionDefinition {
name: "f".into(),
parameters: Vec::new(),
return_type: Type::Named("Meters".into()),
body: Block::expression(Expression::new(
ExpressionKind::TypeConstruction(
"Meters".into(),
vec![(
"0".into(),
Expression::new(
ExpressionKind::Literal(Literal::Float(42.0)),
Some(Type::Float(FloatWidth::W64)),
),
)],
),
Some(Type::Named("Meters".into())),
)),
public: false,
span: Default::default(),
}),
];
let output = compile(&module);
assert!(output.contains("((Meters)42"));
assert!(
!output.contains("struct Meters"),
"primitive type construction should use cast, not compound literal"
);
}
#[test]
fn downcast_typedef_transparent() {
let module = vec![
Declaration::Type(Newtype {
name: "Meters".into(),
inner_type: Type::Float(FloatWidth::W64),
public: false,
}),
Declaration::Function(FunctionDefinition {
name: "f".into(),
parameters: Vec::new(),
return_type: Type::Float(FloatWidth::W64),
body: Block {
statements: vec![Statement::Let {
name: "m".into(),
binding: Binding::Value,
declared_type: Some(Type::Named("Meters".into())),
value: Expression::new(
ExpressionKind::TypeConstruction(
"Meters".into(),
vec![(
"0".into(),
Expression::new(
ExpressionKind::Literal(Literal::Float(5.0)),
Some(Type::Float(FloatWidth::W64)),
),
)],
),
Some(Type::Named("Meters".into())),
),
}],
result: Some(Box::new(Expression::new(
ExpressionKind::Variable("m".into()),
Some(Type::Named("Meters".into())),
))),
},
public: false,
span: Default::default(),
}),
];
let output = compile(&module);
assert!(output.contains("typedef double Meters;"));
assert!(output.contains("const Meters m = ((Meters)5"));
assert!(output.contains("return m;"));
}
#[test]
fn vector_type_emits_correct_byte_size() {
let vector_type = Type::Vector(Box::new(Type::Float(FloatWidth::W32)), 4);
let mut emitter = Emitter::new();
emitter.emit_type(&vector_type);
assert_eq!(emitter.output, "float __attribute__((vector_size(16)))");
}
#[test]
fn vector_i32_emits_correct_byte_size() {
let vector_type = Type::Vector(Box::new(i32_type()), 8);
let mut emitter = Emitter::new();
emitter.emit_type(&vector_type);
assert_eq!(emitter.output, "int32_t __attribute__((vector_size(32)))");
}
fn parse_resolve_compile(source: &str) -> String {
let mut module = t_parser_c::parse(source).unwrap();
{
let e = t_ree::resolve::resolve_module(&mut module, true);
assert!(e.is_empty(), "{e:?}");
};
compile(&module)
}
fn parse_resolve_compile_lenient(source: &str) -> String {
let mut module = t_parser_c::parse(source).unwrap();
{
let e = t_ree::resolve::resolve_module(&mut module, false);
assert!(e.is_empty(), "{e:?}");
};
compile(&module)
}
#[test]
fn replace_on_mutable_parameter() {
let output = parse_resolve_compile_lenient("fn set_value(p: |i32, value: i32) { p := value; }");
assert!(
output.contains("(*p) = value"),
"p := value should deref through pointer: {output}"
);
}
#[test]
fn replace_on_var_binding() {
let output = parse_resolve_compile("fn f() -> i32 { let a = 10; var x = a; x := 20 }");
assert!(
output.contains("x = 20"),
"var x := 20 should emit x = 20: {output}"
);
}
#[test]
fn replace_field_on_mutable_parameter() {
let output = parse_resolve_compile(
"type x = i32; type y = i32; type point = x & y;\n\
fn set_x(|point, x) { point.x := x; }",
);
assert!(
output.contains("point->x = x"),
"point.x := x on |point should emit point->x = x: {output}"
);
}
#[test]
fn assign_mutable_parameter_via_replace() {
let output =
parse_resolve_compile_lenient("fn set_value(p: |i32, value: i32) -> i32 { p := value }");
assert!(
output.contains("(*p) = value"),
"p := value as expression should deref through pointer: {output}"
);
}
#[test]
fn let_mutable_pointer_emits_const_after_star() {
let output = parse_resolve_compile("fn f(p: |i32) { let x: |i32 = p; }");
assert!(
output.contains("int32_t * const x"),
"let with |T should emit T * const: {output}"
);
}
#[test]
fn let_shared_pointer_keeps_const_prefix() {
let output = parse_resolve_compile("fn f(p: &i32) { let x: &i32 = p; }");
assert!(
output.contains("const int32_t * const x"),
"let with &T should emit const T * const: {output}"
);
}
#[test]
fn print_newtype_over_f64_uses_float_format() {
let output = parse_resolve_compile(
"type x = f64;\n\
fn f() { let v = x: 1.0; print(v); }",
);
assert!(
output.contains("%g"),
"print of newtype over f64 should use %g: {output}"
);
}
#[test]
fn pointer_newtype_field_assign() {
let output = parse_resolve_compile(
"type index = u64;\n\
type next = |index;\n\
type node = index & next;\n\
fn set_next(|node, next) { node.next := next; }",
);
assert!(
!output.contains("type mismatch"),
"pointer newtype field assign should not error: {output}"
);
assert!(
output.contains("node->next = next"),
"field assign on pointer newtype should emit ->: {output}"
);
}
#[test]
fn print_newtype_over_u64_uses_lu_format() {
let output = parse_resolve_compile(
"type count = u64;\n\
fn f() { let c = count: 5; print(c); }",
);
assert!(
output.contains("%lu"),
"print of newtype over u64 should use %lu: {output}"
);
}
#[test]
fn print_field_newtype_over_u64_uses_lu_format() {
let output = parse_resolve_compile(
"type count = u64;\n\
type name = u64;\n\
type entry = count & name;\n\
fn f() { let e = entry { count: 1, name: 2 }; print(e.count); }",
);
assert!(
output.contains("%lu"),
"print of field newtype over u64 should use %lu: {output}"
);
}
#[test]
fn downcast_typedef_emits_cast() {
let output = parse_resolve_compile(
"type x = f32;\n\
fn f() -> f32 { let a = x: 1.5; a.f32 }",
);
assert!(
output.contains("(float)a"),
"downcast on typedef newtype should emit cast: {output}"
);
}
#[test]
fn downcast_nested_typedef_identity_skip() {
let output = parse_resolve_compile(
"type x = f32;\n\
type length = x;\n\
fn f() -> x { let a = length: 1.5; a.x }",
);
assert!(
output.contains("return a"),
"identity downcast on nested typedef should be skipped: {output}"
);
}
#[test]
fn var_struct_field_uses_dot() {
let output = parse_resolve_compile(
"type x = f32;\n\
type y = f32;\n\
type point = x & y;\n\
fn f() { var p = point { x: 1.0, y: 2.0 }; p.x := x: 9.0; }",
);
assert!(
output.contains("p.x"),
"var struct field access should use dot, not arrow: {output}"
);
assert!(
!output.contains("p->x"),
"var struct field should not use arrow: {output}"
);
}
#[test]
fn var_struct_print_format() {
let output = parse_resolve_compile(
"type x = f32;\n\
type y = f32;\n\
type point = x & y;\n\
fn f() { var p = point { x: 1.0, y: 2.0 }; print(p.x, p.y); }",
);
assert!(
output.contains("%g"),
"print of var struct fields should use float format: {output}"
);
}
#[test]
fn duplicate_label_params_qualified() {
let output = parse_resolve_compile(
"fn f() {\n\
label a(i: u64 = 0);\n\
jump a(i + 1);\n\
label b(i: u64 = 0);\n\
jump b(i + 1);\n\
}",
);
assert!(
output.contains("a_i") && output.contains("b_i"),
"label params should be qualified with label name: {output}"
);
assert!(
!output.contains("uint64_t i "),
"should not have unqualified param name: {output}"
);
}
#[test]
fn c_full_slice() {
let output = parse_resolve_compile(
"fn f() {\n\
let arr = [1, 2, 3];\n\
let s = arr[];\n\
print(s.length);\n\
}",
);
assert!(
output.contains(".data = arr"),
"full slice should reference array data: {output}"
);
assert!(
output.contains(".length ="),
"full slice should have length: {output}"
);
}
#[test]
fn c_sub_slice() {
let output = parse_resolve_compile(
"fn f() {\n\
let arr = [10, 20, 30, 40, 50];\n\
let s = arr[1, 4];\n\
print(s.length);\n\
}",
);
assert!(
output.contains("&arr[1]"),
"sub-slice should take address at start index: {output}"
);
assert!(
output.contains("(4) - (1)"),
"sub-slice length should be end - start: {output}"
);
}