use super::*;
use crate::freshness::{is_fresh_object_type, widen_freshness};
use crate::intern::PROPERTY_MAP_THRESHOLD;
use tsz_binder::SymbolId;
#[test]
fn test_interner_intrinsics() {
let interner = TypeInterner::new();
assert!(interner.lookup(TypeId::STRING).is_some());
assert!(interner.lookup(TypeId::NUMBER).is_some());
assert!(interner.lookup(TypeId::ANY).is_some());
}
#[test]
fn test_interner_deduplication() {
let interner = TypeInterner::new();
let id1 = interner.literal_string("hello");
let id2 = interner.literal_string("hello");
let id3 = interner.literal_string("world");
assert_eq!(id1, id2);
assert_ne!(id1, id3);
}
#[test]
fn test_interner_keyof_and_index_access_constructors() {
let interner = TypeInterner::new();
let keys = interner.keyof(TypeId::STRING);
assert_eq!(interner.lookup(keys), Some(TypeData::KeyOf(TypeId::STRING)));
let string_array = interner.array(TypeId::STRING);
let indexed = interner.index_access(string_array, TypeId::NUMBER);
assert_eq!(
interner.lookup(indexed),
Some(TypeData::IndexAccess(string_array, TypeId::NUMBER))
);
}
#[test]
fn test_interner_lazy_and_type_param_constructors() {
let interner = TypeInterner::new();
let lazy = interner.lazy(DefId(42));
assert_eq!(interner.lookup(lazy), Some(TypeData::Lazy(DefId(42))));
let t = TypeParamInfo {
name: interner.intern_string("T"),
constraint: Some(TypeId::STRING),
default: Some(TypeId::NUMBER),
is_const: false,
};
let type_param = interner.type_param(t.clone());
assert_eq!(interner.lookup(type_param), Some(TypeData::TypeParameter(t)));
}
#[test]
fn test_interner_type_query_constructor() {
let interner = TypeInterner::new();
let query = interner.type_query(SymbolRef(7));
assert_eq!(
interner.lookup(query),
Some(TypeData::TypeQuery(SymbolRef(7)))
);
}
#[test]
fn test_interner_enum_constructor() {
let interner = TypeInterner::new();
let enum_ty = interner.enum_type(DefId(9), TypeId::NUMBER);
assert_eq!(
interner.lookup(enum_ty),
Some(TypeData::Enum(DefId(9), TypeId::NUMBER))
);
}
#[test]
fn test_interner_fresh_object_distinct_from_non_fresh() {
let interner = TypeInterner::new();
let prop = PropertyInfo::new(interner.intern_string("x"), TypeId::NUMBER);
let fresh = interner.object_fresh(vec![prop.clone()]);
let non_fresh = interner.object(vec![prop]);
assert_ne!(fresh, non_fresh);
assert!(is_fresh_object_type(&interner, fresh));
assert!(!is_fresh_object_type(&interner, non_fresh));
assert_eq!(widen_freshness(&interner, fresh), non_fresh);
}
#[test]
fn test_interner_bigint_literal() {
let interner = TypeInterner::new();
let id = interner.literal_bigint("123");
let key = interner
.lookup(id)
.expect("bigint literal should be interned");
match key {
TypeData::Literal(LiteralValue::BigInt(atom)) => {
assert_eq!(interner.resolve_atom(atom), "123");
}
_ => panic!("Expected bigint literal, got {:?}", key),
}
}
#[test]
fn test_interner_union_normalization() {
let interner = TypeInterner::new();
let single = interner.union(vec![TypeId::STRING]);
assert_eq!(single, TypeId::STRING);
let with_any = interner.union(vec![TypeId::STRING, TypeId::ANY]);
assert_eq!(with_any, TypeId::ANY);
let with_never = interner.union(vec![TypeId::STRING, TypeId::NEVER]);
assert_eq!(with_never, TypeId::STRING);
let empty = interner.union(vec![]);
assert_eq!(empty, TypeId::NEVER);
let with_error = interner.union(vec![TypeId::STRING, TypeId::ERROR]);
assert_eq!(with_error, TypeId::ERROR);
}
#[test]
fn test_interner_union_unknown_dominates() {
let interner = TypeInterner::new();
let with_unknown = interner.union(vec![TypeId::STRING, TypeId::UNKNOWN]);
assert_eq!(with_unknown, TypeId::UNKNOWN);
let only_unknown = interner.union(vec![TypeId::UNKNOWN]);
assert_eq!(only_unknown, TypeId::UNKNOWN);
}
#[test]
fn test_interner_union_any_beats_unknown() {
let interner = TypeInterner::new();
let any_and_unknown = interner.union(vec![TypeId::ANY, TypeId::UNKNOWN]);
assert_eq!(any_and_unknown, TypeId::ANY);
}
#[test]
fn test_interner_union_dedups_and_flattens() {
let interner = TypeInterner::new();
let nested = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let flattened = interner.union(vec![TypeId::STRING, nested, TypeId::STRING]);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert_eq!(flattened, expected);
}
#[test]
fn test_interner_intersection_normalization() {
let interner = TypeInterner::new();
let single = interner.intersection(vec![TypeId::STRING]);
assert_eq!(single, TypeId::STRING);
let with_never = interner.intersection(vec![TypeId::STRING, TypeId::NEVER]);
assert_eq!(with_never, TypeId::NEVER);
let empty = interner.intersection(vec![]);
assert_eq!(empty, TypeId::UNKNOWN);
let with_any = interner.intersection(vec![TypeId::STRING, TypeId::ANY]);
assert_eq!(with_any, TypeId::ANY);
let with_error = interner.intersection(vec![TypeId::STRING, TypeId::ERROR]);
assert_eq!(with_error, TypeId::ERROR);
}
#[test]
fn test_interner_intersection_unknown_identity() {
let interner = TypeInterner::new();
let with_unknown = interner.intersection(vec![TypeId::STRING, TypeId::UNKNOWN]);
assert_eq!(with_unknown, TypeId::STRING);
let only_unknown = interner.intersection(vec![TypeId::UNKNOWN]);
assert_eq!(only_unknown, TypeId::UNKNOWN);
}
#[test]
fn test_interner_intersection_any_over_unknown() {
let interner = TypeInterner::new();
let any_and_unknown = interner.intersection(vec![TypeId::ANY, TypeId::UNKNOWN]);
assert_eq!(any_and_unknown, TypeId::ANY);
}
#[test]
fn test_interner_intersection_flattens_and_dedups() {
let interner = TypeInterner::new();
let obj_a = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
TypeId::NUMBER,
)]);
let obj_b = interner.object(vec![PropertyInfo::new(
interner.intern_string("b"),
TypeId::STRING,
)]);
let inner = interner.intersection(vec![obj_a, obj_b]);
let outer = interner.intersection(vec![inner, obj_a]);
let dup = interner.intersection(vec![obj_b, obj_a, obj_a]);
assert_eq!(outer, inner);
assert_eq!(dup, inner);
}
#[test]
fn test_interner_intersection_disjoint_primitives() {
let interner = TypeInterner::new();
let disjoint = interner.intersection(vec![TypeId::STRING, TypeId::NUMBER]);
assert_eq!(disjoint, TypeId::NEVER);
let literal = interner.literal_string("a");
let disjoint_literal = interner.intersection(vec![literal, TypeId::BOOLEAN]);
assert_eq!(disjoint_literal, TypeId::NEVER);
}
#[test]
fn test_interner_intersection_disjoint_object_literals() {
let interner = TypeInterner::new();
let kind = interner.intern_string("kind");
let obj_a = interner.object(vec![PropertyInfo::new(kind, interner.literal_string("a"))]);
let obj_b = interner.object(vec![PropertyInfo::new(kind, interner.literal_string("b"))]);
let disjoint = interner.intersection(vec![obj_a, obj_b]);
assert_eq!(disjoint, TypeId::NEVER);
}
#[test]
fn test_interner_intersection_disjoint_object_literal_union() {
let interner = TypeInterner::new();
let kind = interner.intern_string("kind");
let union = interner.union(vec![
interner.literal_string("a"),
interner.literal_string("b"),
]);
let obj_union = interner.object(vec![PropertyInfo::new(kind, union)]);
let obj_c = interner.object(vec![PropertyInfo::new(kind, interner.literal_string("c"))]);
let disjoint = interner.intersection(vec![obj_union, obj_c]);
assert_eq!(disjoint, TypeId::NEVER);
}
#[test]
fn test_interner_intersection_optional_object_literals_not_reduced() {
let interner = TypeInterner::new();
let kind = interner.intern_string("kind");
let obj_a = interner.object(vec![PropertyInfo::opt(kind, interner.literal_string("a"))]);
let obj_b = interner.object(vec![PropertyInfo::opt(kind, interner.literal_string("b"))]);
let intersection = interner.intersection(vec![obj_a, obj_b]);
assert_ne!(intersection, TypeId::NEVER);
}
#[test]
fn test_interner_object_sorting() {
let interner = TypeInterner::new();
let props1 = vec![
PropertyInfo::new(interner.intern_string("a"), TypeId::STRING),
PropertyInfo::new(interner.intern_string("b"), TypeId::NUMBER),
];
let props2 = vec![
PropertyInfo::new(interner.intern_string("b"), TypeId::NUMBER),
PropertyInfo::new(interner.intern_string("a"), TypeId::STRING),
];
let id1 = interner.object(props1);
let id2 = interner.object(props2);
assert_eq!(id1, id2);
}
#[test]
fn test_interner_object_property_lookup_cache() {
let interner = TypeInterner::new();
let mut props = Vec::with_capacity(PROPERTY_MAP_THRESHOLD + 2);
for i in 0..(PROPERTY_MAP_THRESHOLD + 2) {
let name = format!("prop{}", i);
props.push(PropertyInfo::new(
interner.intern_string(&name),
TypeId::NUMBER,
));
}
let obj = interner.object(props);
let shape_id = match interner.lookup(obj) {
Some(TypeData::Object(shape_id)) => shape_id,
other => panic!("expected object type, got {:?}", other),
};
let target_name = format!("prop{}", PROPERTY_MAP_THRESHOLD / 2);
let target_atom = interner.intern_string(&target_name);
match interner.object_property_index(shape_id, target_atom) {
PropertyLookup::Found(idx) => {
let shape = interner.object_shape(shape_id);
assert_eq!(shape.properties[idx].name, target_atom);
}
other => panic!("expected cached lookup, got {:?}", other),
}
let missing = interner.intern_string("missing");
assert_eq!(
interner.object_property_index(shape_id, missing),
PropertyLookup::NotFound
);
let small = interner.object(vec![PropertyInfo::new(
interner.intern_string("only"),
TypeId::STRING,
)]);
let small_shape_id = match interner.lookup(small) {
Some(TypeData::Object(shape_id)) => shape_id,
other => panic!("expected object type, got {:?}", other),
};
assert_eq!(
interner.object_property_index(small_shape_id, interner.intern_string("only")),
PropertyLookup::Uncached
);
}
#[test]
fn test_interner_application_deduplication() {
let interner = TypeInterner::new();
let base = interner.lazy(DefId(1));
let app1 = interner.application(base, vec![TypeId::STRING]);
let app2 = interner.application(base, vec![TypeId::STRING]);
let app3 = interner.application(base, vec![TypeId::NUMBER]);
assert_eq!(app1, app2);
assert_ne!(app1, app3);
}
#[test]
fn test_tuple_list_interning_deduplication() {
use std::sync::Arc;
let interner = TypeInterner::new();
let elements = vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
},
];
let tuple_a = interner.tuple(elements.clone());
let tuple_b = interner.tuple(elements);
let Some(TypeData::Tuple(list_a)) = interner.lookup(tuple_a) else {
panic!("Expected tuple type");
};
let Some(TypeData::Tuple(list_b)) = interner.lookup(tuple_b) else {
panic!("Expected tuple type");
};
assert_eq!(list_a, list_b);
let elems_a = interner.tuple_list(list_a);
let elems_b = interner.tuple_list(list_b);
assert!(Arc::ptr_eq(&elems_a, &elems_b));
assert_eq!(elems_a.len(), 2);
}
#[test]
fn test_template_literal_list_interning_deduplication() {
use std::sync::Arc;
let interner = TypeInterner::new();
let spans = vec![
TemplateSpan::Text(interner.intern_string("prefix")),
TemplateSpan::Type(TypeId::STRING),
TemplateSpan::Text(interner.intern_string("suffix")),
];
let template_a = interner.template_literal(spans.clone());
let template_b = interner.template_literal(spans);
let Some(TypeData::TemplateLiteral(list_a)) = interner.lookup(template_a) else {
panic!("Expected template literal type");
};
let Some(TypeData::TemplateLiteral(list_b)) = interner.lookup(template_b) else {
panic!("Expected template literal type");
};
assert_eq!(list_a, list_b);
let spans_a = interner.template_list(list_a);
let spans_b = interner.template_list(list_b);
assert!(Arc::ptr_eq(&spans_a, &spans_b));
assert_eq!(spans_a.len(), 3);
}
#[test]
fn test_intersection_visibility_merging() {
let interner = TypeInterner::new();
let obj_private = interner.object(vec![PropertyInfo {
name: interner.intern_string("x"),
type_id: TypeId::NUMBER,
write_type: TypeId::NUMBER,
optional: false,
readonly: false,
is_method: false,
visibility: Visibility::Private,
parent_id: None,
}]);
let obj_public = interner.object(vec![PropertyInfo::new(
interner.intern_string("x"),
TypeId::STRING,
)]);
let intersection = interner.intersection2(obj_private, obj_public);
if let Some(TypeData::Object(shape_id)) = interner.lookup(intersection) {
let shape = interner.object_shape(shape_id);
assert_eq!(shape.properties.len(), 1);
assert_eq!(shape.properties[0].visibility, Visibility::Private);
} else {
panic!("Expected object type");
}
}
#[test]
fn test_intersection_disjoint_literals() {
let interner = TypeInterner::new();
let lit1 = interner.literal_number(1.0);
let lit2 = interner.literal_number(2.0);
let intersection = interner.intersection2(lit1, lit2);
assert_eq!(intersection, TypeId::NEVER);
}
#[test]
fn test_intersection_object_merging() {
let interner = TypeInterner::new();
let obj1 = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
TypeId::NUMBER,
)]);
let obj2 = interner.object(vec![PropertyInfo::new(
interner.intern_string("b"),
TypeId::NUMBER,
)]);
let intersection = interner.intersection2(obj1, obj2);
if let Some(TypeData::Object(shape_id)) = interner.lookup(intersection) {
let shape = interner.object_shape(shape_id);
assert_eq!(shape.properties.len(), 2);
let prop_names: Vec<_> = shape.properties.iter().map(|p| p.name.0).collect();
let atom_a = interner.intern_string("a").0;
let atom_b = interner.intern_string("b").0;
assert!(prop_names.contains(&atom_a));
assert!(prop_names.contains(&atom_b));
} else {
panic!("Expected object type");
}
}
#[test]
fn test_intersection_disjoint_property_types() {
let interner = TypeInterner::new();
let lit1 = interner.literal_number(1.0);
let lit2 = interner.literal_number(2.0);
let obj1 = interner.object(vec![PropertyInfo::new(interner.intern_string("a"), lit1)]);
let obj2 = interner.object(vec![PropertyInfo::new(interner.intern_string("a"), lit2)]);
let intersection = interner.intersection2(obj1, obj2);
assert_eq!(intersection, TypeId::NEVER);
}
#[test]
fn test_visibility_interning_distinct_shape_ids() {
let interner = TypeInterner::new();
let obj_public = interner.object(vec![PropertyInfo::new(
interner.intern_string("x"),
TypeId::NUMBER,
)]);
let obj_private = interner.object(vec![PropertyInfo {
name: interner.intern_string("x"),
type_id: TypeId::NUMBER,
write_type: TypeId::NUMBER,
optional: false,
readonly: false,
is_method: false,
visibility: Visibility::Private,
parent_id: None,
}]);
assert_ne!(
obj_public, obj_private,
"Objects with different visibility should have different TypeIds"
);
let shape_public = match interner.lookup(obj_public) {
Some(TypeData::Object(shape_id)) => shape_id,
other => panic!("Expected object type, got {:?}", other),
};
let shape_private = match interner.lookup(obj_private) {
Some(TypeData::Object(shape_id)) => shape_id,
other => panic!("Expected object type, got {:?}", other),
};
assert_ne!(
shape_public, shape_private,
"Objects with different visibility should have different ObjectShapeIds"
);
}
#[test]
fn test_parent_id_interning_distinct_shape_ids() {
let interner = TypeInterner::new();
let obj_class1 = interner.object(vec![PropertyInfo {
name: interner.intern_string("x"),
type_id: TypeId::NUMBER,
write_type: TypeId::NUMBER,
optional: false,
readonly: false,
is_method: false,
visibility: Visibility::Public,
parent_id: Some(SymbolId(1)),
}]);
let obj_class2 = interner.object(vec![PropertyInfo {
name: interner.intern_string("x"),
type_id: TypeId::NUMBER,
write_type: TypeId::NUMBER,
optional: false,
readonly: false,
is_method: false,
visibility: Visibility::Public,
parent_id: Some(SymbolId(2)),
}]);
assert_ne!(
obj_class1, obj_class2,
"Objects with different parent_id should have different TypeIds"
);
let shape_class1 = match interner.lookup(obj_class1) {
Some(TypeData::Object(shape_id)) => shape_id,
other => panic!("Expected object type, got {:?}", other),
};
let shape_class2 = match interner.lookup(obj_class2) {
Some(TypeData::Object(shape_id)) => shape_id,
other => panic!("Expected object type, got {:?}", other),
};
assert_ne!(
shape_class1, shape_class2,
"Objects with different parent_id should have different ObjectShapeIds"
);
}
#[test]
fn test_union_order_independence() {
let interner = TypeInterner::new();
let type_a = interner.literal_string("a");
let type_b = interner.literal_string("b");
let union_ab = interner.union(vec![type_a, type_b]);
let union_ba = interner.union(vec![type_b, type_a]);
assert_eq!(
union_ab, union_ba,
"Unions should be order-independent: A | B == B | A"
);
let type_c = interner.literal_string("c");
let union_abc = interner.union(vec![type_a, type_b, type_c]);
let union_cba = interner.union(vec![type_c, type_b, type_a]);
assert_eq!(
union_abc, union_cba,
"Unions with 3+ members should be order-independent"
);
}
#[test]
fn test_intersection_order_independence() {
let interner = TypeInterner::new();
let type_a = interner.literal_string("a");
let type_b = interner.literal_string("b");
let inter_ab = interner.intersection(vec![type_a, type_b]);
let inter_ba = interner.intersection(vec![type_b, type_a]);
assert_eq!(
inter_ab, inter_ba,
"Intersections should be order-independent: A & B == B & A"
);
}
#[test]
fn test_union_redundancy_elimination() {
let interner = TypeInterner::new();
let type_a = interner.literal_string("a");
let union_aa = interner.union(vec![type_a, type_a]);
assert_eq!(union_aa, type_a, "Union of A | A should simplify to A");
}
#[test]
fn test_intersection_redundancy_elimination() {
let interner = TypeInterner::new();
let type_a = interner.literal_string("a");
let inter_aa = interner.intersection(vec![type_a, type_a]);
assert_eq!(
inter_aa, type_a,
"Intersection of A & A should simplify to A"
);
}
#[test]
fn test_partial_object_merging_in_intersection() {
let interner = TypeInterner::new();
let obj1 = interner.object(vec![PropertyInfo {
name: interner.intern_string("a"),
type_id: TypeId::STRING,
write_type: TypeId::NEVER,
optional: false,
readonly: false,
is_method: false,
visibility: Visibility::Public,
parent_id: None,
}]);
let obj2 = interner.object(vec![PropertyInfo {
name: interner.intern_string("b"),
type_id: TypeId::NUMBER,
write_type: TypeId::NEVER,
optional: false,
readonly: false,
is_method: false,
visibility: Visibility::Public,
parent_id: None,
}]);
let prim = TypeId::BOOLEAN;
let inter1 = interner.intersection(vec![obj1, obj2, prim]);
let inter2 = interner.intersection(vec![obj2, obj1, prim]);
assert_eq!(
inter1, inter2,
"Partial object merging should be order-independent"
);
if let Some(TypeData::Intersection(members)) = interner.lookup(inter1) {
let member_list = interner.type_list(members);
assert_eq!(
member_list.len(),
2,
"Result should have 2 members: merged object + boolean"
);
} else {
panic!("Expected intersection type");
}
}
#[test]
fn test_partial_callable_merging_in_intersection() {
let interner = TypeInterner::new();
let func1 = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo::unnamed(TypeId::STRING)],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let func2 = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo::unnamed(TypeId::NUMBER)],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let prim = TypeId::BOOLEAN;
let inter = interner.intersection(vec![func1, func2, prim]);
if let Some(TypeData::Intersection(members)) = interner.lookup(inter) {
let member_list = interner.type_list(members);
assert_eq!(
member_list.len(),
2,
"Result should have 2 members: merged callable + boolean"
);
} else {
panic!("Expected intersection type");
}
}
#[test]
fn test_partial_object_and_callable_merging() {
let interner = TypeInterner::new();
let obj1 = interner.object(vec![PropertyInfo {
name: interner.intern_string("a"),
type_id: TypeId::STRING,
write_type: TypeId::NEVER,
optional: false,
readonly: false,
is_method: false,
visibility: Visibility::Public,
parent_id: None,
}]);
let obj2 = interner.object(vec![PropertyInfo {
name: interner.intern_string("b"),
type_id: TypeId::NUMBER,
write_type: TypeId::NEVER,
optional: false,
readonly: false,
is_method: false,
visibility: Visibility::Public,
parent_id: None,
}]);
let func1 = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo::unnamed(TypeId::STRING)],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let func2 = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo::unnamed(TypeId::NUMBER)],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let inter = interner.intersection(vec![obj1, obj2, func1, func2]);
if let Some(TypeData::Intersection(members)) = interner.lookup(inter) {
let member_list = interner.type_list(members);
assert_eq!(
member_list.len(),
2,
"Result should have 2 members: merged object + merged callable"
);
if let Some(TypeData::Object(_) | TypeData::ObjectWithIndex(_)) =
interner.lookup(member_list[0])
{
} else {
panic!("First member should be an object");
}
if let Some(TypeData::Callable(shape_id)) = interner.lookup(member_list[1]) {
let callable = interner.callable_shape(shape_id);
assert_eq!(
callable.call_signatures.len(),
2,
"Merged callable should have 2 call signatures"
);
} else {
panic!("Second member should be a callable");
}
} else {
panic!("Expected intersection type");
}
}
#[test]
fn test_template_never_absorption() {
let interner = TypeInterner::new();
let template = interner.template_literal(vec![TemplateSpan::Type(TypeId::NEVER)]);
assert_eq!(
template,
TypeId::NEVER,
"Template with never should be never"
);
let template2 = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("a")),
TemplateSpan::Type(TypeId::NEVER),
TemplateSpan::Text(interner.intern_string("b")),
]);
assert_eq!(
template2,
TypeId::NEVER,
"Template with never anywhere should be never"
);
}
#[test]
fn test_template_empty_string_removal() {
let interner = TypeInterner::new();
let empty_lit = interner.literal_string("");
let template = interner.template_literal(vec![TemplateSpan::Type(empty_lit)]);
match interner.lookup(template) {
Some(TypeData::Literal(LiteralValue::String(s))) => {
let s = interner.resolve_atom_ref(s);
assert!(s.is_empty(), "Should be empty string literal");
}
_ => panic!("Expected empty string literal"),
}
let template2 = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("a")),
TemplateSpan::Type(empty_lit),
TemplateSpan::Text(interner.intern_string("b")),
]);
match interner.lookup(template2) {
Some(TypeData::Literal(LiteralValue::String(s))) => {
let s = interner.resolve_atom_ref(s);
assert_eq!(s.to_string(), "ab", "Should be merged 'ab' literal");
}
_ => panic!("Expected 'ab' string literal"),
}
}
#[test]
fn test_template_unknown_widening() {
let interner = TypeInterner::new();
let template = interner.template_literal(vec![TemplateSpan::Type(TypeId::UNKNOWN)]);
assert_eq!(
template,
TypeId::STRING,
"Template with unknown should widen to string"
);
}
#[test]
fn test_template_any_widening() {
let interner = TypeInterner::new();
let template = interner.template_literal(vec![TemplateSpan::Type(TypeId::ANY)]);
assert_eq!(
template,
TypeId::STRING,
"Template with any should widen to string"
);
}
#[test]
fn test_empty_object_rule_intersection() {
let interner = TypeInterner::new();
let empty_obj = interner.object(vec![]);
let string_and_empty = interner.intersection(vec![TypeId::STRING, empty_obj]);
assert_eq!(
string_and_empty,
TypeId::STRING,
"string & empty_object should normalize to string"
);
let number_and_empty = interner.intersection(vec![TypeId::NUMBER, empty_obj]);
assert_eq!(
number_and_empty,
TypeId::NUMBER,
"number & empty_object should normalize to number"
);
let string_or_null = interner.union(vec![TypeId::STRING, TypeId::NULL]);
let union_and_empty = interner.intersection(vec![string_or_null, empty_obj]);
assert_eq!(
union_and_empty,
TypeId::STRING,
"(string | null) & empty_object should normalize to string"
);
let string_and_empty_and_number =
interner.intersection(vec![TypeId::STRING, empty_obj, TypeId::NUMBER]);
assert_eq!(
string_and_empty_and_number,
TypeId::NEVER,
"string & empty_object & number should be never (disjoint primitives)"
);
}