use ifc_schema::{Schema, TypeKind};
use super::scalar::{primitive_of, Primitive};
const MAX_ALIAS_HOPS: usize = 16;
#[must_use]
pub fn wrapper_fits(schema: &Schema, wrapper: &str, declared: &str) -> Option<bool> {
if schema.type_def(declared).is_some() || schema.entity(declared).is_some() {
return Some(aliases_to(schema, wrapper, declared));
}
let expected = Primitive::from_resolved(declared)?;
let Some(actual) = primitive_of(schema, wrapper).filter(|_| is_declared(schema, wrapper))
else {
return Some(false);
};
Some(actual == expected || (expected == Primitive::Real && actual == Primitive::Integer))
}
fn aliases_to(schema: &Schema, wrapper: &str, declared: &str) -> bool {
let mut current = wrapper.trim().to_string();
for _ in 0..MAX_ALIAS_HOPS {
if current.eq_ignore_ascii_case(declared) {
return true;
}
let Some(TypeKind::Defined(target)) =
schema.type_def(¤t).map(|definition| &definition.kind)
else {
return false;
};
current = target.trim().to_string();
}
false
}
fn is_declared(schema: &Schema, name: &str) -> bool {
schema.type_def(name).is_some()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::type_check::{check_value, Mismatch};
use ifc_model::Value;
const ALIASED_SELECT: &str = "\
SCHEMA ALIASED_SELECT;
TYPE Load = SELECT (Load_Choice, Other_Load); END_TYPE;
TYPE Load_Choice = SELECT (Rated_Load, Computed_Load); END_TYPE;
TYPE Other_Load = SELECT (Guessed_Load); END_TYPE;
TYPE Rated_Load = REAL; END_TYPE;
TYPE Guessed_Load = REAL; END_TYPE;
TYPE Computed_Load = Number_Or_Flag; END_TYPE;
TYPE Number_Or_Flag = SELECT (Plain_Number, Flag); END_TYPE;
TYPE Plain_Number = REAL; END_TYPE;
TYPE Flag = ENUMERATION OF (unbounded, unknown); END_TYPE;
ENTITY Beam;
span : Number_Or_Flag;
load : Load;
computed : Computed_Load;
END_ENTITY;
END_SCHEMA;
";
fn wrap(type_name: &str, value: Value) -> Value {
Value::Typed {
type_name: type_name.into(),
value: Box::new(value),
}
}
#[test]
fn nested_and_aliased_selects_are_judged_as_part_21_writes_them() {
let schema = Schema::from_express(ALIASED_SELECT);
let number = || wrap("PLAIN_NUMBER", Value::Real(7.5));
assert_eq!(check_value(&schema, "Number_Or_Flag", &number()), None);
let flag = wrap("FLAG", Value::Enum("UNKNOWN".into()));
assert_eq!(check_value(&schema, "Number_Or_Flag", &flag), None);
let rated = wrap("RATED_LOAD", Value::Real(3.0));
assert_eq!(check_value(&schema, "Load", &rated), None);
let computed = wrap("COMPUTED_LOAD", number());
assert_eq!(check_value(&schema, "Load", &computed), None);
assert_eq!(check_value(&schema, "Computed_Load", &number()), None);
assert!(matches!(
check_value(&schema, "Computed_Load", &Value::Real(1.0)),
Some(Mismatch::UntypedSelectValue { .. })
));
assert!(matches!(
check_value(&schema, "Load", &wrap("COMPUTED_LOAD", Value::Real(1.5))),
Some(Mismatch::UntypedSelectValue { .. })
));
assert!(matches!(
check_value(&schema, "Load", &number()),
Some(Mismatch::SelectMember { .. })
));
assert_eq!(check_value(&schema, "Rated_Load", &Value::Real(1.0)), None);
assert!(matches!(
check_value(&schema, "Rated_Load", &wrap("RATED_LOAD", Value::Real(1.0))),
Some(Mismatch::TypedOutsideSelect { .. })
));
}
#[test]
fn the_declared_type_and_its_specialisations_fit() {
let schema = ifc_schema::ifc4();
assert_eq!(
wrapper_fits(schema, "IFCAREAMEASURE", "IfcAreaMeasure"),
Some(true)
);
assert_eq!(
wrapper_fits(schema, "IFCPOSITIVELENGTHMEASURE", "IfcLengthMeasure"),
Some(true)
);
assert_eq!(
wrapper_fits(schema, "IFCWALLTYPEENUM", "IfcWallTypeEnum"),
Some(true)
);
}
#[test]
fn another_type_does_not_fit() {
let schema = ifc_schema::ifc4();
assert_eq!(
wrapper_fits(schema, "IFCLABEL", "IfcAreaMeasure"),
Some(false)
);
assert_eq!(
wrapper_fits(schema, "IFCLENGTHMEASURE", "IfcAreaMeasure"),
Some(false)
);
assert_eq!(
wrapper_fits(schema, "IFCLENGTHMEASURE", "IfcPositiveLengthMeasure"),
Some(false)
);
assert_eq!(
wrapper_fits(schema, "IFCNOSUCHMEASURE", "IfcAreaMeasure"),
Some(false)
);
}
#[test]
fn a_primitive_slot_is_judged_by_primitive() {
let schema = ifc_schema::ifc4();
assert_eq!(wrapper_fits(schema, "IFCINTEGER", "INTEGER"), Some(true));
assert_eq!(wrapper_fits(schema, "IFCINTEGER", "REAL"), Some(true));
assert_eq!(wrapper_fits(schema, "IFCLABEL", "REAL"), Some(false));
assert_eq!(wrapper_fits(schema, "IFCNOSUCHTYPE", "REAL"), Some(false));
assert_eq!(wrapper_fits(schema, "IFCLENGTHMEASURE", "LIST"), None);
}
}