use super::*;
use crate::abi::expr::*;
use crate::abi::types::*;
#[cfg(test)]
mod dependency_tests {
use super::*;
fn create_literal_expr(value: u64) -> ExprKind {
ExprKind::Literal(LiteralExpr::U64(value))
}
fn create_field_ref_expr(path: Vec<&str>) -> ExprKind {
ExprKind::FieldRef(FieldRefExpr {
path: path.into_iter().map(|s| s.to_string()).collect(),
})
}
fn create_u32_primitive() -> TypeKind {
TypeKind::Primitive(PrimitiveType::Integral(IntegralType::U32))
}
fn create_u8_primitive() -> TypeKind {
TypeKind::Primitive(PrimitiveType::Integral(IntegralType::U8))
}
fn create_type_ref(name: &str) -> TypeKind {
TypeKind::TypeRef(TypeRefType {
name: name.to_string(),
package: None,
comment: None,
})
}
#[test]
fn test_simple_type_dependency() {
let mut analyzer = DependencyAnalyzer::new();
let typedefs = vec![
TypeDef {
name: "BaseType".to_string(),
format: None,
kind: create_u32_primitive(),
},
TypeDef {
name: "DerivedType".to_string(),
format: None,
kind: TypeKind::Struct(StructType {
container_attributes: Default::default(),
fields: vec![StructField {
name: "base_field".to_string(),
format: None,
field_type: create_type_ref("BaseType"),
}],
}),
},
];
let analysis = analyzer.analyze_multiple_typedefs(&typedefs);
assert!(analysis.cycles.is_empty());
assert!(analysis.topological_order.is_some());
assert!(analysis.layout_violations.is_empty());
let topo_order = analysis.topological_order.unwrap();
let base_pos = topo_order.iter().position(|x| x == "BaseType").unwrap();
let derived_pos = topo_order.iter().position(|x| x == "DerivedType").unwrap();
assert!(base_pos < derived_pos);
}
#[test]
fn test_circular_type_dependency() {
let mut analyzer = DependencyAnalyzer::new();
let typedefs = vec![
TypeDef {
name: "TypeA".to_string(),
format: None,
kind: TypeKind::Struct(StructType {
container_attributes: Default::default(),
fields: vec![StructField {
name: "field_b".to_string(),
format: None,
field_type: create_type_ref("TypeB"),
}],
}),
},
TypeDef {
name: "TypeB".to_string(),
format: None,
kind: TypeKind::Struct(StructType {
container_attributes: Default::default(),
fields: vec![StructField {
name: "field_a".to_string(),
format: None,
field_type: create_type_ref("TypeA"),
}],
}),
},
];
let analysis = analyzer.analyze_multiple_typedefs(&typedefs);
assert!(!analysis.cycles.is_empty());
assert!(analysis.topological_order.is_none());
let cycle = &analysis.cycles[0];
assert!(cycle.cycle.contains(&"TypeA".to_string()));
assert!(cycle.cycle.contains(&"TypeB".to_string()));
}
#[test]
fn test_valid_enum_with_constant_tag() {
let mut analyzer = DependencyAnalyzer::new();
let typedefs = vec![TypeDef {
name: "MyEnum".to_string(),
format: None,
kind: TypeKind::Enum(EnumType {
container_attributes: Default::default(),
tag_ref: create_literal_expr(42), variants: vec![EnumVariant {
name: "Variant1".to_string(),
tag_value: 1,
variant_type: create_u32_primitive(),
}],
}),
}];
let analysis = analyzer.analyze_multiple_typedefs(&typedefs);
assert!(analysis.cycles.is_empty());
assert!(analysis.layout_violations.is_empty());
}
#[test]
fn test_invalid_enum_tag_layout_cycle() {
let mut analyzer = DependencyAnalyzer::new();
let typedefs = vec![
TypeDef {
name: "Container".to_string(),
format: None,
kind: TypeKind::Struct(StructType {
container_attributes: Default::default(),
fields: vec![
StructField {
name: "my_enum".to_string(),
format: None,
field_type: create_type_ref("MyEnum"),
},
StructField {
name: "other_field".to_string(),
format: None,
field_type: create_u32_primitive(),
},
],
}),
},
TypeDef {
name: "MyEnum".to_string(),
format: None,
kind: TypeKind::Enum(EnumType {
container_attributes: Default::default(),
tag_ref: create_field_ref_expr(vec!["Container", "other_field"]),
variants: vec![EnumVariant {
name: "Variant1".to_string(),
tag_value: 1,
variant_type: create_u32_primitive(),
}],
}),
},
];
let analysis = analyzer.analyze_multiple_typedefs(&typedefs);
println!("Layout violations: {}", analysis.layout_violations.len());
for violation in &analysis.layout_violations {
println!(" {}: {}", violation.violating_type, violation.reason);
}
assert!(!analysis.layout_violations.is_empty());
let violation = &analysis.layout_violations[0];
assert!(violation.violating_type == "MyEnum" || violation.violating_type == "Container");
assert!(
violation.reason.contains("layout cycle")
|| violation.reason.contains("creating a layout cycle")
|| violation.reason.contains("forward dependency")
);
}
#[test]
fn test_valid_array_with_constant_size() {
let mut analyzer = DependencyAnalyzer::new();
let typedefs = vec![TypeDef {
name: "MyArray".to_string(),
format: None,
kind: TypeKind::Array(ArrayType {
container_attributes: Default::default(),
size: create_literal_expr(10), element_type: Box::new(create_u8_primitive()),
jagged: false,
}),
}];
let analysis = analyzer.analyze_multiple_typedefs(&typedefs);
assert!(analysis.cycles.is_empty());
assert!(analysis.layout_violations.is_empty());
}
#[test]
fn test_invalid_array_size_layout_cycle() {
let mut analyzer = DependencyAnalyzer::new();
let typedefs = vec![
TypeDef {
name: "Container".to_string(),
format: None,
kind: TypeKind::Struct(StructType {
container_attributes: Default::default(),
fields: vec![
StructField {
name: "dynamic_array".to_string(),
format: None,
field_type: create_type_ref("MyArray"),
},
StructField {
name: "size_field".to_string(),
format: None,
field_type: create_u32_primitive(),
},
],
}),
},
TypeDef {
name: "MyArray".to_string(),
format: None,
kind: TypeKind::Array(ArrayType {
container_attributes: Default::default(),
size: create_field_ref_expr(vec!["Container", "size_field"]),
element_type: Box::new(create_u8_primitive()),
jagged: false,
}),
},
];
let analysis = analyzer.analyze_multiple_typedefs(&typedefs);
assert!(!analysis.layout_violations.is_empty());
let violation = &analysis.layout_violations[0];
assert!(violation.violating_type == "MyArray" || violation.violating_type == "Container");
assert!(
violation.reason.contains("layout cycle")
|| violation.reason.contains("creating a layout cycle")
|| violation.reason.contains("forward dependency")
);
}
#[test]
fn test_forward_field_reference_violation() {
let mut analyzer = DependencyAnalyzer::new();
let typedefs = vec![
TypeDef {
name: "BadStruct".to_string(),
format: None,
kind: TypeKind::Struct(StructType {
container_attributes: Default::default(),
fields: vec![
StructField {
name: "early_field".to_string(),
format: None,
field_type: create_type_ref("ArrayWithForwardRef"),
},
StructField {
name: "middle_field".to_string(),
format: None,
field_type: create_u32_primitive(),
},
StructField {
name: "late_field".to_string(),
format: None,
field_type: create_u32_primitive(),
},
],
}),
},
TypeDef {
name: "ArrayWithForwardRef".to_string(),
format: None,
kind: TypeKind::Array(ArrayType {
container_attributes: Default::default(),
size: create_field_ref_expr(vec!["BadStruct", "late_field"]),
element_type: Box::new(create_u8_primitive()),
jagged: false,
}),
},
];
let analysis = analyzer.analyze_multiple_typedefs(&typedefs);
assert!(!analysis.layout_violations.is_empty());
let violation = &analysis.layout_violations[0];
assert_eq!(violation.violating_type, "BadStruct");
assert!(violation.reason.contains("forward dependency"));
}
#[test]
fn test_complex_expression_dependencies() {
let mut analyzer = DependencyAnalyzer::new();
let typedefs = vec![
TypeDef {
name: "ComplexEnum".to_string(),
format: None,
kind: TypeKind::Enum(EnumType {
container_attributes: Default::default(),
tag_ref: ExprKind::Add(AddExpr {
left: Box::new(create_field_ref_expr(vec!["SomeStruct", "field1"])),
right: Box::new(ExprKind::Mul(MulExpr {
left: Box::new(create_field_ref_expr(vec!["SomeStruct", "field2"])),
right: Box::new(create_literal_expr(2)),
})),
}),
variants: vec![EnumVariant {
name: "Variant1".to_string(),
tag_value: 1,
variant_type: create_u32_primitive(),
}],
}),
},
TypeDef {
name: "SomeStruct".to_string(),
format: None,
kind: TypeKind::Struct(StructType {
container_attributes: Default::default(),
fields: vec![
StructField {
name: "field1".to_string(),
format: None,
field_type: create_u32_primitive(),
},
StructField {
name: "field2".to_string(),
format: None,
field_type: create_u32_primitive(),
},
],
}),
},
];
let analysis = analyzer.analyze_multiple_typedefs(&typedefs);
let enum_deps = analysis
.graph
.edges
.iter()
.filter(|dep| dep.from == "ComplexEnum")
.collect::<Vec<_>>();
assert!(enum_deps.len() >= 2);
assert!(enum_deps.iter().any(|dep| dep.to.contains("field1")));
assert!(enum_deps.iter().any(|dep| dep.to.contains("field2")));
}
#[test]
fn test_sizeof_and_alignof_dependencies() {
let mut analyzer = DependencyAnalyzer::new();
let typedefs = vec![
TypeDef {
name: "ArrayWithSizeof".to_string(),
format: None,
kind: TypeKind::Array(ArrayType {
container_attributes: Default::default(),
size: ExprKind::Sizeof(SizeofExpr {
type_name: "SomeType".to_string(),
}),
element_type: Box::new(create_u8_primitive()),
jagged: false,
}),
},
TypeDef {
name: "SomeType".to_string(),
format: None,
kind: create_u32_primitive(),
},
];
let analysis = analyzer.analyze_multiple_typedefs(&typedefs);
assert!(analysis.cycles.is_empty());
assert!(analysis.layout_violations.is_empty());
let sizeof_deps = analysis
.graph
.edges
.iter()
.filter(|dep| dep.from == "ArrayWithSizeof" && dep.to == "SomeType")
.collect::<Vec<_>>();
assert_eq!(sizeof_deps.len(), 1);
assert_eq!(sizeof_deps[0].kind, DependencyKind::TypeReference);
}
#[test]
fn test_union_dependencies() {
let mut analyzer = DependencyAnalyzer::new();
let typedefs = vec![
TypeDef {
name: "MyUnion".to_string(),
format: None,
kind: TypeKind::Union(UnionType {
container_attributes: Default::default(),
variants: vec![
UnionVariant {
name: "variant1".to_string(),
variant_type: create_type_ref("TypeA"),
},
UnionVariant {
name: "variant2".to_string(),
variant_type: create_type_ref("TypeB"),
},
],
}),
},
TypeDef {
name: "TypeA".to_string(),
format: None,
kind: create_u32_primitive(),
},
TypeDef {
name: "TypeB".to_string(),
format: None,
kind: create_u8_primitive(),
},
];
let analysis = analyzer.analyze_multiple_typedefs(&typedefs);
assert!(analysis.cycles.is_empty());
assert!(analysis.layout_violations.is_empty());
let union_deps = analysis
.graph
.edges
.iter()
.filter(|dep| dep.from == "MyUnion")
.collect::<Vec<_>>();
assert_eq!(union_deps.len(), 2);
assert!(union_deps.iter().any(|dep| dep.to == "TypeA"));
assert!(union_deps.iter().any(|dep| dep.to == "TypeB"));
}
#[test]
fn test_packed_struct_analysis() {
let mut analyzer = DependencyAnalyzer::new();
let typedefs = vec![TypeDef {
name: "PackedStruct".to_string(),
format: None,
kind: TypeKind::Struct(StructType {
container_attributes: ContainerAttributes {
packed: true,
aligned: 0,
comment: None,
},
fields: vec![
StructField {
name: "field1".to_string(),
format: None,
field_type: create_u8_primitive(),
},
StructField {
name: "field2".to_string(),
format: None,
field_type: create_u32_primitive(),
},
],
}),
}];
let analysis = analyzer.analyze_multiple_typedefs(&typedefs);
assert!(analysis.cycles.is_empty());
assert!(analysis.layout_violations.is_empty());
}
#[test]
fn test_aligned_struct_analysis() {
let mut analyzer = DependencyAnalyzer::new();
let typedefs = vec![TypeDef {
name: "AlignedStruct".to_string(),
format: None,
kind: TypeKind::Struct(StructType {
container_attributes: ContainerAttributes {
packed: false,
aligned: 16,
comment: Some("16-byte aligned".to_string()),
},
fields: vec![StructField {
name: "field1".to_string(),
format: None,
field_type: create_u32_primitive(),
}],
}),
}];
let analysis = analyzer.analyze_multiple_typedefs(&typedefs);
assert!(analysis.cycles.is_empty());
assert!(analysis.layout_violations.is_empty());
}
#[test]
fn test_deeply_nested_type_cycle() {
let mut analyzer = DependencyAnalyzer::new();
let typedefs = vec![
TypeDef {
name: "TypeA".to_string(),
format: None,
kind: TypeKind::Struct(StructType {
container_attributes: Default::default(),
fields: vec![StructField {
name: "field_b".to_string(),
format: None,
field_type: create_type_ref("TypeB"),
}],
}),
},
TypeDef {
name: "TypeB".to_string(),
format: None,
kind: TypeKind::Struct(StructType {
container_attributes: Default::default(),
fields: vec![StructField {
name: "field_c".to_string(),
format: None,
field_type: create_type_ref("TypeC"),
}],
}),
},
TypeDef {
name: "TypeC".to_string(),
format: None,
kind: TypeKind::Struct(StructType {
container_attributes: Default::default(),
fields: vec![StructField {
name: "field_a".to_string(),
format: None,
field_type: create_type_ref("TypeA"), }],
}),
},
];
let analysis = analyzer.analyze_multiple_typedefs(&typedefs);
assert!(!analysis.cycles.is_empty());
assert!(analysis.topological_order.is_none());
let cycle = &analysis.cycles[0];
assert_eq!(cycle.cycle.len(), 4); assert_eq!(cycle.cycle[0], cycle.cycle[3]); let cycle_types: std::collections::HashSet<&String> = cycle.cycle.iter().collect();
assert!(cycle_types.contains(&"TypeA".to_string()));
assert!(cycle_types.contains(&"TypeB".to_string()));
assert!(cycle_types.contains(&"TypeC".to_string()));
}
#[test]
fn test_transitive_layout_dependency_violation() {
let mut analyzer = DependencyAnalyzer::new();
let typedefs = vec![
TypeDef {
name: "Container".to_string(),
format: None,
kind: TypeKind::Struct(StructType {
container_attributes: Default::default(),
fields: vec![
StructField {
name: "type_a".to_string(),
format: None,
field_type: create_type_ref("TypeA"),
},
StructField {
name: "type_b".to_string(),
format: None,
field_type: create_type_ref("TypeB"),
},
StructField {
name: "reference_field".to_string(),
format: None,
field_type: create_u32_primitive(),
},
],
}),
},
TypeDef {
name: "TypeA".to_string(),
format: None,
kind: TypeKind::Struct(StructType {
container_attributes: Default::default(),
fields: vec![StructField {
name: "dynamic_array".to_string(),
format: None,
field_type: create_type_ref("DynamicArray"),
}],
}),
},
TypeDef {
name: "DynamicArray".to_string(),
format: None,
kind: TypeKind::Array(ArrayType {
container_attributes: Default::default(),
size: create_field_ref_expr(vec!["Container", "reference_field"]),
element_type: Box::new(create_u8_primitive()),
jagged: false,
}),
},
TypeDef {
name: "TypeB".to_string(),
format: None,
kind: create_u32_primitive(),
},
];
let analysis = analyzer.analyze_multiple_typedefs(&typedefs);
assert!(!analysis.layout_violations.is_empty());
let violations: Vec<_> = analysis
.layout_violations
.iter()
.filter(|v| v.violating_type == "DynamicArray")
.collect();
assert!(!violations.is_empty());
let violation = violations[0];
assert!(violation.reason.contains("transitive"));
}
#[test]
fn test_multiple_field_references_in_expression() {
let mut analyzer = DependencyAnalyzer::new();
let typedefs = vec![
TypeDef {
name: "ComplexArray".to_string(),
format: None,
kind: TypeKind::Array(ArrayType {
container_attributes: Default::default(),
size: ExprKind::Add(AddExpr {
left: Box::new(create_field_ref_expr(vec!["DataStruct", "size1"])),
right: Box::new(ExprKind::Sub(SubExpr {
left: Box::new(create_field_ref_expr(vec!["DataStruct", "size2"])),
right: Box::new(create_literal_expr(5)),
})),
}),
element_type: Box::new(create_u8_primitive()),
jagged: false,
}),
},
TypeDef {
name: "DataStruct".to_string(),
format: None,
kind: TypeKind::Struct(StructType {
container_attributes: Default::default(),
fields: vec![
StructField {
name: "size1".to_string(),
format: None,
field_type: create_u32_primitive(),
},
StructField {
name: "size2".to_string(),
format: None,
field_type: create_u32_primitive(),
},
],
}),
},
];
let analysis = analyzer.analyze_multiple_typedefs(&typedefs);
let array_deps = analysis
.graph
.edges
.iter()
.filter(|dep| dep.from == "ComplexArray")
.collect::<Vec<_>>();
assert!(array_deps.len() >= 2);
let dep_targets: Vec<&String> = array_deps.iter().map(|dep| &dep.to).collect();
assert!(dep_targets.iter().any(|target| target.contains("size1")));
assert!(dep_targets.iter().any(|target| target.contains("size2")));
}
#[test]
fn test_array_with_non_constant_element_type() {
let mut analyzer = DependencyAnalyzer::new();
let typedefs = vec![
TypeDef {
name: "BadArray".to_string(),
format: None,
kind: TypeKind::Array(ArrayType {
container_attributes: Default::default(),
size: create_literal_expr(10), element_type: Box::new(create_type_ref("DynamicStruct")), jagged: false,
}),
},
TypeDef {
name: "DynamicStruct".to_string(),
format: None,
kind: TypeKind::Struct(StructType {
container_attributes: Default::default(),
fields: vec![StructField {
name: "dynamic_array".to_string(),
format: None,
field_type: TypeKind::Array(ArrayType {
container_attributes: Default::default(),
size: create_field_ref_expr(vec!["some_field"]), element_type: Box::new(create_u8_primitive()),
jagged: false,
}),
}],
}),
},
];
let analysis = analyzer.analyze_multiple_typedefs(&typedefs);
assert!(!analysis.layout_violations.is_empty());
let violation = analysis
.layout_violations
.iter()
.find(|v| v.violating_type == "BadArray")
.expect("Should find violation for BadArray");
assert!(
violation
.reason
.contains("element type with non-constant size")
);
assert!(
violation
.violating_expression
.contains("array element type")
);
}
#[test]
fn test_array_with_deeply_nested_non_constant_element_type() {
let mut analyzer = DependencyAnalyzer::new();
let typedefs = vec![
TypeDef {
name: "DeepArray".to_string(),
format: None,
kind: TypeKind::Array(ArrayType {
container_attributes: Default::default(),
size: create_literal_expr(5), element_type: Box::new(create_type_ref("WrapperStruct")), jagged: false,
}),
},
TypeDef {
name: "WrapperStruct".to_string(),
format: None,
kind: TypeKind::Struct(StructType {
container_attributes: Default::default(),
fields: vec![
StructField {
name: "constant_field".to_string(),
format: None,
field_type: create_u32_primitive(), },
StructField {
name: "enum_field".to_string(),
format: None,
field_type: create_type_ref("DynamicEnum"), },
],
}),
},
TypeDef {
name: "DynamicEnum".to_string(),
format: None,
kind: TypeKind::Enum(EnumType {
container_attributes: Default::default(),
tag_ref: create_field_ref_expr(vec!["some_external_field"]),
variants: vec![
EnumVariant {
name: "Variant1".to_string(),
tag_value: 1,
variant_type: create_u32_primitive(),
},
EnumVariant {
name: "Variant2".to_string(),
tag_value: 2,
variant_type: create_u64_primitive(), },
],
}),
},
];
let analysis = analyzer.analyze_multiple_typedefs(&typedefs);
assert!(!analysis.layout_violations.is_empty());
let violation = analysis
.layout_violations
.iter()
.find(|v| v.violating_type == "DeepArray")
.expect("Should find violation for DeepArray");
assert!(
violation
.reason
.contains("element type with non-constant size")
);
}
#[test]
fn test_shift_operations_in_expressions() {
let mut analyzer = DependencyAnalyzer::new();
let typedefs = vec![
TypeDef {
name: "ShiftArray".to_string(),
format: None,
kind: TypeKind::Array(ArrayType {
container_attributes: Default::default(),
size: ExprKind::RightShift(RightShiftExpr {
left: Box::new(ExprKind::LeftShift(LeftShiftExpr {
left: Box::new(create_field_ref_expr(vec![
"ConfigStruct",
"base_size",
])),
right: Box::new(create_field_ref_expr(vec![
"ConfigStruct",
"shift_amount",
])),
})),
right: Box::new(create_literal_expr(1)),
}),
element_type: Box::new(create_u8_primitive()),
jagged: false,
}),
},
TypeDef {
name: "ConfigStruct".to_string(),
format: None,
kind: TypeKind::Struct(StructType {
container_attributes: Default::default(),
fields: vec![
StructField {
name: "base_size".to_string(),
format: None,
field_type: create_u32_primitive(),
},
StructField {
name: "shift_amount".to_string(),
format: None,
field_type: create_u32_primitive(),
},
],
}),
},
];
let analysis = analyzer.analyze_multiple_typedefs(&typedefs);
assert!(analysis.cycles.is_empty());
assert!(analysis.layout_violations.is_empty());
let array_deps = analysis
.graph
.edges
.iter()
.filter(|dep| dep.from == "ShiftArray")
.collect::<Vec<_>>();
assert!(array_deps.len() >= 2);
let dep_targets: Vec<&String> = array_deps.iter().map(|dep| &dep.to).collect();
assert!(
dep_targets
.iter()
.any(|target| target.contains("base_size"))
);
assert!(
dep_targets
.iter()
.any(|target| target.contains("shift_amount"))
);
}
#[test]
fn test_empty_typedefs() {
let mut analyzer = DependencyAnalyzer::new();
let analysis = analyzer.analyze_multiple_typedefs(&[]);
assert!(analysis.cycles.is_empty());
assert!(analysis.layout_violations.is_empty());
assert_eq!(analysis.topological_order, Some(vec![]));
}
#[test]
fn test_single_primitive_typedef() {
let mut analyzer = DependencyAnalyzer::new();
let typedefs = vec![TypeDef {
name: "MyU32".to_string(),
format: None,
kind: create_u32_primitive(),
}];
let analysis = analyzer.analyze_multiple_typedefs(&typedefs);
assert!(analysis.cycles.is_empty());
assert!(analysis.layout_violations.is_empty());
assert_eq!(analysis.topological_order, Some(vec!["MyU32".to_string()]));
}
#[test]
fn test_constant_expression_analysis() {
let literal_expr = create_literal_expr(42);
assert!(literal_expr.is_constant());
let field_ref_expr = create_field_ref_expr(vec!["field"]);
assert!(!field_ref_expr.is_constant());
let sizeof_expr = ExprKind::Sizeof(SizeofExpr {
type_name: "SomeType".to_string(),
});
assert!(sizeof_expr.is_constant());
let mixed_expr = ExprKind::Add(AddExpr {
left: Box::new(literal_expr),
right: Box::new(field_ref_expr),
});
assert!(!mixed_expr.is_constant());
let const_expr = ExprKind::Mul(MulExpr {
left: Box::new(create_literal_expr(10)),
right: Box::new(create_literal_expr(20)),
});
assert!(const_expr.is_constant());
let left_shift_expr = ExprKind::LeftShift(LeftShiftExpr {
left: Box::new(create_literal_expr(4)),
right: Box::new(create_literal_expr(2)),
});
assert!(left_shift_expr.is_constant());
let right_shift_expr = ExprKind::RightShift(RightShiftExpr {
left: Box::new(create_literal_expr(16)),
right: Box::new(create_literal_expr(2)),
});
assert!(right_shift_expr.is_constant());
let mixed_shift_expr = ExprKind::LeftShift(LeftShiftExpr {
left: Box::new(create_literal_expr(8)),
right: Box::new(create_field_ref_expr(vec!["shift_amount"])),
});
assert!(!mixed_shift_expr.is_constant());
let popcount_const_expr = ExprKind::Popcount(PopcountExpr {
operand: Box::new(create_literal_expr(15)), });
assert!(popcount_const_expr.is_constant());
let popcount_non_const_expr = ExprKind::Popcount(PopcountExpr {
operand: Box::new(create_field_ref_expr(vec!["some_field"])),
});
assert!(!popcount_non_const_expr.is_constant());
}
#[test]
fn test_duplicate_type_names() {
let mut analyzer = DependencyAnalyzer::new();
let typedefs = vec![
TypeDef {
name: "DuplicateName".to_string(),
format: None,
kind: create_u32_primitive(),
},
TypeDef {
name: "DuplicateName".to_string(), format: None,
kind: create_u8_primitive(),
},
];
let analysis = analyzer.analyze_multiple_typedefs(&typedefs);
assert!(!analysis.validation_errors.is_empty());
let duplicate_error = analysis
.validation_errors
.iter()
.find(|e| e.error_type == "DuplicateTypeName")
.expect("Should find duplicate type name error");
assert_eq!(duplicate_error.violating_type, "DuplicateName");
assert_eq!(duplicate_error.duplicate_name, "DuplicateName");
}
#[test]
fn test_duplicate_struct_field_names() {
let mut analyzer = DependencyAnalyzer::new();
let typedefs = vec![TypeDef {
name: "BadStruct".to_string(),
format: None,
kind: TypeKind::Struct(StructType {
container_attributes: Default::default(),
fields: vec![
StructField {
name: "duplicate_field".to_string(),
format: None,
field_type: create_u32_primitive(),
},
StructField {
name: "duplicate_field".to_string(), format: None,
field_type: create_u8_primitive(),
},
StructField {
name: "unique_field".to_string(),
format: None,
field_type: create_u32_primitive(),
},
],
}),
}];
let analysis = analyzer.analyze_multiple_typedefs(&typedefs);
assert!(!analysis.validation_errors.is_empty());
let duplicate_error = analysis
.validation_errors
.iter()
.find(|e| e.error_type == "DuplicateFieldName")
.expect("Should find duplicate field name error");
assert_eq!(duplicate_error.violating_type, "BadStruct");
assert_eq!(duplicate_error.duplicate_name, "duplicate_field");
}
#[test]
fn test_duplicate_union_variant_names() {
let mut analyzer = DependencyAnalyzer::new();
let typedefs = vec![TypeDef {
name: "BadUnion".to_string(),
format: None,
kind: TypeKind::Union(UnionType {
container_attributes: Default::default(),
variants: vec![
UnionVariant {
name: "duplicate_variant".to_string(),
variant_type: create_u32_primitive(),
},
UnionVariant {
name: "duplicate_variant".to_string(), variant_type: create_u8_primitive(),
},
],
}),
}];
let analysis = analyzer.analyze_multiple_typedefs(&typedefs);
assert!(!analysis.validation_errors.is_empty());
let duplicate_error = analysis
.validation_errors
.iter()
.find(|e| e.error_type == "DuplicateVariantName")
.expect("Should find duplicate variant name error");
assert_eq!(duplicate_error.violating_type, "BadUnion");
assert_eq!(duplicate_error.duplicate_name, "duplicate_variant");
}
#[test]
fn test_duplicate_enum_variant_names() {
let mut analyzer = DependencyAnalyzer::new();
let typedefs = vec![TypeDef {
name: "BadEnum".to_string(),
format: None,
kind: TypeKind::Enum(EnumType {
container_attributes: Default::default(),
tag_ref: create_literal_expr(0),
variants: vec![
EnumVariant {
name: "duplicate_variant".to_string(),
tag_value: 1,
variant_type: create_u32_primitive(),
},
EnumVariant {
name: "duplicate_variant".to_string(), tag_value: 2,
variant_type: create_u8_primitive(),
},
],
}),
}];
let analysis = analyzer.analyze_multiple_typedefs(&typedefs);
assert!(!analysis.validation_errors.is_empty());
let duplicate_error = analysis
.validation_errors
.iter()
.find(|e| e.error_type == "DuplicateVariantName")
.expect("Should find duplicate variant name error");
assert_eq!(duplicate_error.violating_type, "BadEnum");
assert_eq!(duplicate_error.duplicate_name, "duplicate_variant");
}
#[test]
fn test_duplicate_enum_tag_values() {
let mut analyzer = DependencyAnalyzer::new();
let typedefs = vec![TypeDef {
name: "BadEnum".to_string(),
format: None,
kind: TypeKind::Enum(EnumType {
container_attributes: Default::default(),
tag_ref: create_literal_expr(0),
variants: vec![
EnumVariant {
name: "variant1".to_string(),
tag_value: 1,
variant_type: create_u32_primitive(),
},
EnumVariant {
name: "variant2".to_string(),
tag_value: 1, variant_type: create_u8_primitive(),
},
],
}),
}];
let analysis = analyzer.analyze_multiple_typedefs(&typedefs);
assert!(!analysis.validation_errors.is_empty());
let duplicate_error = analysis
.validation_errors
.iter()
.find(|e| e.error_type == "DuplicateTagValue")
.expect("Should find duplicate tag value error");
assert_eq!(duplicate_error.violating_type, "BadEnum");
assert_eq!(duplicate_error.duplicate_name, "1");
}
#[test]
fn test_multiple_validation_errors() {
let mut analyzer = DependencyAnalyzer::new();
let typedefs = vec![
TypeDef {
name: "DuplicateType".to_string(),
format: None,
kind: create_u32_primitive(),
},
TypeDef {
name: "DuplicateType".to_string(),
format: None,
kind: create_u8_primitive(),
},
TypeDef {
name: "BadStruct".to_string(),
format: None,
kind: TypeKind::Struct(StructType {
container_attributes: Default::default(),
fields: vec![
StructField {
name: "bad_field".to_string(),
format: None,
field_type: create_u32_primitive(),
},
StructField {
name: "bad_field".to_string(),
format: None,
field_type: create_u8_primitive(),
},
],
}),
},
TypeDef {
name: "BadEnum".to_string(),
format: None,
kind: TypeKind::Enum(EnumType {
container_attributes: Default::default(),
tag_ref: create_literal_expr(0),
variants: vec![
EnumVariant {
name: "bad_variant".to_string(),
tag_value: 5,
variant_type: create_u32_primitive(),
},
EnumVariant {
name: "bad_variant".to_string(),
tag_value: 5,
variant_type: create_u8_primitive(),
},
],
}),
},
];
let analysis = analyzer.analyze_multiple_typedefs(&typedefs);
assert!(analysis.validation_errors.len() >= 4);
let error_types: HashSet<String> = analysis
.validation_errors
.iter()
.map(|e| e.error_type.clone())
.collect();
assert!(error_types.contains("DuplicateTypeName"));
assert!(error_types.contains("DuplicateFieldName"));
assert!(error_types.contains("DuplicateVariantName"));
assert!(error_types.contains("DuplicateTagValue"));
}
#[test]
fn test_valid_names_no_errors() {
let mut analyzer = DependencyAnalyzer::new();
let typedefs = vec![
TypeDef {
name: "ValidStruct".to_string(),
format: None,
kind: TypeKind::Struct(StructType {
container_attributes: Default::default(),
fields: vec![
StructField {
name: "field1".to_string(),
format: None,
field_type: create_u32_primitive(),
},
StructField {
name: "field2".to_string(),
format: None,
field_type: create_u8_primitive(),
},
],
}),
},
TypeDef {
name: "ValidEnum".to_string(),
format: None,
kind: TypeKind::Enum(EnumType {
container_attributes: Default::default(),
tag_ref: create_literal_expr(0),
variants: vec![
EnumVariant {
name: "variant1".to_string(),
tag_value: 1,
variant_type: create_u32_primitive(),
},
EnumVariant {
name: "variant2".to_string(),
tag_value: 2,
variant_type: create_u8_primitive(),
},
],
}),
},
TypeDef {
name: "ValidUnion".to_string(),
format: None,
kind: TypeKind::Union(UnionType {
container_attributes: Default::default(),
variants: vec![
UnionVariant {
name: "variant1".to_string(),
variant_type: create_u32_primitive(),
},
UnionVariant {
name: "variant2".to_string(),
variant_type: create_u8_primitive(),
},
],
}),
},
];
let analysis = analyzer.analyze_multiple_typedefs(&typedefs);
assert!(analysis.validation_errors.is_empty());
}
#[test]
fn test_layout_dependency_chain_detection() {
let mut analyzer = DependencyAnalyzer::new();
let typedefs = vec![
TypeDef {
name: "RootStruct".to_string(),
format: None,
kind: TypeKind::Struct(StructType {
container_attributes: Default::default(),
fields: vec![
StructField {
name: "intermediate".to_string(),
format: None,
field_type: create_type_ref("IntermediateStruct"),
},
StructField {
name: "target_field".to_string(),
format: None,
field_type: create_u32_primitive(),
},
],
}),
},
TypeDef {
name: "IntermediateStruct".to_string(),
format: None,
kind: TypeKind::Struct(StructType {
container_attributes: Default::default(),
fields: vec![StructField {
name: "problematic_array".to_string(),
format: None,
field_type: create_type_ref("ProblematicArray"),
}],
}),
},
TypeDef {
name: "ProblematicArray".to_string(),
format: None,
kind: TypeKind::Array(ArrayType {
container_attributes: Default::default(),
size: create_field_ref_expr(vec!["RootStruct", "target_field"]),
element_type: Box::new(create_u8_primitive()),
jagged: false,
}),
},
];
let analysis = analyzer.analyze_multiple_typedefs(&typedefs);
assert!(!analysis.layout_violations.is_empty());
let violation = analysis
.layout_violations
.iter()
.find(|v| v.violating_type == "ProblematicArray")
.expect("Should find violation for ProblematicArray");
assert!(violation.dependency_chain.len() > 1);
assert!(violation.reason.contains("transitive"));
}
#[test]
fn test_valid_enum_tag_after_enum_in_struct() {
let mut analyzer = DependencyAnalyzer::new();
let typedefs = vec![
TypeDef {
name: "ValidStruct".to_string(),
format: None,
kind: TypeKind::Struct(StructType {
container_attributes: Default::default(),
fields: vec![
StructField {
name: "my_enum".to_string(),
format: None,
field_type: create_type_ref("ConstantSizeEnum"),
},
StructField {
name: "tag_field".to_string(),
format: None,
field_type: create_u32_primitive(),
},
StructField {
name: "other_data".to_string(),
format: None,
field_type: create_u64_primitive(),
},
],
}),
},
TypeDef {
name: "ConstantSizeEnum".to_string(),
format: None,
kind: TypeKind::Enum(EnumType {
container_attributes: Default::default(),
tag_ref: create_field_ref_expr(vec!["ValidStruct", "tag_field"]),
variants: vec![
EnumVariant {
name: "VariantA".to_string(),
tag_value: 1,
variant_type: create_u32_primitive(), },
EnumVariant {
name: "VariantB".to_string(),
tag_value: 2,
variant_type: create_u32_primitive(), },
EnumVariant {
name: "VariantC".to_string(),
tag_value: 3,
variant_type: create_u32_primitive(), },
],
}),
},
];
let analysis = analyzer.analyze_multiple_typedefs(&typedefs);
assert!(
analysis.layout_violations.is_empty(),
"Should not have layout violations for enum with constant-sized variants referencing later tag field"
);
assert!(analysis.cycles.is_empty());
assert!(analysis.topological_order.is_some());
assert!(!analysis.graph.edges.is_empty());
}
fn create_u64_primitive() -> TypeKind {
TypeKind::Primitive(PrimitiveType::Integral(IntegralType::U64))
}
fn create_size_discriminated_union(
_name: &str,
variants: Vec<(&str, u64, TypeKind)>,
) -> TypeKind {
TypeKind::SizeDiscriminatedUnion(SizeDiscriminatedUnionType {
container_attributes: Default::default(),
variants: variants
.into_iter()
.map(
|(variant_name, size, variant_type)| SizeDiscriminatedVariant {
name: variant_name.to_string(),
expected_size: size,
variant_type,
},
)
.collect(),
})
}
#[test]
fn test_valid_size_discriminated_union() {
let mut analyzer = DependencyAnalyzer::new();
let typedefs = vec![TypeDef {
name: "TokenAccountUnion".to_string(),
format: None,
kind: create_size_discriminated_union(
"TokenAccountUnion",
vec![
("token_account", 165, create_u32_primitive()),
("token_mint", 82, create_u64_primitive()),
],
),
}];
let analysis = analyzer.analyze_multiple_typedefs(&typedefs);
assert!(analysis.cycles.is_empty());
assert!(analysis.layout_violations.is_empty());
assert!(analysis.validation_errors.is_empty());
}
#[test]
fn test_size_discriminated_union_duplicate_sizes() {
let mut analyzer = DependencyAnalyzer::new();
let typedefs = vec![TypeDef {
name: "BadUnion".to_string(),
format: None,
kind: create_size_discriminated_union(
"BadUnion",
vec![
("variant1", 100, create_u32_primitive()),
("variant2", 100, create_u64_primitive()), ],
),
}];
let analysis = analyzer.analyze_multiple_typedefs(&typedefs);
assert!(!analysis.layout_violations.is_empty());
let violation = &analysis.layout_violations[0];
assert_eq!(violation.violating_type, "BadUnion");
assert!(violation.reason.contains("same expected size"));
}
#[test]
fn test_size_discriminated_union_insufficient_variants() {
let mut analyzer = DependencyAnalyzer::new();
let typedefs = vec![TypeDef {
name: "SingleVariantUnion".to_string(),
format: None,
kind: create_size_discriminated_union(
"SingleVariantUnion",
vec![("only_variant", 100, create_u32_primitive())],
),
}];
let analysis = analyzer.analyze_multiple_typedefs(&typedefs);
assert!(!analysis.layout_violations.is_empty());
let violation = &analysis.layout_violations[0];
assert_eq!(violation.violating_type, "SingleVariantUnion");
assert!(violation.reason.contains("at least 2 variants"));
}
#[test]
fn test_size_discriminated_union_as_sole_factor_in_struct() {
let mut analyzer = DependencyAnalyzer::new();
let typedefs = vec![
TypeDef {
name: "TokenAccountUnion".to_string(),
format: None,
kind: create_size_discriminated_union(
"TokenAccountUnion",
vec![
("token_account", 165, create_u32_primitive()),
("token_mint", 82, create_u64_primitive()),
],
),
},
TypeDef {
name: "ValidContainer".to_string(),
format: None,
kind: TypeKind::Struct(StructType {
container_attributes: Default::default(),
fields: vec![
StructField {
name: "fixed_header".to_string(),
format: None,
field_type: create_u32_primitive(), },
StructField {
name: "account_data".to_string(),
format: None,
field_type: create_type_ref("TokenAccountUnion"), },
],
}),
},
];
let analysis = analyzer.analyze_multiple_typedefs(&typedefs);
assert!(analysis.cycles.is_empty());
assert!(analysis.layout_violations.is_empty());
}
#[test]
fn test_size_discriminated_union_with_other_variable_factor() {
let mut analyzer = DependencyAnalyzer::new();
let typedefs = vec![
TypeDef {
name: "TokenAccountUnion".to_string(),
format: None,
kind: create_size_discriminated_union(
"TokenAccountUnion",
vec![
("token_account", 165, create_u32_primitive()),
("token_mint", 82, create_u64_primitive()),
],
),
},
TypeDef {
name: "InvalidContainer".to_string(),
format: None,
kind: TypeKind::Struct(StructType {
container_attributes: Default::default(),
fields: vec![
StructField {
name: "account_data".to_string(),
format: None,
field_type: create_type_ref("TokenAccountUnion"),
},
StructField {
name: "dynamic_array".to_string(),
format: None,
field_type: TypeKind::Array(ArrayType {
container_attributes: Default::default(),
size: create_field_ref_expr(vec!["some_field"]), element_type: Box::new(create_u8_primitive()),
jagged: false,
}),
},
],
}),
},
];
let analysis = analyzer.analyze_multiple_typedefs(&typedefs);
assert!(!analysis.layout_violations.is_empty());
let violation = analysis
.layout_violations
.iter()
.find(|v| v.violating_type == "InvalidContainer")
.expect("Should find violation for InvalidContainer");
assert!(violation.reason.contains("not the sole factor affecting"));
}
#[test]
fn test_size_discriminated_union_in_array() {
let mut analyzer = DependencyAnalyzer::new();
let typedefs = vec![
TypeDef {
name: "TokenAccountUnion".to_string(),
format: None,
kind: create_size_discriminated_union(
"TokenAccountUnion",
vec![
("token_account", 165, create_u32_primitive()),
("token_mint", 82, create_u64_primitive()),
],
),
},
TypeDef {
name: "InvalidArray".to_string(),
format: None,
kind: TypeKind::Array(ArrayType {
container_attributes: Default::default(),
size: create_literal_expr(10),
element_type: Box::new(create_type_ref("TokenAccountUnion")), jagged: false,
}),
},
];
let analysis = analyzer.analyze_multiple_typedefs(&typedefs);
assert!(!analysis.layout_violations.is_empty());
let violation = analysis
.layout_violations
.iter()
.find(|v| v.violating_type == "InvalidArray")
.expect("Should find violation for InvalidArray");
assert!(
violation
.reason
.contains("Arrays cannot have elements with variable sizes")
);
}
#[test]
fn test_size_discriminated_union_in_regular_union() {
let mut analyzer = DependencyAnalyzer::new();
let typedefs = vec![
TypeDef {
name: "TokenAccountUnion".to_string(),
format: None,
kind: create_size_discriminated_union(
"TokenAccountUnion",
vec![
("token_account", 165, create_u32_primitive()),
("token_mint", 82, create_u64_primitive()),
],
),
},
TypeDef {
name: "InvalidRegularUnion".to_string(),
format: None,
kind: TypeKind::Union(UnionType {
container_attributes: Default::default(),
variants: vec![
UnionVariant {
name: "variant1".to_string(),
variant_type: create_u32_primitive(),
},
UnionVariant {
name: "variant2".to_string(),
variant_type: create_type_ref("TokenAccountUnion"), },
],
}),
},
];
let analysis = analyzer.analyze_multiple_typedefs(&typedefs);
assert!(!analysis.layout_violations.is_empty());
let violation = analysis
.layout_violations
.iter()
.find(|v| v.violating_type == "InvalidRegularUnion")
.expect("Should find violation for InvalidRegularUnion");
assert!(
violation
.reason
.contains("unions/enums cannot contain size-discriminated unions")
);
}
#[test]
fn test_size_discriminated_union_via_typeref_propagation() {
let mut analyzer = DependencyAnalyzer::new();
let typedefs = vec![
TypeDef {
name: "TokenAccountUnion".to_string(),
format: None,
kind: create_size_discriminated_union(
"TokenAccountUnion",
vec![
("token_account", 165, create_u32_primitive()),
("token_mint", 82, create_u64_primitive()),
],
),
},
TypeDef {
name: "WrapperType".to_string(),
format: None,
kind: create_type_ref("TokenAccountUnion"),
},
TypeDef {
name: "InvalidContainer".to_string(),
format: None,
kind: TypeKind::Struct(StructType {
container_attributes: Default::default(),
fields: vec![
StructField {
name: "wrapper".to_string(),
format: None,
field_type: create_type_ref("WrapperType"), },
StructField {
name: "other_var".to_string(),
format: None,
field_type: TypeKind::Array(ArrayType {
container_attributes: Default::default(),
size: create_field_ref_expr(vec!["field"]), element_type: Box::new(create_u8_primitive()),
jagged: false,
}),
},
],
}),
},
];
let analysis = analyzer.analyze_multiple_typedefs(&typedefs);
assert!(!analysis.layout_violations.is_empty());
let violation = analysis
.layout_violations
.iter()
.find(|v| v.violating_type == "InvalidContainer")
.expect("Should find violation for InvalidContainer");
assert!(violation.reason.contains("not the sole factor affecting"));
}
#[test]
fn test_size_discriminated_union_duplicate_variant_names() {
let mut analyzer = DependencyAnalyzer::new();
let typedefs = vec![TypeDef {
name: "BadNamesUnion".to_string(),
format: None,
kind: TypeKind::SizeDiscriminatedUnion(SizeDiscriminatedUnionType {
container_attributes: Default::default(),
variants: vec![
SizeDiscriminatedVariant {
name: "duplicate_name".to_string(),
expected_size: 100,
variant_type: create_u32_primitive(),
},
SizeDiscriminatedVariant {
name: "duplicate_name".to_string(), expected_size: 200,
variant_type: create_u64_primitive(),
},
],
}),
}];
let analysis = analyzer.analyze_multiple_typedefs(&typedefs);
assert!(!analysis.validation_errors.is_empty());
let error = analysis
.validation_errors
.iter()
.find(|e| e.error_type == "DuplicateVariantName")
.expect("Should find duplicate variant name error");
assert_eq!(error.violating_type, "BadNamesUnion");
assert_eq!(error.duplicate_name, "duplicate_name");
}
}
#[cfg(test)]
mod resolved_tests {
use super::*;
use crate::abi::resolved::*;
#[test]
fn test_constant_status_analysis() {
let resolver = TypeResolver::new();
let const_expr = ExprKind::Literal(LiteralExpr::U32(42));
let status = resolver
.analyze_expression_constantness(&const_expr, None)
.unwrap();
assert_eq!(status, ConstantStatus::Constant);
let field_expr = ExprKind::FieldRef(FieldRefExpr {
path: vec!["field".to_string()],
});
let status = resolver
.analyze_expression_constantness(&field_expr, None)
.unwrap();
assert!(matches!(status, ConstantStatus::NonConstant(_)));
let complex_expr = ExprKind::Add(AddExpr {
left: Box::new(const_expr),
right: Box::new(field_expr),
});
let status = resolver
.analyze_expression_constantness(&complex_expr, None)
.unwrap();
assert!(matches!(status, ConstantStatus::NonConstant(_)));
}
#[test]
fn test_primitive_type_resolution() {
let mut resolver = TypeResolver::new();
let typedef = TypeDef {
name: "MyU32".to_string(),
format: None,
kind: TypeKind::Primitive(PrimitiveType::Integral(IntegralType::U32)),
};
resolver.add_typedef(typedef);
resolver.resolve_all().unwrap();
let resolved = resolver.get_type_info("MyU32").unwrap();
assert_eq!(resolved.size, Size::Const(4));
assert_eq!(resolved.alignment, 4);
assert!(matches!(resolved.kind, ResolvedTypeKind::Primitive { .. }));
}
#[test]
fn test_struct_type_resolution() {
let mut resolver = TypeResolver::new();
let typedefs = vec![TypeDef {
name: "SimpleStruct".to_string(),
format: None,
kind: TypeKind::Struct(StructType {
container_attributes: Default::default(),
fields: vec![
StructField {
name: "field1".to_string(),
format: None,
field_type: TypeKind::Primitive(PrimitiveType::Integral(IntegralType::U8)),
},
StructField {
name: "field2".to_string(),
format: None,
field_type: TypeKind::Primitive(PrimitiveType::Integral(IntegralType::U32)),
},
],
}),
}];
for typedef in typedefs {
resolver.add_typedef(typedef);
}
resolver.resolve_all().unwrap();
let resolved = resolver.get_type_info("SimpleStruct").unwrap();
assert_eq!(resolved.size, Size::Const(8)); assert_eq!(resolved.alignment, 4);
if let ResolvedTypeKind::Struct { fields, .. } = &resolved.kind {
assert_eq!(fields.len(), 2);
assert_eq!(fields[0].offset, Some(0));
assert_eq!(fields[1].offset, Some(4)); } else {
panic!("Expected struct type");
}
}
#[test]
fn test_packed_struct_resolution() {
let mut resolver = TypeResolver::new();
let typedef = TypeDef {
name: "PackedStruct".to_string(),
format: None,
kind: TypeKind::Struct(StructType {
container_attributes: ContainerAttributes {
packed: true,
aligned: 0,
comment: None,
},
fields: vec![
StructField {
name: "field1".to_string(),
format: None,
field_type: TypeKind::Primitive(PrimitiveType::Integral(IntegralType::U8)),
},
StructField {
name: "field2".to_string(),
format: None,
field_type: TypeKind::Primitive(PrimitiveType::Integral(IntegralType::U32)),
},
],
}),
};
resolver.add_typedef(typedef);
resolver.resolve_all().unwrap();
let resolved = resolver.get_type_info("PackedStruct").unwrap();
assert_eq!(resolved.size, Size::Const(5));
if let ResolvedTypeKind::Struct { fields, packed, .. } = &resolved.kind {
assert!(packed);
assert_eq!(fields[0].offset, Some(0));
assert_eq!(fields[1].offset, Some(1)); } else {
panic!("Expected struct type");
}
}
#[test]
fn test_array_with_constant_size() {
let mut resolver = TypeResolver::new();
let typedef = TypeDef {
name: "ConstantArray".to_string(),
format: None,
kind: TypeKind::Array(ArrayType {
container_attributes: Default::default(),
size: ExprKind::Literal(LiteralExpr::U64(10)),
element_type: Box::new(TypeKind::Primitive(PrimitiveType::Integral(
IntegralType::U8,
))),
jagged: false,
}),
};
resolver.add_typedef(typedef);
resolver.resolve_all().unwrap();
let resolved = resolver.get_type_info("ConstantArray").unwrap();
assert_eq!(resolved.size, Size::Const(10));
assert_eq!(resolved.alignment, 1);
if let ResolvedTypeKind::Array {
size_constant_status,
..
} = &resolved.kind
{
assert_eq!(*size_constant_status, ConstantStatus::Constant);
} else {
panic!("Expected array type");
}
}
#[test]
fn test_array_with_field_reference() {
let mut resolver = TypeResolver::new();
let typedef = TypeDef {
name: "DynamicArray".to_string(),
format: None,
kind: TypeKind::Array(ArrayType {
container_attributes: Default::default(),
size: ExprKind::FieldRef(FieldRefExpr {
path: vec!["some_field".to_string()],
}),
element_type: Box::new(TypeKind::Primitive(PrimitiveType::Integral(
IntegralType::U8,
))),
jagged: false,
}),
};
resolver.add_typedef(typedef);
resolver.resolve_all().unwrap();
let resolved = resolver.get_type_info("DynamicArray").unwrap();
assert!(matches!(resolved.size, Size::Variable(_)));
if let ResolvedTypeKind::Array {
size_constant_status,
..
} = &resolved.kind
{
assert!(matches!(
size_constant_status,
ConstantStatus::NonConstant(_)
));
} else {
panic!("Expected array type");
}
}
#[test]
fn test_union_type_resolution() {
let mut resolver = TypeResolver::new();
let typedef = TypeDef {
name: "SimpleUnion".to_string(),
format: None,
kind: TypeKind::Union(UnionType {
container_attributes: Default::default(),
variants: vec![
UnionVariant {
name: "variant1".to_string(),
variant_type: TypeKind::Primitive(PrimitiveType::Integral(
IntegralType::U8,
)),
},
UnionVariant {
name: "variant2".to_string(),
variant_type: TypeKind::Primitive(PrimitiveType::Integral(
IntegralType::U32,
)),
},
],
}),
};
resolver.add_typedef(typedef);
resolver.resolve_all().unwrap();
let resolved = resolver.get_type_info("SimpleUnion").unwrap();
assert_eq!(resolved.size, Size::Const(4)); assert_eq!(resolved.alignment, 4);
if let ResolvedTypeKind::Union { variants } = &resolved.kind {
assert_eq!(variants.len(), 2);
assert_eq!(variants[0].offset, Some(0));
assert_eq!(variants[1].offset, Some(0));
} else {
panic!("Expected union type");
}
}
#[test]
fn test_enum_type_resolution() {
let mut resolver = TypeResolver::new();
let typedef = TypeDef {
name: "SimpleEnum".to_string(),
format: None,
kind: TypeKind::Enum(EnumType {
container_attributes: Default::default(),
tag_ref: ExprKind::Literal(LiteralExpr::U64(0)),
variants: vec![
EnumVariant {
name: "Variant1".to_string(),
tag_value: 1,
variant_type: TypeKind::Primitive(PrimitiveType::Integral(
IntegralType::U32,
)),
},
EnumVariant {
name: "Variant2".to_string(),
tag_value: 2,
variant_type: TypeKind::Primitive(PrimitiveType::Integral(
IntegralType::U8,
)),
},
],
}),
};
resolver.add_typedef(typedef);
resolver.resolve_all().unwrap();
let resolved = resolver.get_type_info("SimpleEnum").unwrap();
assert!(
matches!(resolved.size, Size::Variable(_)),
"Enums with variant-dependent payloads should report variable size"
);
assert_eq!(resolved.alignment, 4);
if let ResolvedTypeKind::Enum {
tag_constant_status,
variants,
..
} = &resolved.kind
{
assert_eq!(*tag_constant_status, ConstantStatus::Constant);
assert_eq!(variants.len(), 2);
assert_eq!(variants[0].tag_value, 1);
assert_eq!(variants[1].tag_value, 2);
} else {
panic!("Expected enum type");
}
}
#[test]
fn test_circular_type_reference_detection() {
let mut resolver = TypeResolver::new();
let typedefs = vec![
TypeDef {
name: "TypeA".to_string(),
format: None,
kind: TypeKind::Struct(StructType {
container_attributes: Default::default(),
fields: vec![StructField {
name: "field_b".to_string(),
format: None,
field_type: TypeKind::TypeRef(TypeRefType {
name: "TypeB".to_string(),
package: None,
comment: None,
}),
}],
}),
},
TypeDef {
name: "TypeB".to_string(),
format: None,
kind: TypeKind::TypeRef(TypeRefType {
name: "TypeA".to_string(),
package: None,
comment: None,
}),
},
];
for typedef in typedefs {
resolver.add_typedef(typedef);
}
let result = resolver.resolve_all();
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
ResolutionError::CircularDependency(_)
));
}
#[test]
fn test_field_reference_collection() {
let resolver = TypeResolver::new();
let simple_ref = ExprKind::FieldRef(FieldRefExpr {
path: vec!["field".to_string()],
});
let status = resolver
.analyze_expression_constantness(&simple_ref, None)
.unwrap();
if let ConstantStatus::NonConstant(ref refs) = status {
assert_eq!(refs.len(), 1);
assert!(refs.contains_key("field"));
} else {
panic!("Expected non-constant status for field reference");
}
let complex_expr = ExprKind::Add(AddExpr {
left: Box::new(ExprKind::FieldRef(FieldRefExpr {
path: vec!["field1".to_string()],
})),
right: Box::new(ExprKind::Mul(MulExpr {
left: Box::new(ExprKind::FieldRef(FieldRefExpr {
path: vec!["field2".to_string()],
})),
right: Box::new(ExprKind::Literal(LiteralExpr::U32(2))),
})),
});
let status = resolver
.analyze_expression_constantness(&complex_expr, None)
.unwrap();
if let ConstantStatus::NonConstant(ref refs) = status {
assert_eq!(refs.len(), 2);
assert!(refs.contains_key("field1"));
assert!(refs.contains_key("field2"));
} else {
panic!("Expected non-constant status for complex expression");
}
}
#[test]
fn test_non_constant_dependency_tracking() {
let mut resolver = TypeResolver::new();
let typedef = TypeDef {
name: "DependentArray".to_string(),
format: None,
kind: TypeKind::Array(ArrayType {
container_attributes: Default::default(),
size: ExprKind::Add(AddExpr {
left: Box::new(ExprKind::FieldRef(FieldRefExpr {
path: vec!["size_field".to_string()],
})),
right: Box::new(ExprKind::Literal(LiteralExpr::U32(10))),
}),
element_type: Box::new(TypeKind::Primitive(PrimitiveType::Integral(
IntegralType::U8,
))),
jagged: false,
}),
};
resolver.add_typedef(typedef);
resolver.resolve_all().unwrap();
let resolved = resolver
.get_type_info("DependentArray")
.expect("Type should be resolved");
assert!(matches!(resolved.size, Size::Variable(_)));
if let ResolvedTypeKind::Array {
size_constant_status,
..
} = &resolved.kind
{
if let ConstantStatus::NonConstant(refs) = size_constant_status {
assert_eq!(refs.len(), 1);
assert!(refs.contains_key("size_field"));
} else {
panic!("Expected non-constant size status for DependentArray");
}
} else {
panic!("DependentArray should resolve to an array type");
}
}
#[test]
fn test_shift_operations_constant_evaluation() {
let mut resolver = TypeResolver::new();
let typedef = TypeDef {
name: "ShiftArray".to_string(),
format: None,
kind: TypeKind::Array(ArrayType {
container_attributes: Default::default(),
size: ExprKind::RightShift(RightShiftExpr {
left: Box::new(ExprKind::LeftShift(LeftShiftExpr {
left: Box::new(ExprKind::Literal(LiteralExpr::U64(4))),
right: Box::new(ExprKind::Literal(LiteralExpr::U64(2))),
})),
right: Box::new(ExprKind::Literal(LiteralExpr::U64(1))),
}),
element_type: Box::new(TypeKind::Primitive(PrimitiveType::Integral(
IntegralType::U8,
))),
jagged: false,
}),
};
resolver.add_typedef(typedef);
resolver.resolve_all().unwrap();
let resolved = resolver.get_type_info("ShiftArray").unwrap();
assert_eq!(resolved.size, Size::Const(8)); assert_eq!(resolved.alignment, 1);
if let ResolvedTypeKind::Array {
size_constant_status,
..
} = &resolved.kind
{
assert_eq!(*size_constant_status, ConstantStatus::Constant);
} else {
panic!("Expected array type");
}
}
#[test]
fn test_popcount_operations_constant_evaluation() {
let mut resolver = TypeResolver::new();
let typedef = TypeDef {
name: "PopcountArray".to_string(),
format: None,
kind: TypeKind::Array(ArrayType {
container_attributes: Default::default(),
size: ExprKind::Popcount(PopcountExpr {
operand: Box::new(ExprKind::Literal(LiteralExpr::U64(15))), }),
element_type: Box::new(TypeKind::Primitive(PrimitiveType::Integral(
IntegralType::U8,
))),
jagged: false,
}),
};
resolver.add_typedef(typedef);
resolver.resolve_all().unwrap();
let resolved = resolver.get_type_info("PopcountArray").unwrap();
assert_eq!(resolved.size, Size::Const(4)); assert_eq!(resolved.alignment, 1);
if let ResolvedTypeKind::Array {
size_constant_status,
..
} = &resolved.kind
{
assert_eq!(*size_constant_status, ConstantStatus::Constant);
} else {
panic!("Expected array type");
}
}
#[test]
fn test_size_discriminated_union_type_resolution() {
let mut resolver = TypeResolver::new();
let typedef = TypeDef {
name: "TokenAccountUnion".to_string(),
format: None,
kind: TypeKind::SizeDiscriminatedUnion(SizeDiscriminatedUnionType {
container_attributes: Default::default(),
variants: vec![
SizeDiscriminatedVariant {
name: "token_account".to_string(),
expected_size: 4,
variant_type: TypeKind::Primitive(PrimitiveType::Integral(
IntegralType::U32,
)),
},
SizeDiscriminatedVariant {
name: "token_mint".to_string(),
expected_size: 8,
variant_type: TypeKind::Primitive(PrimitiveType::Integral(
IntegralType::U64,
)),
},
],
}),
};
resolver.add_typedef(typedef);
resolver.resolve_all().unwrap();
let resolved = resolver.get_type_info("TokenAccountUnion").unwrap();
assert!(matches!(resolved.size, Size::Variable(_))); assert_eq!(resolved.alignment, 8);
if let ResolvedTypeKind::SizeDiscriminatedUnion { variants } = &resolved.kind {
assert_eq!(variants.len(), 2);
assert_eq!(variants[0].name, "token_account");
assert_eq!(variants[0].expected_size, 4);
assert_eq!(variants[1].name, "token_mint");
assert_eq!(variants[1].expected_size, 8);
} else {
panic!("Expected size-discriminated union type");
}
}
}