use super::*;
use crate::intern::TypeInterner;
#[test]
fn test_intersection_string_number_is_never() {
let interner = TypeInterner::new();
let result = interner.intersection2(TypeId::STRING, TypeId::NUMBER);
assert_eq!(result, TypeId::NEVER, "string & number should be never");
}
#[test]
fn test_intersection_string_boolean_is_never() {
let interner = TypeInterner::new();
let result = interner.intersection2(TypeId::STRING, TypeId::BOOLEAN);
assert_eq!(result, TypeId::NEVER, "string & boolean should be never");
}
#[test]
fn test_intersection_number_boolean_is_never() {
let interner = TypeInterner::new();
let result = interner.intersection2(TypeId::NUMBER, TypeId::BOOLEAN);
assert_eq!(result, TypeId::NEVER, "number & boolean should be never");
}
#[test]
fn test_intersection_string_bigint_is_never() {
let interner = TypeInterner::new();
let result = interner.intersection2(TypeId::STRING, TypeId::BIGINT);
assert_eq!(result, TypeId::NEVER, "string & bigint should be never");
}
#[test]
fn test_intersection_symbol_string_is_never() {
let interner = TypeInterner::new();
let result = interner.intersection2(TypeId::SYMBOL, TypeId::STRING);
assert_eq!(result, TypeId::NEVER, "symbol & string should be never");
}
#[test]
fn test_intersection_null_undefined_is_never() {
let interner = TypeInterner::new();
let result = interner.intersection2(TypeId::NULL, TypeId::UNDEFINED);
assert_eq!(result, TypeId::NEVER, "null & undefined should be never");
}
#[test]
fn test_intersection_literal_of_different_types_is_never() {
let interner = TypeInterner::new();
let hello = interner.literal_string("hello");
let one = interner.literal_number(1.0);
let result = interner.intersection2(hello, one);
assert_eq!(result, TypeId::NEVER, "\"hello\" & 1 should be never");
}
#[test]
fn test_intersection_same_primitive_is_itself() {
let interner = TypeInterner::new();
let result = interner.intersection2(TypeId::STRING, TypeId::STRING);
assert_eq!(result, TypeId::STRING, "string & string should be string");
}
#[test]
fn test_intersection_different_string_literals_is_never() {
let interner = TypeInterner::new();
let hello = interner.literal_string("hello");
let world = interner.literal_string("world");
let result = interner.intersection2(hello, world);
assert_eq!(
result,
TypeId::NEVER,
"\"hello\" & \"world\" should be never"
);
}
#[test]
fn test_intersection_different_number_literals_is_never() {
let interner = TypeInterner::new();
let one = interner.literal_number(1.0);
let two = interner.literal_number(2.0);
let result = interner.intersection2(one, two);
assert_eq!(result, TypeId::NEVER, "1 & 2 should be never");
}
#[test]
fn test_intersection_literal_with_primitive_is_literal() {
let interner = TypeInterner::new();
let hello = interner.literal_string("hello");
let result = interner.intersection2(hello, TypeId::STRING);
assert_eq!(result, hello, "\"hello\" & string should be \"hello\"");
}
#[test]
fn test_intersection_object_merge_properties() {
let interner = TypeInterner::new();
let obj_a = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
TypeId::STRING,
)]);
let obj_b = interner.object(vec![PropertyInfo::new(
interner.intern_string("b"),
TypeId::NUMBER,
)]);
let result = interner.intersection2(obj_a, obj_b);
if let Some(TypeData::Object(shape_id)) = interner.lookup(result) {
let shape = interner.object_shape(shape_id);
assert_eq!(shape.properties.len(), 2, "Should have both properties");
let prop_a = shape
.properties
.iter()
.find(|p| p.name == interner.intern_string("a"));
assert!(prop_a.is_some(), "Should have property 'a'");
assert_eq!(prop_a.unwrap().type_id, TypeId::STRING);
let prop_b = shape
.properties
.iter()
.find(|p| p.name == interner.intern_string("b"));
assert!(prop_b.is_some(), "Should have property 'b'");
assert_eq!(prop_b.unwrap().type_id, TypeId::NUMBER);
} else {
panic!("Expected object type");
}
}
#[test]
fn test_intersection_object_same_property_intersect_types() {
let interner = TypeInterner::new();
let obj_a = interner.object(vec![PropertyInfo::new(
interner.intern_string("x"),
TypeId::STRING,
)]);
let obj_b = interner.object(vec![PropertyInfo::new(
interner.intern_string("x"),
TypeId::NUMBER,
)]);
let result = interner.intersection2(obj_a, obj_b);
if let Some(TypeData::Object(shape_id)) = interner.lookup(result) {
let shape = interner.object_shape(shape_id);
let prop_x = shape
.properties
.iter()
.find(|p| p.name == interner.intern_string("x"));
assert!(prop_x.is_some());
assert_eq!(
prop_x.unwrap().type_id,
TypeId::NEVER,
"Property type should be never"
);
} else {
panic!("Expected object type with never property");
}
}
#[test]
fn test_intersection_required_wins_over_optional() {
let interner = TypeInterner::new();
let obj_optional = interner.object(vec![PropertyInfo::opt(
interner.intern_string("x"),
TypeId::STRING,
)]);
let obj_required = interner.object(vec![PropertyInfo::new(
interner.intern_string("x"),
TypeId::STRING,
)]);
let result = interner.intersection2(obj_optional, obj_required);
if let Some(TypeData::Object(shape_id)) = interner.lookup(result) {
let shape = interner.object_shape(shape_id);
let prop_x = shape
.properties
.iter()
.find(|p| p.name == interner.intern_string("x"));
assert!(prop_x.is_some());
assert!(!prop_x.unwrap().optional, "Property should be required");
} else {
panic!("Expected object type");
}
}
#[test]
fn test_intersection_readonly_is_cumulative() {
let interner = TypeInterner::new();
let obj_readonly = interner.object(vec![PropertyInfo::readonly(
interner.intern_string("x"),
TypeId::STRING,
)]);
let obj_mutable = interner.object(vec![PropertyInfo::new(
interner.intern_string("x"),
TypeId::STRING,
)]);
let result = interner.intersection2(obj_readonly, obj_mutable);
if let Some(TypeData::Object(shape_id)) = interner.lookup(result) {
let shape = interner.object_shape(shape_id);
let prop_x = shape
.properties
.iter()
.find(|p| p.name == interner.intern_string("x"));
assert!(prop_x.is_some());
assert!(prop_x.unwrap().readonly, "Property should be readonly");
} else {
panic!("Expected object type");
}
}
#[test]
fn test_intersection_both_optional_stays_optional() {
let interner = TypeInterner::new();
let obj_a = interner.object(vec![PropertyInfo::opt(
interner.intern_string("x"),
TypeId::STRING,
)]);
let obj_b = interner.object(vec![PropertyInfo::opt(
interner.intern_string("x"),
TypeId::STRING,
)]);
let result = interner.intersection2(obj_a, obj_b);
if let Some(TypeData::Object(shape_id)) = interner.lookup(result) {
let shape = interner.object_shape(shape_id);
let prop_x = shape
.properties
.iter()
.find(|p| p.name == interner.intern_string("x"));
assert!(prop_x.is_some());
assert!(prop_x.unwrap().optional, "Property should be optional");
} else {
panic!("Expected object type");
}
}
#[test]
fn test_intersection_function_overloads() {
let interner = TypeInterner::new();
let func1 = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: TypeId::STRING,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::NUMBER,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let func2 = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: TypeId::NUMBER,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::STRING,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let result = interner.intersection2(func1, func2);
if let Some(TypeData::Callable(shape_id)) = interner.lookup(result) {
let shape = interner.callable_shape(shape_id);
assert_eq!(
shape.call_signatures.len(),
2,
"Should have both call signatures"
);
} else {
panic!("Expected callable type with overloaded signatures");
}
}
#[test]
fn test_union_literal_absorbed_into_primitive() {
let interner = TypeInterner::new();
let hello = interner.literal_string("hello");
let world = interner.literal_string("world");
let result = interner.union3(hello, world, TypeId::STRING);
assert_eq!(
result,
TypeId::STRING,
"Literals should be absorbed into primitive"
);
}
#[test]
fn test_union_number_literals_absorbed_into_number() {
let interner = TypeInterner::new();
let one = interner.literal_number(1.0);
let two = interner.literal_number(2.0);
let three = interner.literal_number(3.0);
let result = interner.union(vec![one, two, three, TypeId::NUMBER]);
assert_eq!(
result,
TypeId::NUMBER,
"Number literals should be absorbed into number"
);
}
#[test]
fn test_union_boolean_literals_absorbed_into_boolean() {
let interner = TypeInterner::new();
let result = interner.union3(TypeId::BOOLEAN_TRUE, TypeId::BOOLEAN_FALSE, TypeId::BOOLEAN);
assert_eq!(
result,
TypeId::BOOLEAN,
"Boolean literals should be absorbed into boolean"
);
}
#[test]
fn test_union_true_false_reduces_to_boolean() {
let interner = TypeInterner::new();
let t = interner.literal_boolean(true);
let f = interner.literal_boolean(false);
assert_eq!(
t,
TypeId::BOOLEAN_TRUE,
"literal_boolean(true) should use intrinsic ID"
);
assert_eq!(
f,
TypeId::BOOLEAN_FALSE,
"literal_boolean(false) should use intrinsic ID"
);
let result = interner.union2(t, f);
assert_eq!(
result,
TypeId::BOOLEAN,
"true | false should reduce to boolean"
);
}
#[test]
fn test_union_bigint_literals_absorbed_into_bigint() {
let interner = TypeInterner::new();
let bigint1 = interner.literal_bigint("1");
let bigint2 = interner.literal_bigint("2");
let result = interner.union3(bigint1, bigint2, TypeId::BIGINT);
assert_eq!(
result,
TypeId::BIGINT,
"Bigint literals should be absorbed into bigint"
);
}
#[test]
fn test_union_literals_without_primitive_stay_as_union() {
let interner = TypeInterner::new();
let hello = interner.literal_string("hello");
let world = interner.literal_string("world");
let result = interner.union2(hello, world);
if let Some(TypeData::Union(list_id)) = interner.lookup(result) {
let members = interner.type_list(list_id);
assert_eq!(members.len(), 2, "Should have both literals");
} else {
panic!("Expected union type");
}
}
#[test]
fn test_union_any_dominates() {
let interner = TypeInterner::new();
let result = interner.union2(TypeId::ANY, TypeId::STRING);
assert_eq!(result, TypeId::ANY, "any should dominate union");
let result = interner.union2(TypeId::STRING, TypeId::ANY);
assert_eq!(result, TypeId::ANY, "any should dominate union");
}
#[test]
fn test_union_unknown_dominates() {
let interner = TypeInterner::new();
let result = interner.union2(TypeId::UNKNOWN, TypeId::STRING);
assert_eq!(result, TypeId::UNKNOWN, "unknown should dominate union");
}
#[test]
fn test_union_any_dominates_unknown() {
let interner = TypeInterner::new();
let result = interner.union2(TypeId::ANY, TypeId::UNKNOWN);
assert_eq!(result, TypeId::ANY, "any should dominate unknown");
}
#[test]
fn test_union_remove_never() {
let interner = TypeInterner::new();
let result = interner.union2(TypeId::STRING, TypeId::NEVER);
assert_eq!(result, TypeId::STRING, "never should be removed from union");
}
#[test]
fn test_union_multiple_never_removed() {
let interner = TypeInterner::new();
let result = interner.union(vec![
TypeId::STRING,
TypeId::NEVER,
TypeId::NUMBER,
TypeId::NEVER,
]);
if let Some(TypeData::Union(list_id)) = interner.lookup(result) {
let members = interner.type_list(list_id);
assert_eq!(
members.len(),
2,
"Should have 2 members after removing never"
);
} else {
panic!("Expected union type");
}
}
#[test]
fn test_union_only_never_is_never() {
let interner = TypeInterner::new();
let result = interner.union2(TypeId::NEVER, TypeId::NEVER);
assert_eq!(result, TypeId::NEVER, "Union of only never should be never");
}
#[test]
fn test_union_deduplicates() {
let interner = TypeInterner::new();
let result = interner.union(vec![TypeId::STRING, TypeId::STRING, TypeId::NUMBER]);
if let Some(TypeData::Union(list_id)) = interner.lookup(result) {
let members = interner.type_list(list_id);
assert_eq!(members.len(), 2, "Should deduplicate string");
} else {
panic!("Expected union type");
}
}
#[test]
fn test_union_sorts_consistently() {
let interner = TypeInterner::new();
let result1 = interner.union(vec![TypeId::NUMBER, TypeId::STRING]);
let result2 = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert_eq!(result1, result2, "Unions should be sorted consistently");
}
#[test]
fn test_intersection_remove_unknown() {
let interner = TypeInterner::new();
let result = interner.intersection2(TypeId::STRING, TypeId::UNKNOWN);
assert_eq!(
result,
TypeId::STRING,
"unknown should be removed from intersection"
);
}
#[test]
fn test_intersection_any_is_identity() {
let interner = TypeInterner::new();
let result = interner.intersection2(TypeId::STRING, TypeId::ANY);
assert_eq!(
result,
TypeId::ANY,
"any in intersection should result in any"
);
}
#[test]
fn test_intersection_flattens_nested() {
let interner = TypeInterner::new();
let obj_a = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
TypeId::STRING,
)]);
let obj_b = interner.object(vec![PropertyInfo::new(
interner.intern_string("b"),
TypeId::NUMBER,
)]);
let inner = interner.intersection2(obj_a, obj_b);
let outer = interner.intersection2(inner, obj_a);
assert_eq!(inner, outer, "Nested intersections should be flattened");
}
#[test]
fn test_distributive_conditional_over_union() {
let interner = TypeInterner::new();
let union = interner.union2(TypeId::STRING, TypeId::NUMBER);
let conditional = ConditionalType {
check_type: union,
extends_type: TypeId::STRING,
true_type: TypeId::BOOLEAN_TRUE,
false_type: TypeId::BOOLEAN_FALSE,
is_distributive: true,
};
let result = interner.conditional(conditional);
if let Some(TypeData::Conditional(cond_id)) = interner.lookup(result) {
let cond = interner.conditional_type(cond_id);
assert!(cond.is_distributive, "Should be marked as distributive");
assert_eq!(cond.check_type, union);
assert_eq!(cond.extends_type, TypeId::STRING);
} else {
panic!("Expected conditional type");
}
}
#[test]
fn test_intersection_empty_is_unknown() {
let interner = TypeInterner::new();
let result = interner.intersection(vec![]);
assert_eq!(
result,
TypeId::UNKNOWN,
"Empty intersection should be unknown"
);
}
#[test]
fn test_intersection_single_member_is_itself() {
let interner = TypeInterner::new();
let result = interner.intersection(vec![TypeId::STRING]);
assert_eq!(
result,
TypeId::STRING,
"Single-member intersection should be that member"
);
}
#[test]
fn test_union_empty_is_never() {
let interner = TypeInterner::new();
let result = interner.union(vec![]);
assert_eq!(result, TypeId::NEVER, "Empty union should be never");
}
#[test]
fn test_union_single_member_is_itself() {
let interner = TypeInterner::new();
let result = interner.union(vec![TypeId::STRING]);
assert_eq!(
result,
TypeId::STRING,
"Single-member union should be that member"
);
}