use decy_analyzer::output_params::{OutputParamDetector, ParameterKind};
use decy_hir::{BinaryOperator, HirExpression, HirFunction, HirParameter, HirStatement, HirType};
fn create_test_function(
name: &str,
params: Vec<HirParameter>,
return_type: HirType,
body: Vec<HirStatement>,
) -> HirFunction {
HirFunction::new_with_body(name.to_string(), return_type, params, body)
}
fn create_pointer_param(name: &str) -> HirParameter {
HirParameter::new(name.to_string(), HirType::Pointer(Box::new(HirType::Int)))
}
fn create_char_pointer_param(name: &str) -> HirParameter {
HirParameter::new(name.to_string(), HirType::Pointer(Box::new(HirType::Char)))
}
#[test]
fn test_detect_simple_output_parameter() {
let func = create_test_function(
"parse",
vec![create_char_pointer_param("input"), create_pointer_param("result")],
HirType::Int,
vec![
HirStatement::DerefAssignment {
target: HirExpression::Variable("result".to_string()),
value: HirExpression::IntLiteral(42),
},
HirStatement::Return(Some(HirExpression::IntLiteral(0))),
],
);
let detector = OutputParamDetector::new();
let output_params = detector.detect(&func);
assert_eq!(output_params.len(), 1);
assert_eq!(output_params[0].name, "result");
assert_eq!(output_params[0].kind, ParameterKind::Output);
assert!(output_params[0].is_fallible);
}
#[test]
fn test_input_parameter_not_detected_as_output() {
let func = create_test_function(
"increment",
vec![create_pointer_param("value")],
HirType::Int,
vec![
HirStatement::VariableDeclaration {
name: "old".to_string(),
var_type: HirType::Int,
initializer: Some(HirExpression::Dereference(Box::new(HirExpression::Variable(
"value".to_string(),
)))),
},
HirStatement::DerefAssignment {
target: HirExpression::Variable("value".to_string()),
value: HirExpression::BinaryOp {
op: BinaryOperator::Add,
left: Box::new(HirExpression::Variable("old".to_string())),
right: Box::new(HirExpression::IntLiteral(1)),
},
},
HirStatement::Return(Some(HirExpression::IntLiteral(0))),
],
);
let detector = OutputParamDetector::new();
let output_params = detector.detect(&func);
assert_eq!(output_params.len(), 0);
}
#[test]
fn test_multiple_output_parameters() {
let func = create_test_function(
"parse_coords",
vec![
create_char_pointer_param("input"),
create_pointer_param("x"),
create_pointer_param("y"),
],
HirType::Int,
vec![
HirStatement::DerefAssignment {
target: HirExpression::Variable("x".to_string()),
value: HirExpression::IntLiteral(10),
},
HirStatement::DerefAssignment {
target: HirExpression::Variable("y".to_string()),
value: HirExpression::IntLiteral(20),
},
HirStatement::Return(Some(HirExpression::IntLiteral(0))),
],
);
let detector = OutputParamDetector::new();
let output_params = detector.detect(&func);
assert_eq!(output_params.len(), 2);
let x_param = output_params.iter().find(|p| p.name == "x").unwrap();
let y_param = output_params.iter().find(|p| p.name == "y").unwrap();
assert_eq!(x_param.kind, ParameterKind::Output);
assert_eq!(y_param.kind, ParameterKind::Output);
}
#[test]
fn test_fallible_operation_detection() {
let func = create_test_function(
"try_parse",
vec![create_char_pointer_param("input"), create_pointer_param("result")],
HirType::Int,
vec![
HirStatement::If {
condition: HirExpression::BinaryOp {
op: BinaryOperator::Equal,
left: Box::new(HirExpression::Variable("input".to_string())),
right: Box::new(HirExpression::NullLiteral),
},
then_block: vec![HirStatement::Return(Some(HirExpression::IntLiteral(-1)))],
else_block: None,
},
HirStatement::DerefAssignment {
target: HirExpression::Variable("result".to_string()),
value: HirExpression::IntLiteral(42),
},
HirStatement::Return(Some(HirExpression::IntLiteral(0))),
],
);
let detector = OutputParamDetector::new();
let output_params = detector.detect(&func);
assert_eq!(output_params.len(), 1);
assert_eq!(output_params[0].name, "result");
assert!(output_params[0].is_fallible, "Should detect fallible operation");
}
#[test]
fn test_non_fallible_operation() {
let func = create_test_function(
"get_default",
vec![create_pointer_param("result")],
HirType::Void,
vec![HirStatement::DerefAssignment {
target: HirExpression::Variable("result".to_string()),
value: HirExpression::IntLiteral(42),
}],
);
let detector = OutputParamDetector::new();
let output_params = detector.detect(&func);
assert_eq!(output_params.len(), 1);
assert_eq!(output_params[0].name, "result");
assert!(!output_params[0].is_fallible, "Void return is not fallible");
}
#[test]
fn test_parameter_not_written_not_detected() {
let func = create_test_function(
"no_op",
vec![create_pointer_param("result")],
HirType::Int,
vec![HirStatement::Return(Some(HirExpression::IntLiteral(0)))],
);
let detector = OutputParamDetector::new();
let output_params = detector.detect(&func);
assert_eq!(output_params.len(), 0, "Unwritten parameter should not be detected");
}
#[test]
fn test_conditional_write_detected_as_output() {
let func = create_test_function(
"maybe_write",
vec![HirParameter::new("flag".to_string(), HirType::Int), create_pointer_param("result")],
HirType::Int,
vec![
HirStatement::If {
condition: HirExpression::Variable("flag".to_string()),
then_block: vec![HirStatement::DerefAssignment {
target: HirExpression::Variable("result".to_string()),
value: HirExpression::IntLiteral(42),
}],
else_block: None,
},
HirStatement::Return(Some(HirExpression::IntLiteral(0))),
],
);
let detector = OutputParamDetector::new();
let output_params = detector.detect(&func);
assert_eq!(output_params.len(), 1);
assert_eq!(output_params[0].name, "result");
}
#[test]
fn test_non_pointer_not_detected() {
let func = create_test_function(
"add",
vec![
HirParameter::new("a".to_string(), HirType::Int),
HirParameter::new("b".to_string(), HirType::Int),
],
HirType::Int,
vec![HirStatement::Return(Some(HirExpression::BinaryOp {
op: BinaryOperator::Add,
left: Box::new(HirExpression::Variable("a".to_string())),
right: Box::new(HirExpression::Variable("b".to_string())),
}))],
);
let detector = OutputParamDetector::new();
let output_params = detector.detect(&func);
assert_eq!(output_params.len(), 0, "Non-pointer parameters should not be detected");
}
#[test]
fn test_pointer_read_only_not_output() {
let func = create_test_function(
"read_value",
vec![create_pointer_param("ptr")],
HirType::Int,
vec![HirStatement::Return(Some(HirExpression::Dereference(Box::new(
HirExpression::Variable("ptr".to_string()),
))))],
);
let detector = OutputParamDetector::new();
let output_params = detector.detect(&func);
assert_eq!(output_params.len(), 0, "Read-only pointer should not be output");
}
#[test]
fn test_double_pointer_output() {
let func = create_test_function(
"create_object",
vec![HirParameter::new(
"obj".to_string(),
HirType::Pointer(Box::new(HirType::Pointer(Box::new(HirType::Struct(
"Object".to_string(),
))))),
)],
HirType::Int,
vec![
HirStatement::DerefAssignment {
target: HirExpression::Variable("obj".to_string()),
value: HirExpression::Malloc {
size: Box::new(HirExpression::Sizeof { type_name: "Object".to_string() }),
},
},
HirStatement::Return(Some(HirExpression::IntLiteral(0))),
],
);
let detector = OutputParamDetector::new();
let output_params = detector.detect(&func);
assert_eq!(output_params.len(), 1);
assert_eq!(output_params[0].name, "obj");
assert_eq!(output_params[0].kind, ParameterKind::Output);
}
#[test]
fn test_output_param_detector_default() {
let detector = OutputParamDetector::default();
let func = create_test_function("empty", vec![], HirType::Void, vec![]);
let params = detector.detect(&func);
assert!(params.is_empty());
}
#[test]
fn test_detect_no_parameters() {
let func =
create_test_function("noop", vec![], HirType::Void, vec![HirStatement::Return(None)]);
let detector = OutputParamDetector::new();
assert!(detector.detect(&func).is_empty());
}
#[test]
fn test_detect_non_pointer_only_params() {
let func = create_test_function(
"add",
vec![
HirParameter::new("a".to_string(), HirType::Int),
HirParameter::new("b".to_string(), HirType::Int),
],
HirType::Int,
vec![HirStatement::Return(Some(HirExpression::BinaryOp {
op: decy_hir::BinaryOperator::Add,
left: Box::new(HirExpression::Variable("a".to_string())),
right: Box::new(HirExpression::Variable("b".to_string())),
}))],
);
let detector = OutputParamDetector::new();
assert!(detector.detect(&func).is_empty());
}