use super::*;
use crate::TypeInterner;
use crate::TypeResolver;
use crate::def::DefId;
use crate::{TypeSubstitution, Visibility, instantiate_type};
use tsz_binder::SymbolId;
#[test]
fn test_intrinsic_subtyping() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
assert!(checker.is_subtype_of(TypeId::STRING, TypeId::STRING));
assert!(checker.is_subtype_of(TypeId::NUMBER, TypeId::NUMBER));
assert!(!checker.is_subtype_of(TypeId::STRING, TypeId::NUMBER));
assert!(checker.is_subtype_of(TypeId::ANY, TypeId::STRING));
assert!(checker.is_subtype_of(TypeId::STRING, TypeId::ANY));
assert!(checker.is_subtype_of(TypeId::STRING, TypeId::UNKNOWN));
assert!(!checker.is_subtype_of(TypeId::UNKNOWN, TypeId::STRING));
assert!(checker.is_subtype_of(TypeId::NEVER, TypeId::STRING));
assert!(!checker.is_subtype_of(TypeId::STRING, TypeId::NEVER));
}
#[test]
fn test_any_top_bottom_subtyping() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
assert!(checker.is_subtype_of(TypeId::ANY, TypeId::NEVER));
assert!(checker.is_subtype_of(TypeId::NEVER, TypeId::ANY));
}
#[test]
fn test_legacy_null_undefined_subtyping() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
checker.strict_null_checks = false;
assert!(checker.is_subtype_of(TypeId::NULL, TypeId::STRING));
assert!(checker.is_subtype_of(TypeId::UNDEFINED, TypeId::STRING));
}
#[test]
fn test_error_type_permissive_subtyping() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
assert!(checker.is_subtype_of(TypeId::ERROR, TypeId::STRING));
assert!(checker.is_subtype_of(TypeId::STRING, TypeId::ERROR));
assert!(checker.is_subtype_of(TypeId::ERROR, TypeId::ERROR));
}
#[test]
fn test_error_type_acts_like_any() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let tuple = interner.tuple(vec![TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
}]);
assert!(checker.is_subtype_of(TypeId::ERROR, TypeId::OBJECT));
assert!(checker.is_subtype_of(tuple, TypeId::ERROR));
}
#[test]
fn test_literal_subtyping() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let hello = interner.literal_string("hello");
let world = interner.literal_string("world");
assert!(checker.is_subtype_of(hello, hello));
assert!(!checker.is_subtype_of(hello, world));
assert!(checker.is_subtype_of(hello, TypeId::STRING));
assert!(!checker.is_subtype_of(hello, TypeId::NUMBER));
}
#[test]
fn test_template_literal_subtyping_to_string() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let red = interner.literal_string("red");
let blue = interner.literal_string("blue");
let colors = interner.union(vec![red, blue]);
let template = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("color-")),
TemplateSpan::Type(colors),
]);
assert!(checker.is_subtype_of(template, TypeId::STRING));
assert!(!checker.is_subtype_of(TypeId::STRING, template));
}
#[test]
fn test_template_literal_apparent_member_subtyping() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let red = interner.literal_string("red");
let blue = interner.literal_string("blue");
let colors = interner.union(vec![red, blue]);
let template = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("color-")),
TemplateSpan::Type(colors),
]);
let method = |return_type| {
interner.function(FunctionShape {
params: Vec::new(),
this_type: None,
return_type,
type_params: Vec::new(),
type_predicate: None,
is_constructor: false,
is_method: false,
})
};
let to_upper = interner.intern_string("toUpperCase");
let target = interner.object(vec![PropertyInfo::method(to_upper, method(TypeId::STRING))]);
let mismatch = interner.object(vec![PropertyInfo::method(to_upper, method(TypeId::NUMBER))]);
assert!(checker.is_subtype_of(template, target));
assert!(!checker.is_subtype_of(template, mismatch));
}
#[test]
fn test_template_literal_number_index_subtyping() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let red = interner.literal_string("red");
let blue = interner.literal_string("blue");
let colors = interner.union(vec![red, blue]);
let template = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("color-")),
TemplateSpan::Type(colors),
]);
let target = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: Vec::new(),
string_index: None,
number_index: Some(IndexSignature {
key_type: TypeId::NUMBER,
value_type: TypeId::STRING,
readonly: false,
}),
});
let mismatch = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: Vec::new(),
string_index: None,
number_index: Some(IndexSignature {
key_type: TypeId::NUMBER,
value_type: TypeId::NUMBER,
readonly: false,
}),
});
assert!(checker.is_subtype_of(template, target));
assert!(!checker.is_subtype_of(template, mismatch));
}
#[test]
fn test_apparent_number_member_subtyping() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let rest_any = interner.array(TypeId::ANY);
let method = |return_type| {
interner.function(FunctionShape {
params: vec![ParamInfo {
name: None,
type_id: rest_any,
optional: false,
rest: true,
}],
this_type: None,
return_type,
type_params: Vec::new(),
type_predicate: None,
is_constructor: false,
is_method: false,
})
};
let to_fixed = interner.intern_string("toFixed");
let target = interner.object(vec![PropertyInfo::method(to_fixed, method(TypeId::STRING))]);
let mismatch = interner.object(vec![PropertyInfo::method(to_fixed, method(TypeId::NUMBER))]);
assert!(checker.is_subtype_of(TypeId::NUMBER, target));
assert!(!checker.is_subtype_of(TypeId::NUMBER, mismatch));
}
#[test]
fn test_apparent_string_member_subtyping() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let method = |return_type| {
interner.function(FunctionShape {
params: Vec::new(),
this_type: None,
return_type,
type_params: Vec::new(),
type_predicate: None,
is_constructor: false,
is_method: false,
})
};
let to_upper = interner.intern_string("toUpperCase");
let target = interner.object(vec![PropertyInfo::method(to_upper, method(TypeId::STRING))]);
let mismatch = interner.object(vec![PropertyInfo::method(to_upper, method(TypeId::NUMBER))]);
assert!(checker.is_subtype_of(TypeId::STRING, target));
assert!(!checker.is_subtype_of(TypeId::STRING, mismatch));
}
#[test]
fn test_apparent_string_length_subtyping() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let length = interner.intern_string("length");
let target = interner.object(vec![PropertyInfo::new(length, TypeId::NUMBER)]);
let mismatch = interner.object(vec![PropertyInfo::new(length, TypeId::STRING)]);
assert!(checker.is_subtype_of(TypeId::STRING, target));
assert!(!checker.is_subtype_of(TypeId::STRING, mismatch));
}
#[test]
fn test_apparent_string_number_index_subtyping() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let target = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: Vec::new(),
string_index: None,
number_index: Some(IndexSignature {
key_type: TypeId::NUMBER,
value_type: TypeId::STRING,
readonly: false,
}),
});
let mismatch = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: Vec::new(),
string_index: None,
number_index: Some(IndexSignature {
key_type: TypeId::NUMBER,
value_type: TypeId::NUMBER,
readonly: false,
}),
});
assert!(checker.is_subtype_of(TypeId::STRING, target));
assert!(!checker.is_subtype_of(TypeId::STRING, mismatch));
}
#[test]
fn test_apparent_boolean_member_subtyping() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let method = |return_type| {
interner.function(FunctionShape {
params: Vec::new(),
this_type: None,
return_type,
type_params: Vec::new(),
type_predicate: None,
is_constructor: false,
is_method: false,
})
};
let value_of = interner.intern_string("valueOf");
let target = interner.object(vec![PropertyInfo::method(
value_of,
method(TypeId::BOOLEAN),
)]);
let mismatch = interner.object(vec![PropertyInfo::method(value_of, method(TypeId::NUMBER))]);
assert!(checker.is_subtype_of(TypeId::BOOLEAN, target));
assert!(!checker.is_subtype_of(TypeId::BOOLEAN, mismatch));
}
#[test]
fn test_apparent_symbol_member_subtyping() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let description = interner.union(vec![TypeId::STRING, TypeId::UNDEFINED]);
let name = interner.intern_string("description");
let target = interner.object(vec![PropertyInfo {
name,
type_id: description,
write_type: description,
optional: false,
readonly: false,
is_method: false,
visibility: Visibility::Public,
parent_id: None,
}]);
let mismatch = interner.object(vec![PropertyInfo {
name,
type_id: TypeId::STRING,
write_type: TypeId::STRING,
optional: false,
readonly: false,
is_method: false,
visibility: Visibility::Public,
parent_id: None,
}]);
assert!(checker.is_subtype_of(TypeId::SYMBOL, target));
assert!(!checker.is_subtype_of(TypeId::SYMBOL, mismatch));
}
#[test]
fn test_apparent_bigint_member_subtyping() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let method = |return_type| {
interner.function(FunctionShape {
params: Vec::new(),
this_type: None,
return_type,
type_params: Vec::new(),
type_predicate: None,
is_constructor: false,
is_method: false,
})
};
let value_of = interner.intern_string("valueOf");
let target = interner.object(vec![PropertyInfo::method(value_of, method(TypeId::BIGINT))]);
let mismatch = interner.object(vec![PropertyInfo::method(value_of, method(TypeId::NUMBER))]);
assert!(checker.is_subtype_of(TypeId::BIGINT, target));
assert!(!checker.is_subtype_of(TypeId::BIGINT, mismatch));
}
#[test]
fn test_apparent_object_member_subtyping() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let method = |return_type| {
interner.function(FunctionShape {
params: Vec::new(),
this_type: None,
return_type,
type_params: Vec::new(),
type_predicate: None,
is_constructor: false,
is_method: false,
})
};
let has_own = interner.intern_string("hasOwnProperty");
let target = interner.object(vec![PropertyInfo::method(has_own, method(TypeId::BOOLEAN))]);
let mismatch = interner.object(vec![PropertyInfo::method(has_own, method(TypeId::STRING))]);
assert!(checker.is_subtype_of(TypeId::NUMBER, target));
assert!(!checker.is_subtype_of(TypeId::NUMBER, mismatch));
}
#[test]
fn test_object_trifecta_subtyping() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let obj = interner.object(vec![PropertyInfo::new(
interner.intern_string("x"),
TypeId::NUMBER,
)]);
let array = interner.array(TypeId::STRING);
let tuple = interner.tuple(vec![TupleElement {
type_id: TypeId::BOOLEAN,
name: None,
optional: false,
rest: false,
}]);
let func = interner.function(FunctionShape {
params: Vec::new(),
this_type: None,
return_type: TypeId::VOID,
type_params: Vec::new(),
type_predicate: None,
is_constructor: false,
is_method: false,
});
let empty_object = interner.object(Vec::new());
assert!(checker.is_subtype_of(obj, TypeId::OBJECT));
assert!(checker.is_subtype_of(array, TypeId::OBJECT));
assert!(checker.is_subtype_of(tuple, TypeId::OBJECT));
assert!(checker.is_subtype_of(func, TypeId::OBJECT));
assert!(checker.is_subtype_of(TypeId::STRING, empty_object));
assert!(!checker.is_subtype_of(TypeId::STRING, TypeId::OBJECT));
assert!(!checker.is_subtype_of(TypeId::NUMBER, TypeId::OBJECT));
}
#[test]
fn test_object_trifecta_object_interface_accepts_primitives() {
let interner = TypeInterner::new();
let mut env = TypeEnvironment::new();
let to_string = interner.function(FunctionShape {
params: Vec::new(),
this_type: None,
return_type: TypeId::STRING,
type_params: Vec::new(),
type_predicate: None,
is_constructor: false,
is_method: false,
});
let object_interface = interner.object(vec![PropertyInfo::method(
interner.intern_string("toString"),
to_string,
)]);
let def_id = DefId(1);
env.insert_def(def_id, object_interface);
let object_ref = interner.lazy(def_id);
let mut checker = SubtypeChecker::with_resolver(&interner, &env);
let empty_object = interner.object(Vec::new());
assert!(checker.is_subtype_of(TypeId::STRING, object_ref));
assert!(checker.is_subtype_of(TypeId::NUMBER, object_ref));
assert!(checker.is_subtype_of(TypeId::STRING, empty_object));
assert!(!checker.is_subtype_of(TypeId::STRING, TypeId::OBJECT));
}
#[test]
fn test_object_trifecta_nullish_rejection() {
let interner = TypeInterner::new();
let mut env = TypeEnvironment::new();
let to_string = interner.function(FunctionShape {
params: Vec::new(),
this_type: None,
return_type: TypeId::STRING,
type_params: Vec::new(),
type_predicate: None,
is_constructor: false,
is_method: false,
});
let object_interface = interner.object(vec![PropertyInfo::method(
interner.intern_string("toString"),
to_string,
)]);
let def_id = DefId(99);
env.insert_def(def_id, object_interface);
let object_ref = interner.lazy(def_id);
let mut checker = SubtypeChecker::with_resolver(&interner, &env);
let empty_object = interner.object(Vec::new());
assert!(!checker.is_subtype_of(TypeId::NULL, TypeId::OBJECT));
assert!(!checker.is_subtype_of(TypeId::UNDEFINED, TypeId::OBJECT));
assert!(!checker.is_subtype_of(TypeId::NULL, empty_object));
assert!(!checker.is_subtype_of(TypeId::UNDEFINED, empty_object));
assert!(!checker.is_subtype_of(TypeId::NULL, object_ref));
assert!(!checker.is_subtype_of(TypeId::UNDEFINED, object_ref));
}
#[test]
fn test_primitive_boxing_assignability() {
let interner = TypeInterner::new();
let mut env = TypeEnvironment::new();
let to_fixed = interner.function(FunctionShape {
params: Vec::new(),
this_type: None,
return_type: TypeId::STRING,
type_params: Vec::new(),
type_predicate: None,
is_constructor: false,
is_method: false,
});
let number_interface = interner.object(vec![PropertyInfo::method(
interner.intern_string("toFixed"),
to_fixed,
)]);
let def_id = DefId(2);
env.insert_def(def_id, number_interface);
let number_ref = interner.lazy(def_id);
let mut checker = SubtypeChecker::with_resolver(&interner, &env);
assert!(checker.is_subtype_of(TypeId::NUMBER, number_ref));
assert!(!checker.is_subtype_of(number_ref, TypeId::NUMBER));
}
#[test]
fn test_primitive_boxing_bigint_assignability() {
let interner = TypeInterner::new();
let mut env = TypeEnvironment::new();
let to_string = interner.function(FunctionShape {
params: Vec::new(),
this_type: None,
return_type: TypeId::STRING,
type_params: Vec::new(),
type_predicate: None,
is_constructor: false,
is_method: false,
});
let bigint_interface = interner.object(vec![PropertyInfo::method(
interner.intern_string("toString"),
to_string,
)]);
let def_id = DefId(3);
env.insert_def(def_id, bigint_interface);
let bigint_ref = interner.lazy(def_id);
let mut checker = SubtypeChecker::with_resolver(&interner, &env);
assert!(checker.is_subtype_of(TypeId::BIGINT, bigint_ref));
assert!(!checker.is_subtype_of(bigint_ref, TypeId::BIGINT));
}
#[test]
fn test_primitive_boxing_boolean_assignability() {
let interner = TypeInterner::new();
let mut env = TypeEnvironment::new();
let to_string = interner.function(FunctionShape {
params: Vec::new(),
this_type: None,
return_type: TypeId::STRING,
type_params: Vec::new(),
type_predicate: None,
is_constructor: false,
is_method: false,
});
let boolean_interface = interner.object(vec![PropertyInfo::method(
interner.intern_string("toString"),
to_string,
)]);
let def_id = DefId(4);
env.insert_def(def_id, boolean_interface);
let boolean_ref = interner.lazy(def_id);
let mut checker = SubtypeChecker::with_resolver(&interner, &env);
assert!(checker.is_subtype_of(TypeId::BOOLEAN, boolean_ref));
assert!(!checker.is_subtype_of(boolean_ref, TypeId::BOOLEAN));
}
#[test]
fn test_primitive_boxing_string_assignability() {
let interner = TypeInterner::new();
let mut env = TypeEnvironment::new();
let to_upper = interner.function(FunctionShape {
params: Vec::new(),
this_type: None,
return_type: TypeId::STRING,
type_params: Vec::new(),
type_predicate: None,
is_constructor: false,
is_method: false,
});
let string_interface = interner.object(vec![PropertyInfo::method(
interner.intern_string("toUpperCase"),
to_upper,
)]);
let def_id = DefId(5);
env.insert_def(def_id, string_interface);
let string_ref = interner.lazy(def_id);
let mut checker = SubtypeChecker::with_resolver(&interner, &env);
assert!(checker.is_subtype_of(TypeId::STRING, string_ref));
assert!(!checker.is_subtype_of(string_ref, TypeId::STRING));
}
#[test]
fn test_primitive_boxing_symbol_assignability() {
let interner = TypeInterner::new();
let mut env = TypeEnvironment::new();
let description = interner.union(vec![TypeId::STRING, TypeId::UNDEFINED]);
let symbol_interface = interner.object(vec![PropertyInfo::new(
interner.intern_string("description"),
description,
)]);
let def_id = DefId(6);
env.insert_def(def_id, symbol_interface);
let symbol_ref = interner.lazy(def_id);
let mut checker = SubtypeChecker::with_resolver(&interner, &env);
assert!(checker.is_subtype_of(TypeId::SYMBOL, symbol_ref));
assert!(!checker.is_subtype_of(symbol_ref, TypeId::SYMBOL));
}
#[test]
fn test_weak_type_detection_requires_overlap() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a = interner.intern_string("a");
let b = interner.intern_string("b");
let weak_target = interner.object(vec![PropertyInfo::opt(a, TypeId::NUMBER)]);
let no_overlap = interner.object(vec![PropertyInfo::new(b, TypeId::NUMBER)]);
let overlap = interner.object(vec![PropertyInfo::new(a, TypeId::NUMBER)]);
assert!(checker.is_subtype_of(no_overlap, weak_target));
assert!(checker.is_subtype_of(overlap, weak_target));
}
#[test]
fn test_weak_type_detection_empty_object_allowed() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a = interner.intern_string("a");
let weak_target = interner.object(vec![PropertyInfo::opt(a, TypeId::NUMBER)]);
let empty_object = interner.object(vec![]);
assert!(checker.is_subtype_of(empty_object, weak_target));
}
#[test]
fn test_weak_type_detection_multiple_optional_properties() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a = interner.intern_string("a");
let b = interner.intern_string("b");
let c = interner.intern_string("c");
let weak_target = interner.object(vec![
PropertyInfo::opt(a, TypeId::NUMBER),
PropertyInfo::opt(b, TypeId::STRING),
]);
let no_overlap = interner.object(vec![PropertyInfo::new(c, TypeId::BOOLEAN)]);
assert!(checker.is_subtype_of(no_overlap, weak_target));
let partial_overlap = interner.object(vec![PropertyInfo::new(a, TypeId::NUMBER)]);
assert!(checker.is_subtype_of(partial_overlap, weak_target));
}
#[test]
fn test_weak_type_detection_not_weak_if_has_required() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a = interner.intern_string("a");
let b = interner.intern_string("b");
let not_weak_target = interner.object(vec![
PropertyInfo::opt(a, TypeId::NUMBER),
PropertyInfo {
name: b,
type_id: TypeId::STRING,
write_type: TypeId::STRING,
optional: false, readonly: false,
is_method: false,
visibility: Visibility::Public,
parent_id: None,
},
]);
let c = interner.intern_string("c");
let unrelated_source = interner.object(vec![PropertyInfo::new(c, TypeId::BOOLEAN)]);
assert!(!checker.is_subtype_of(unrelated_source, not_weak_target));
}
#[test]
fn test_split_accessor_variance() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let name = interner.intern_string("x");
let wide_write = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let wide_accessor = interner.object(vec![PropertyInfo {
name,
type_id: TypeId::STRING,
write_type: wide_write,
optional: false,
readonly: false,
is_method: false,
visibility: Visibility::Public,
parent_id: None,
}]);
let narrow_accessor = interner.object(vec![PropertyInfo {
name,
type_id: TypeId::STRING,
write_type: TypeId::STRING,
optional: false,
readonly: false,
is_method: false,
visibility: Visibility::Public,
parent_id: None,
}]);
assert!(checker.is_subtype_of(wide_accessor, narrow_accessor));
assert!(!checker.is_subtype_of(narrow_accessor, wide_accessor));
}
#[test]
fn test_exact_optional_property_types_toggle() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let name = interner.intern_string("x");
let target = interner.object(vec![PropertyInfo {
name,
type_id: TypeId::NUMBER,
write_type: TypeId::NUMBER,
optional: true,
readonly: false,
is_method: false,
visibility: Visibility::Public,
parent_id: None,
}]);
let source = interner.object(vec![PropertyInfo {
name,
type_id: TypeId::UNDEFINED,
write_type: TypeId::UNDEFINED,
optional: false,
readonly: false,
is_method: false,
visibility: Visibility::Public,
parent_id: None,
}]);
assert!(checker.is_subtype_of(source, target));
checker.exact_optional_property_types = true;
assert!(!checker.is_subtype_of(source, target));
}
#[test]
fn test_unique_symbol_subtyping() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let sym_a = interner.intern(TypeData::UniqueSymbol(SymbolRef(1)));
let sym_b = interner.intern(TypeData::UniqueSymbol(SymbolRef(2)));
assert!(checker.is_subtype_of(sym_a, sym_a));
assert!(!checker.is_subtype_of(sym_a, sym_b));
assert!(checker.is_subtype_of(sym_a, TypeId::SYMBOL));
assert!(!checker.is_subtype_of(TypeId::SYMBOL, sym_a));
}
#[test]
fn test_union_subtyping() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let string_or_number = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert!(checker.is_subtype_of(TypeId::STRING, string_or_number));
assert!(checker.is_subtype_of(TypeId::NUMBER, string_or_number));
assert!(!checker.is_subtype_of(TypeId::BOOLEAN, string_or_number));
let just_string = interner.union(vec![TypeId::STRING]);
assert!(checker.is_subtype_of(just_string, string_or_number));
}
#[test]
fn test_recursion_depth_limit_provisional_subtyping() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
fn nest_array(interner: &TypeInterner, base: TypeId, depth: usize) -> TypeId {
let mut ty = base;
for _ in 0..depth {
ty = interner.array(ty);
}
ty
}
let shallow_string = nest_array(&interner, TypeId::STRING, 10);
let shallow_number = nest_array(&interner, TypeId::NUMBER, 10);
assert!(!checker.is_subtype_of(shallow_string, shallow_number));
let deep_string = nest_array(&interner, TypeId::STRING, 120);
let deep_number = nest_array(&interner, TypeId::NUMBER, 120);
let result = checker.check_subtype(deep_string, deep_number);
assert!(matches!(result, SubtypeResult::DepthExceeded));
assert!(checker.guard.is_exceeded());
}
#[test]
fn test_no_unchecked_indexed_access_array_subtyping() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let string_array = interner.array(TypeId::STRING);
let index_access = interner.intern(TypeData::IndexAccess(string_array, TypeId::NUMBER));
assert!(checker.is_subtype_of(index_access, TypeId::STRING));
checker.no_unchecked_indexed_access = true;
assert!(!checker.is_subtype_of(index_access, TypeId::STRING));
let string_or_undefined = interner.union(vec![TypeId::STRING, TypeId::UNDEFINED]);
assert!(checker.is_subtype_of(index_access, string_or_undefined));
}
#[test]
fn test_no_unchecked_indexed_access_tuple_subtyping() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let tuple = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
},
]);
let index_access = interner.intern(TypeData::IndexAccess(tuple, TypeId::NUMBER));
let string_or_number = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert!(checker.is_subtype_of(index_access, string_or_number));
checker.no_unchecked_indexed_access = true;
assert!(!checker.is_subtype_of(index_access, string_or_number));
let string_number_or_undefined =
interner.union(vec![TypeId::STRING, TypeId::NUMBER, TypeId::UNDEFINED]);
assert!(checker.is_subtype_of(index_access, string_number_or_undefined));
}
#[test]
fn test_no_unchecked_object_index_signature_subtyping() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let indexed = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: Vec::new(),
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: TypeId::NUMBER,
readonly: false,
}),
number_index: None,
});
let index_access = interner.intern(TypeData::IndexAccess(indexed, TypeId::NUMBER));
assert!(checker.is_subtype_of(index_access, TypeId::NUMBER));
checker.no_unchecked_indexed_access = true;
assert!(!checker.is_subtype_of(index_access, TypeId::NUMBER));
let number_or_undefined = interner.union(vec![TypeId::NUMBER, TypeId::UNDEFINED]);
assert!(checker.is_subtype_of(index_access, number_or_undefined));
}
#[test]
fn test_no_unchecked_indexed_access_string_index_signature() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let indexed = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: Vec::new(),
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: TypeId::NUMBER,
readonly: false,
}),
number_index: None,
});
let index_access = interner.intern(TypeData::IndexAccess(indexed, TypeId::STRING));
assert!(checker.is_subtype_of(index_access, TypeId::NUMBER));
checker.no_unchecked_indexed_access = true;
assert!(!checker.is_subtype_of(index_access, TypeId::NUMBER));
let number_or_undefined = interner.union(vec![TypeId::NUMBER, TypeId::UNDEFINED]);
assert!(checker.is_subtype_of(index_access, number_or_undefined));
}
#[test]
fn test_no_unchecked_indexed_access_union_index_signature() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let indexed = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: Vec::new(),
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: TypeId::NUMBER,
readonly: false,
}),
number_index: None,
});
let index_type = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let index_access = interner.intern(TypeData::IndexAccess(indexed, index_type));
assert!(checker.is_subtype_of(index_access, TypeId::NUMBER));
checker.no_unchecked_indexed_access = true;
assert!(!checker.is_subtype_of(index_access, TypeId::NUMBER));
let number_or_undefined = interner.union(vec![TypeId::NUMBER, TypeId::UNDEFINED]);
assert!(checker.is_subtype_of(index_access, number_or_undefined));
}
#[test]
fn test_correlated_union_index_access_subtyping() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let kind = interner.intern_string("kind");
let key_a = interner.intern_string("a");
let key_b = interner.intern_string("b");
let obj_a = interner.object(vec![
PropertyInfo::new(kind, interner.literal_string("a")),
PropertyInfo::new(key_a, TypeId::NUMBER),
]);
let obj_b = interner.object(vec![
PropertyInfo::new(kind, interner.literal_string("b")),
PropertyInfo::new(key_b, TypeId::STRING),
]);
let union_obj = interner.union(vec![obj_a, obj_b]);
let key_union = interner.union(vec![
interner.literal_string("a"),
interner.literal_string("b"),
]);
let index_access = interner.intern(TypeData::IndexAccess(union_obj, key_union));
let expected = interner.union(vec![TypeId::NUMBER, TypeId::STRING]);
assert!(checker.is_subtype_of(index_access, expected));
assert!(!checker.is_subtype_of(index_access, TypeId::NUMBER));
}
#[test]
fn test_object_subtyping() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let obj_x = interner.object(vec![PropertyInfo::new(
interner.intern_string("x"),
TypeId::NUMBER,
)]);
let obj_xy = interner.object(vec![
PropertyInfo::new(interner.intern_string("x"), TypeId::NUMBER),
PropertyInfo::new(interner.intern_string("y"), TypeId::STRING),
]);
assert!(checker.is_subtype_of(obj_xy, obj_x));
assert!(!checker.is_subtype_of(obj_x, obj_xy));
}
#[test]
fn test_readonly_property_subtyping() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let name = interner.intern_string("x");
let readonly_obj = interner.object(vec![PropertyInfo {
name,
type_id: TypeId::NUMBER,
write_type: TypeId::NUMBER,
optional: false,
readonly: true,
is_method: false,
visibility: Visibility::Public,
parent_id: None,
}]);
let mutable_obj = interner.object(vec![PropertyInfo {
name,
type_id: TypeId::NUMBER,
write_type: TypeId::NUMBER,
optional: false,
readonly: false,
is_method: false,
visibility: Visibility::Public,
parent_id: None,
}]);
assert!(checker.is_subtype_of(readonly_obj, mutable_obj));
assert!(checker.is_subtype_of(mutable_obj, readonly_obj));
}
#[test]
fn test_readonly_array_subtyping() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let mutable_array = interner.array(TypeId::STRING);
let readonly_array = interner.intern(TypeData::ReadonlyType(mutable_array));
assert!(checker.is_subtype_of(mutable_array, readonly_array));
assert!(!checker.is_subtype_of(readonly_array, mutable_array));
}
#[test]
fn test_readonly_tuple_subtyping() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let tuple = interner.tuple(vec![
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
]);
let readonly_tuple = interner.intern(TypeData::ReadonlyType(tuple));
assert!(checker.is_subtype_of(tuple, readonly_tuple));
assert!(!checker.is_subtype_of(readonly_tuple, tuple));
}
#[test]
fn test_array_subtyping() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let string_array = interner.array(TypeId::STRING);
let number_array = interner.array(TypeId::NUMBER);
let any_array = interner.array(TypeId::ANY);
assert!(checker.is_subtype_of(string_array, string_array));
assert!(!checker.is_subtype_of(string_array, number_array));
assert!(checker.is_subtype_of(string_array, any_array));
}
#[test]
fn test_array_covariant_mutable_unsoundness() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let string_array = interner.array(TypeId::STRING);
let string_or_number = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let union_array = interner.array(string_or_number);
assert!(checker.is_subtype_of(string_array, union_array));
assert!(!checker.is_subtype_of(union_array, string_array));
}
#[test]
fn test_type_environment() {
let _interner = TypeInterner::new();
let mut env = TypeEnvironment::new();
assert!(env.is_empty());
assert_eq!(env.len(), 0);
let sym1 = SymbolRef(1);
let sym2 = SymbolRef(2);
env.insert(sym1, TypeId::STRING);
env.insert(sym2, TypeId::NUMBER);
assert_eq!(env.get(sym1), Some(TypeId::STRING));
assert_eq!(env.get(sym2), Some(TypeId::NUMBER));
assert_eq!(env.get(SymbolRef(999)), None);
assert!(env.contains(sym1));
assert!(!env.contains(SymbolRef(999)));
assert_eq!(env.len(), 2);
}
#[test]
fn test_ref_resolution_with_environment() {
let interner = TypeInterner::new();
let mut env = TypeEnvironment::new();
let ref_type = interner.lazy(DefId(1));
let mut checker = SubtypeChecker::new(&interner);
assert!(!checker.is_subtype_of(ref_type, TypeId::STRING));
env.insert_def(DefId(1), TypeId::STRING);
let mut checker_with_env = SubtypeChecker::with_resolver(&interner, &env);
assert!(checker_with_env.is_subtype_of(ref_type, TypeId::STRING));
assert!(!checker_with_env.is_subtype_of(ref_type, TypeId::NUMBER));
}
#[test]
fn test_reference_lazy_fallback_uses_symbol_to_def_mapping() {
let interner = TypeInterner::new();
let mut env = TypeEnvironment::new();
let real_def = DefId(100);
env.insert_def(real_def, TypeId::STRING);
env.register_def_symbol_mapping(real_def, SymbolId(5));
let raw_reference = interner.reference(SymbolRef(5));
let mut checker = SubtypeChecker::with_resolver(&interner, &env);
assert!(checker.is_subtype_of(raw_reference, TypeId::STRING));
assert!(!checker.is_subtype_of(raw_reference, TypeId::NUMBER));
}
#[test]
fn test_lazy_type_params_falls_back_from_symbol_based_lazy_ref() {
let interner = TypeInterner::new();
let mut env = TypeEnvironment::new();
let t_param = TypeParamInfo {
name: interner.intern_string("T"),
constraint: Some(TypeId::STRING),
default: None,
is_const: false,
};
let generic_def = DefId(200);
env.insert_def_with_params(generic_def, TypeId::STRING, vec![t_param.clone()]);
env.register_def_symbol_mapping(generic_def, SymbolId(42));
let raw_lazy = env
.get_lazy_type_params(DefId(42))
.expect("fallback should resolve params");
assert_eq!(raw_lazy.len(), 1);
assert_eq!(raw_lazy[0], t_param);
let symbol_reference = interner.reference(SymbolRef(42));
let mut checker = SubtypeChecker::with_resolver(&interner, &env);
assert!(checker.is_subtype_of(symbol_reference, TypeId::STRING));
}
#[test]
fn test_ref_to_ref_resolution() {
let interner = TypeInterner::new();
let mut env = TypeEnvironment::new();
let ref1 = interner.lazy(DefId(1));
let ref2 = interner.lazy(DefId(2));
env.insert_def(DefId(1), TypeId::STRING);
env.insert_def(DefId(2), TypeId::STRING);
let mut checker = SubtypeChecker::with_resolver(&interner, &env);
assert!(checker.is_subtype_of(ref1, ref2));
assert!(checker.is_subtype_of(ref2, ref1));
}
#[test]
fn test_ref_to_object_resolution() {
let interner = TypeInterner::new();
let mut env = TypeEnvironment::new();
let obj_x = interner.object(vec![PropertyInfo::new(
interner.intern_string("x"),
TypeId::NUMBER,
)]);
let obj_xy = interner.object(vec![
PropertyInfo::new(interner.intern_string("x"), TypeId::NUMBER),
PropertyInfo::new(interner.intern_string("y"), TypeId::STRING),
]);
let ref_type = interner.lazy(DefId(100));
env.insert_def(DefId(100), obj_xy);
let mut checker = SubtypeChecker::with_resolver(&interner, &env);
assert!(checker.is_subtype_of(ref_type, obj_x));
}
#[test]
fn test_unresolved_ref_behavior() {
let interner = TypeInterner::new();
let env = TypeEnvironment::new();
let ref_type = interner.lazy(DefId(999));
let mut checker = SubtypeChecker::with_resolver(&interner, &env);
assert!(checker.is_subtype_of(ref_type, ref_type));
assert!(!checker.is_subtype_of(ref_type, TypeId::STRING));
}
#[test]
fn test_function_rest_parameter_subtyping() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let any_array = interner.array(TypeId::ANY);
let fixed_params = FunctionShape {
type_params: vec![],
params: vec![
ParamInfo {
name: Some(interner.intern_string("a")),
type_id: TypeId::STRING,
optional: false,
rest: false,
},
ParamInfo {
name: Some(interner.intern_string("b")),
type_id: TypeId::ANY,
optional: false,
rest: false,
},
ParamInfo {
name: Some(interner.intern_string("c")),
type_id: TypeId::ANY,
optional: false,
rest: false,
},
],
this_type: None,
return_type: TypeId::ANY,
type_predicate: None,
is_constructor: false,
is_method: false,
};
let fixed_fn = interner.function(fixed_params);
let rest_params = FunctionShape {
type_params: vec![],
params: vec![
ParamInfo {
name: Some(interner.intern_string("a")),
type_id: TypeId::STRING,
optional: false,
rest: false,
},
ParamInfo {
name: Some(interner.intern_string("b")),
type_id: TypeId::ANY,
optional: false,
rest: false,
},
ParamInfo {
name: Some(interner.intern_string("args")),
type_id: any_array,
optional: false,
rest: true,
},
],
this_type: None,
return_type: TypeId::ANY,
type_predicate: None,
is_constructor: false,
is_method: false,
};
let rest_fn = interner.function(rest_params);
assert!(checker.is_subtype_of(fixed_fn, rest_fn));
}
#[test]
fn test_rest_unknown_bivariant_subtyping_toggle() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let rest_unknown = interner.array(TypeId::UNKNOWN);
let target = interner.function(FunctionShape {
params: vec![ParamInfo {
name: None,
type_id: rest_unknown,
optional: false,
rest: true,
}],
this_type: None,
return_type: TypeId::VOID,
type_params: Vec::new(),
type_predicate: None,
is_constructor: false,
is_method: false,
});
let source = interner.function(FunctionShape {
params: vec![ParamInfo::unnamed(TypeId::STRING)],
this_type: None,
return_type: TypeId::VOID,
type_params: Vec::new(),
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(!checker.is_subtype_of(source, target));
checker.allow_bivariant_rest = true;
assert!(checker.is_subtype_of(source, target));
}
#[test]
#[ignore = "Method bivariance/strict function types not fully implemented"]
fn test_rest_any_bivariant_subtyping_toggle() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let rest_any = interner.array(TypeId::ANY);
let target = interner.function(FunctionShape {
params: vec![ParamInfo {
name: None,
type_id: rest_any,
optional: false,
rest: true,
}],
this_type: None,
return_type: TypeId::VOID,
type_params: Vec::new(),
type_predicate: None,
is_constructor: false,
is_method: false,
});
let source = interner.function(FunctionShape {
params: vec![ParamInfo::unnamed(TypeId::NUMBER)],
this_type: None,
return_type: TypeId::VOID,
type_params: Vec::new(),
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(checker.is_subtype_of(source, target));
checker.allow_bivariant_rest = true;
assert!(checker.is_subtype_of(source, target));
}
#[test]
fn test_tuple_subtyping_extra_elements() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let source = interner.tuple(vec![
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
]);
let target = interner.tuple(vec![TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
}]);
assert!(!checker.is_subtype_of(source, target));
}
#[test]
fn test_tuple_subtyping_with_rest_target() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let string_array = interner.array(TypeId::STRING);
let source = interner.tuple(vec![
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
]);
let target = interner.tuple(vec![
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: string_array,
name: None,
optional: false,
rest: true,
},
]);
assert!(checker.is_subtype_of(source, target));
}
#[test]
fn test_tuple_subtyping_rest_tuple_expansion() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let rest_tuple = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::BOOLEAN,
name: None,
optional: false,
rest: false,
},
]);
let source = interner.tuple(vec![
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::BOOLEAN,
name: None,
optional: false,
rest: false,
},
]);
let target = interner.tuple(vec![
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: rest_tuple,
name: None,
optional: false,
rest: true,
},
]);
assert!(checker.is_subtype_of(source, target));
}
#[test]
fn test_tuple_subtyping_rest_tuple_missing_element() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let rest_tuple = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::BOOLEAN,
name: None,
optional: false,
rest: false,
},
]);
let source = interner.tuple(vec![
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
]);
let target = interner.tuple(vec![
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: rest_tuple,
name: None,
optional: false,
rest: true,
},
]);
assert!(!checker.is_subtype_of(source, target));
}
#[test]
fn test_tuple_subtyping_rest_tuple_extra_element() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let rest_tuple = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::BOOLEAN,
name: None,
optional: false,
rest: false,
},
]);
let source = interner.tuple(vec![
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::BOOLEAN,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::BOOLEAN,
name: None,
optional: false,
rest: false,
},
]);
let target = interner.tuple(vec![
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: rest_tuple,
name: None,
optional: false,
rest: true,
},
]);
assert!(!checker.is_subtype_of(source, target));
}
#[test]
fn test_tuple_subtyping_rest_tuple_variadic_tail() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let boolean_array = interner.array(TypeId::BOOLEAN);
let rest_tuple = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: boolean_array,
name: None,
optional: false,
rest: true,
},
]);
let source = interner.tuple(vec![
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::BOOLEAN,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::BOOLEAN,
name: None,
optional: false,
rest: false,
},
]);
let target = interner.tuple(vec![
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: rest_tuple,
name: None,
optional: false,
rest: true,
},
]);
assert!(checker.is_subtype_of(source, target));
}
#[test]
fn test_tuple_subtyping_source_rest_closed_target() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let string_array = interner.array(TypeId::STRING);
let source = interner.tuple(vec![
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: string_array,
name: None,
optional: false,
rest: true,
},
]);
let target = interner.tuple(vec![
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
]);
assert!(!checker.is_subtype_of(source, target));
}
#[test]
fn test_tuple_subtyping_optional_elements() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let source = interner.tuple(vec![
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: true,
rest: false,
},
]);
let target = interner.tuple(vec![TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
}]);
assert!(!checker.is_subtype_of(source, target));
}
#[test]
fn test_tuple_subtyping_rest_to_rest() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let string_array = interner.array(TypeId::STRING);
let source = interner.tuple(vec![
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: string_array,
name: None,
optional: false,
rest: true,
},
]);
let target = interner.tuple(vec![
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: string_array,
name: None,
optional: false,
rest: true,
},
]);
assert!(checker.is_subtype_of(source, target));
}
#[test]
fn test_tuple_to_array_with_rest() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let string_array = interner.array(TypeId::STRING);
let source = interner.tuple(vec![
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: string_array,
name: None,
optional: false,
rest: true,
},
]);
let target = string_array;
assert!(!checker.is_subtype_of(source, target));
}
#[test]
fn test_tuple_to_array_with_rest_tuple() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let string_array = interner.array(TypeId::STRING);
let rest_tuple = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
]);
let source = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: rest_tuple,
name: None,
optional: false,
rest: true,
},
]);
assert!(checker.is_subtype_of(source, string_array));
}
#[test]
fn test_tuple_to_array_with_rest_tuple_mismatch() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let string_array = interner.array(TypeId::STRING);
let rest_tuple = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
},
]);
let source = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: rest_tuple,
name: None,
optional: false,
rest: true,
},
]);
assert!(!checker.is_subtype_of(source, string_array));
}
#[test]
fn test_tuple_to_array_with_rest_tuple_variadic() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let string_array = interner.array(TypeId::STRING);
let rest_tuple = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: string_array,
name: None,
optional: false,
rest: true,
},
]);
let source = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: rest_tuple,
name: None,
optional: false,
rest: true,
},
]);
assert!(checker.is_subtype_of(source, string_array));
}
#[test]
fn test_tuple_to_array_all_matching_with_rest() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let string_array = interner.array(TypeId::STRING);
let source = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: string_array,
name: None,
optional: false,
rest: true,
},
]);
let target = string_array;
assert!(checker.is_subtype_of(source, target));
}
#[test]
fn test_tuple_to_array_no_rest() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let string_array = interner.array(TypeId::STRING);
let source = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
]);
let target = string_array;
assert!(checker.is_subtype_of(source, target));
}
#[test]
fn test_tuple_to_array_mixed_types() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let string_array = interner.array(TypeId::STRING);
let source = interner.tuple(vec![
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
]);
let target = string_array;
assert!(!checker.is_subtype_of(source, target));
}
#[test]
fn test_tuple_to_array_number_number() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let number_array = interner.array(TypeId::NUMBER);
let source = interner.tuple(vec![
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
},
]);
let target = number_array;
assert!(checker.is_subtype_of(source, target));
}
#[test]
fn test_tuple_array_assignment_tuple_to_union_array() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let union_elem = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let union_array = interner.array(union_elem);
let source = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
},
]);
assert!(checker.is_subtype_of(source, union_array));
}
#[test]
fn test_array_to_variadic_tuple() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let string_array = interner.array(TypeId::STRING);
let target = interner.tuple(vec![TupleElement {
type_id: string_array,
name: None,
optional: false,
rest: true,
}]);
assert!(!checker.is_subtype_of(string_array, target));
}
#[test]
fn test_tuple_array_assignment_array_to_tuple_rejected() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let string_array = interner.array(TypeId::STRING);
let target = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
},
]);
assert!(!checker.is_subtype_of(string_array, target));
}
#[test]
fn test_array_to_variadic_tuple_with_required_prefix() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let string_array = interner.array(TypeId::STRING);
let target = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: string_array,
name: None,
optional: false,
rest: true,
},
]);
assert!(!checker.is_subtype_of(string_array, target));
}
#[test]
fn test_array_to_variadic_tuple_with_optional_prefix() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let string_array = interner.array(TypeId::STRING);
let target = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: true,
rest: false,
},
TupleElement {
type_id: string_array,
name: None,
optional: false,
rest: true,
},
]);
assert!(!checker.is_subtype_of(string_array, target));
}
#[test]
fn test_array_to_fixed_optional_tuple() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let string_array = interner.array(TypeId::STRING);
let target = interner.tuple(vec![TupleElement {
type_id: TypeId::STRING,
name: None,
optional: true,
rest: false,
}]);
assert!(!checker.is_subtype_of(string_array, target));
}
#[test]
fn test_tuple_array_assignment_empty_array_optional_tuple() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let empty_array = interner.array(TypeId::NEVER);
let optional_tuple = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: true,
rest: false,
},
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: true,
rest: false,
},
]);
assert!(checker.is_subtype_of(empty_array, optional_tuple));
}
#[test]
fn test_never_array_to_optional_tuple() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let never_array = interner.array(TypeId::NEVER);
let empty_tuple = interner.tuple(Vec::new());
let optional_tuple = interner.tuple(vec![TupleElement {
type_id: TypeId::STRING,
name: None,
optional: true,
rest: false,
}]);
let required_tuple = interner.tuple(vec![TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
}]);
assert!(checker.is_subtype_of(never_array, empty_tuple));
assert!(checker.is_subtype_of(never_array, optional_tuple));
assert!(!checker.is_subtype_of(never_array, required_tuple));
}
#[test]
fn test_never_array_to_variadic_tuple() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let never_array = interner.array(TypeId::NEVER);
let string_array = interner.array(TypeId::STRING);
let target = interner.tuple(vec![TupleElement {
type_id: string_array,
name: None,
optional: false,
rest: true,
}]);
assert!(checker.is_subtype_of(never_array, target));
}
#[test]
fn test_number_index_signature_numeric_property() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let source = interner.object(vec![PropertyInfo::new(
interner.intern_string("0"),
TypeId::STRING,
)]);
let target_shape = ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
number_index: Some(IndexSignature {
key_type: TypeId::NUMBER,
value_type: TypeId::STRING,
readonly: false,
}),
string_index: None,
};
let target = interner.object_with_index(target_shape);
assert!(checker.is_subtype_of(source, target));
}
#[test]
fn test_number_index_signature_type_mismatch() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let source = interner.object(vec![PropertyInfo::new(
interner.intern_string("0"),
TypeId::NUMBER,
)]);
let target_shape = ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
number_index: Some(IndexSignature {
key_type: TypeId::NUMBER,
value_type: TypeId::STRING,
readonly: false,
}),
string_index: None,
};
let target = interner.object_with_index(target_shape);
assert!(!checker.is_subtype_of(source, target));
}
#[test]
fn test_number_index_signature_vacuously_compatible_with_no_numeric_keys() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let source = interner.object(vec![PropertyInfo::new(
interner.intern_string("one"),
TypeId::NUMBER,
)]);
let target_shape = ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
number_index: Some(IndexSignature {
key_type: TypeId::NUMBER,
value_type: TypeId::NUMBER,
readonly: false,
}),
string_index: None,
};
let target = interner.object_with_index(target_shape);
assert!(!checker.is_subtype_of(source, target));
}
#[test]
#[ignore = "Method bivariance/strict function types not fully implemented"]
fn test_number_index_signature_method_bivariant_property() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let narrow_param = TypeId::STRING;
let wide_param = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let narrow_method = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: narrow_param,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let wide_fn = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: wide_param,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let source_method = interner.object(vec![PropertyInfo::method(
interner.intern_string("0"),
narrow_method,
)]);
let source_prop = interner.object(vec![PropertyInfo::new(
interner.intern_string("0"),
narrow_method,
)]);
let target_shape = ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
number_index: Some(IndexSignature {
key_type: TypeId::NUMBER,
value_type: wide_fn,
readonly: false,
}),
string_index: None,
};
let target = interner.object_with_index(target_shape);
assert!(checker.is_subtype_of(source_method, target));
assert!(!checker.is_subtype_of(source_prop, target));
}
#[test]
#[ignore = "Method bivariance/strict function types not fully implemented"]
fn test_string_index_signature_method_bivariant_property() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let narrow_param = TypeId::STRING;
let wide_param = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let narrow_method = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: narrow_param,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let wide_fn = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: wide_param,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let source_method = interner.object(vec![PropertyInfo::method(
interner.intern_string("foo"),
narrow_method,
)]);
let source_prop = interner.object(vec![PropertyInfo::new(
interner.intern_string("foo"),
narrow_method,
)]);
let target_shape = ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
number_index: None,
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: wide_fn,
readonly: false,
}),
};
let target = interner.object_with_index(target_shape);
assert!(checker.is_subtype_of(source_method, target));
assert!(!checker.is_subtype_of(source_prop, target));
}
#[test]
fn test_number_index_signature_multiple_numeric_props() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let source = interner.object(vec![
PropertyInfo::new(interner.intern_string("0"), TypeId::STRING),
PropertyInfo::new(interner.intern_string("1"), TypeId::STRING),
PropertyInfo::new(interner.intern_string("2"), TypeId::STRING),
]);
let target_shape = ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
number_index: Some(IndexSignature {
key_type: TypeId::NUMBER,
value_type: TypeId::STRING,
readonly: false,
}),
string_index: None,
};
let target = interner.object_with_index(target_shape);
assert!(checker.is_subtype_of(source, target));
}
#[test]
fn test_number_and_string_index_signatures() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let source = interner.object(vec![
PropertyInfo::new(interner.intern_string("0"), TypeId::STRING),
PropertyInfo::new(interner.intern_string("foo"), TypeId::STRING),
]);
let target_shape = ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
number_index: Some(IndexSignature {
key_type: TypeId::NUMBER,
value_type: TypeId::STRING,
readonly: false,
}),
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: TypeId::STRING,
readonly: false,
}),
};
let target = interner.object_with_index(target_shape);
assert!(checker.is_subtype_of(source, target));
}
#[test]
fn test_index_signature_consistency_number_vs_string_index() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let source = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
number_index: Some(IndexSignature {
key_type: TypeId::NUMBER,
value_type: TypeId::STRING,
readonly: false,
}),
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: TypeId::NUMBER,
readonly: false,
}),
});
let target = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
number_index: Some(IndexSignature {
key_type: TypeId::NUMBER,
value_type: TypeId::NUMBER,
readonly: false,
}),
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: TypeId::NUMBER,
readonly: false,
}),
});
assert!(!checker.is_subtype_of(source, target));
}
#[test]
fn test_readonly_index_signature_subtyping() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let readonly_source = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
number_index: None,
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: TypeId::NUMBER,
readonly: true,
}),
});
let mutable_target = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
number_index: None,
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: TypeId::NUMBER,
readonly: false,
}),
});
let readonly_target = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
number_index: None,
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: TypeId::NUMBER,
readonly: true,
}),
});
assert!(!checker.is_subtype_of(readonly_source, mutable_target));
assert!(checker.is_subtype_of(mutable_target, readonly_target));
}
#[test]
fn test_readonly_property_with_mutable_index_signature() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let source = interner.object(vec![PropertyInfo::readonly(
interner.intern_string("x"),
TypeId::NUMBER,
)]);
let mutable_index = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
number_index: None,
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: TypeId::NUMBER,
readonly: false,
}),
});
let readonly_index = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
number_index: None,
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: TypeId::NUMBER,
readonly: true,
}),
});
assert!(!checker.is_subtype_of(source, mutable_index));
assert!(checker.is_subtype_of(source, readonly_index));
}
#[test]
fn test_object_with_index_properties_match_target_index() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let source = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![
PropertyInfo::new(interner.intern_string("0"), TypeId::STRING),
PropertyInfo::new(interner.intern_string("name"), TypeId::STRING),
],
number_index: Some(IndexSignature {
key_type: TypeId::NUMBER,
value_type: TypeId::STRING,
readonly: false,
}),
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: TypeId::STRING,
readonly: false,
}),
});
let target = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
number_index: Some(IndexSignature {
key_type: TypeId::NUMBER,
value_type: TypeId::STRING,
readonly: false,
}),
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: TypeId::STRING,
readonly: false,
}),
});
assert!(checker.is_subtype_of(source, target));
}
#[test]
fn test_object_with_index_property_mismatch_string_index() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let source = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![PropertyInfo::new(
interner.intern_string("name"),
TypeId::STRING,
)],
number_index: None,
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: TypeId::NUMBER,
readonly: false,
}),
});
let target = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
number_index: None,
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: TypeId::NUMBER,
readonly: false,
}),
});
assert!(!checker.is_subtype_of(source, target));
}
#[test]
fn test_object_with_index_property_mismatch_number_index() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let source = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![PropertyInfo::new(
interner.intern_string("0"),
TypeId::STRING,
)],
number_index: Some(IndexSignature {
key_type: TypeId::NUMBER,
value_type: TypeId::NUMBER,
readonly: false,
}),
string_index: None,
});
let target = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
number_index: Some(IndexSignature {
key_type: TypeId::NUMBER,
value_type: TypeId::NUMBER,
readonly: false,
}),
string_index: None,
});
assert!(!checker.is_subtype_of(source, target));
}
#[test]
fn test_object_with_index_satisfies_named_property_string_index() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let source = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
number_index: None,
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: TypeId::NUMBER,
readonly: false,
}),
});
let target = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
TypeId::NUMBER,
)]);
assert!(!checker.is_subtype_of(source, target));
}
#[test]
fn test_object_with_index_named_property_mismatch_string_index() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let source = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
number_index: None,
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: TypeId::NUMBER,
readonly: false,
}),
});
let target = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
TypeId::STRING,
)]);
assert!(!checker.is_subtype_of(source, target));
}
#[test]
fn test_object_to_indexed_property_mismatch_string_index() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let source = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
TypeId::STRING,
)]);
let target = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
number_index: None,
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: TypeId::NUMBER,
readonly: false,
}),
});
assert!(!checker.is_subtype_of(source, target));
}
#[test]
fn test_object_with_index_satisfies_numeric_property_number_index() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let source = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
number_index: Some(IndexSignature {
key_type: TypeId::NUMBER,
value_type: TypeId::STRING,
readonly: false,
}),
string_index: None,
});
let target = interner.object(vec![PropertyInfo::new(
interner.intern_string("0"),
TypeId::STRING,
)]);
assert!(!checker.is_subtype_of(source, target));
}
#[test]
fn test_object_with_index_noncanonical_numeric_property_fails() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let source = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
number_index: Some(IndexSignature {
key_type: TypeId::NUMBER,
value_type: TypeId::STRING,
readonly: false,
}),
string_index: None,
});
let target = interner.object(vec![PropertyInfo::new(
interner.intern_string("01"),
TypeId::STRING,
)]);
assert!(!checker.is_subtype_of(source, target));
}
#[test]
fn test_object_with_index_readonly_index_to_mutable_property_fails() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let source = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
number_index: None,
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: TypeId::NUMBER,
readonly: true,
}),
});
let target = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
TypeId::NUMBER,
)]);
assert!(!checker.is_subtype_of(source, target));
}
#[test]
fn test_type_parameter_constraint_assignability() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let t_param = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: interner.intern_string("T"),
constraint: Some(TypeId::STRING),
default: None,
is_const: false,
}));
assert!(checker.is_subtype_of(t_param, TypeId::STRING));
assert!(!checker.is_subtype_of(t_param, TypeId::NUMBER));
let unconstrained = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: interner.intern_string("U"),
constraint: None,
default: None,
is_const: false,
}));
assert!(!checker.is_subtype_of(unconstrained, TypeId::STRING));
}
#[test]
fn test_base_constraint_assignability_subtyping() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let t_param = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: interner.intern_string("T"),
constraint: Some(TypeId::STRING),
default: None,
is_const: false,
}));
let u_param = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: interner.intern_string("U"),
constraint: Some(TypeId::STRING),
default: None,
is_const: false,
}));
let v_param = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: interner.intern_string("V"),
constraint: Some(TypeId::NUMBER),
default: None,
is_const: false,
}));
assert!(checker.is_subtype_of(t_param, TypeId::STRING));
assert!(!checker.is_subtype_of(t_param, TypeId::NUMBER));
assert!(!checker.is_subtype_of(t_param, u_param));
assert!(!checker.is_subtype_of(t_param, v_param));
}
#[test]
fn test_base_constraint_not_assignable_to_param() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let t_param = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: interner.intern_string("T"),
constraint: Some(TypeId::STRING),
default: None,
is_const: false,
}));
assert!(!checker.is_subtype_of(TypeId::STRING, t_param));
}
#[test]
fn test_type_parameter_identity_only() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let t_param = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: interner.intern_string("T"),
constraint: Some(TypeId::STRING),
default: None,
is_const: false,
}));
let u_param = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: interner.intern_string("U"),
constraint: Some(TypeId::STRING),
default: None,
is_const: false,
}));
assert!(!checker.is_subtype_of(t_param, u_param));
}
#[test]
fn test_deferred_conditional_source_subtyping() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let t_param = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: interner.intern_string("T"),
constraint: None,
default: None,
is_const: false,
}));
let conditional = interner.conditional(ConditionalType {
check_type: t_param,
extends_type: TypeId::STRING,
true_type: TypeId::NUMBER,
false_type: TypeId::BOOLEAN,
is_distributive: true,
});
let target_union = interner.union(vec![TypeId::NUMBER, TypeId::BOOLEAN]);
assert!(checker.is_subtype_of(conditional, target_union));
assert!(!checker.is_subtype_of(conditional, TypeId::NUMBER));
}
#[test]
fn test_deferred_conditional_target_subtyping() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let t_param = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: interner.intern_string("T"),
constraint: None,
default: None,
is_const: false,
}));
let conditional = interner.conditional(ConditionalType {
check_type: t_param,
extends_type: TypeId::STRING,
true_type: TypeId::NUMBER,
false_type: TypeId::BOOLEAN,
is_distributive: true,
});
assert!(!checker.is_subtype_of(TypeId::NUMBER, conditional));
}
#[test]
fn test_deferred_conditional_structural_subtyping() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let t_param = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: interner.intern_string("T"),
constraint: None,
default: None,
is_const: false,
}));
let source = interner.conditional(ConditionalType {
check_type: t_param,
extends_type: TypeId::STRING,
true_type: TypeId::NUMBER,
false_type: TypeId::BOOLEAN,
is_distributive: true,
});
let union_nb = interner.union(vec![TypeId::NUMBER, TypeId::BOOLEAN]);
let target = interner.conditional(ConditionalType {
check_type: t_param,
extends_type: TypeId::STRING,
true_type: union_nb,
false_type: union_nb,
is_distributive: true,
});
let mismatch = interner.conditional(ConditionalType {
check_type: t_param,
extends_type: TypeId::NUMBER,
true_type: union_nb,
false_type: union_nb,
is_distributive: true,
});
assert!(checker.is_subtype_of(source, target));
assert!(!checker.is_subtype_of(source, mismatch));
}
#[test]
fn test_conditional_tuple_wrapper_no_distribution_subtyping() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let t_name = interner.intern_string("T");
let t_param = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: t_name,
constraint: None,
default: None,
is_const: false,
}));
let tuple_check = interner.tuple(vec![TupleElement {
type_id: t_param,
name: None,
optional: false,
rest: false,
}]);
let tuple_extends = interner.tuple(vec![TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
}]);
let conditional = interner.conditional(ConditionalType {
check_type: tuple_check,
extends_type: tuple_extends,
true_type: TypeId::NUMBER,
false_type: TypeId::BOOLEAN,
is_distributive: false,
});
let string_or_number = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let mut subst = TypeSubstitution::new();
subst.insert(t_name, string_or_number);
let instantiated = instantiate_type(&interner, conditional, &subst);
assert!(checker.is_subtype_of(instantiated, TypeId::BOOLEAN));
assert!(!checker.is_subtype_of(instantiated, TypeId::NUMBER));
}
#[test]
fn test_strict_function_variance() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
assert!(checker.strict_function_types);
let union_arg_fn = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: interner.union(vec![TypeId::STRING, TypeId::NUMBER]),
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let string_arg_fn = 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::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(checker.is_subtype_of(union_arg_fn, string_arg_fn));
assert!(!checker.is_subtype_of(string_arg_fn, union_arg_fn));
checker.strict_function_types = false;
assert!(checker.is_subtype_of(string_arg_fn, union_arg_fn));
}
#[test]
fn test_function_variance_union_intersection_targets() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let fn_with_param = |param| {
interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: param,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
})
};
let fn_string = fn_with_param(TypeId::STRING);
let fn_number = fn_with_param(TypeId::NUMBER);
let fn_union_param = fn_with_param(interner.union(vec![TypeId::STRING, TypeId::NUMBER]));
let union_target = interner.union(vec![fn_string, fn_number]);
let intersection_target = interner.intersection(vec![fn_string, fn_number]);
assert!(checker.is_subtype_of(fn_union_param, union_target));
assert!(checker.is_subtype_of(fn_union_param, intersection_target));
assert!(!checker.is_subtype_of(fn_string, intersection_target));
assert!(!checker.is_subtype_of(union_target, fn_union_param));
}
#[test]
fn test_callable_rest_parameter_contravariance() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let rest_union = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let rest_array = interner.array(rest_union);
let source = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![CallSignature {
type_params: vec![],
params: vec![
ParamInfo {
name: Some(interner.intern_string("x")),
type_id: TypeId::STRING,
optional: false,
rest: false,
},
ParamInfo {
name: Some(interner.intern_string("y")),
type_id: TypeId::STRING,
optional: false,
rest: false,
},
],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_method: false,
}],
construct_signatures: vec![],
properties: vec![],
string_index: None,
number_index: None,
});
let target = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![CallSignature {
type_params: vec![],
params: vec![
ParamInfo {
name: Some(interner.intern_string("x")),
type_id: TypeId::STRING,
optional: false,
rest: false,
},
ParamInfo {
name: Some(interner.intern_string("args")),
type_id: rest_array,
optional: false,
rest: true,
},
],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_method: false,
}],
construct_signatures: vec![],
properties: vec![],
string_index: None,
number_index: None,
});
assert!(!checker.is_subtype_of(source, target));
}
#[test]
#[ignore = "Method bivariance/strict function types not fully implemented"]
fn test_method_bivariant_required_param() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let method_name = interner.intern_string("m");
let wide_param = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let narrow_param = TypeId::STRING;
let wide_method = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: wide_param,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let narrow_method = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: narrow_param,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let wide_obj = interner.object(vec![PropertyInfo::method(method_name, wide_method)]);
let narrow_obj = interner.object(vec![PropertyInfo::method(method_name, narrow_method)]);
assert!(checker.is_subtype_of(wide_obj, narrow_obj));
assert!(checker.is_subtype_of(narrow_obj, wide_obj));
}
#[test]
#[ignore = "Method bivariance/strict function types not fully implemented"]
fn test_method_source_bivariant_against_function_property() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let name = interner.intern_string("m");
let wide_param = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let narrow_param = TypeId::STRING;
let narrow_method = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: narrow_param,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let wide_func = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: wide_param,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let source = interner.object(vec![PropertyInfo {
name,
type_id: narrow_method,
write_type: narrow_method,
optional: false,
readonly: false,
is_method: true,
visibility: Visibility::Public,
parent_id: None,
}]);
let target = interner.object(vec![PropertyInfo {
name,
type_id: wide_func,
write_type: wide_func,
optional: false,
readonly: false,
is_method: false,
visibility: Visibility::Public,
parent_id: None,
}]);
assert!(checker.is_subtype_of(source, target));
}
#[test]
#[ignore = "Method bivariance/strict function types not fully implemented"]
fn test_function_source_bivariant_against_method_property() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let name = interner.intern_string("m");
let wide_param = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let narrow_param = TypeId::STRING;
let narrow_func = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: narrow_param,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let wide_method = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: wide_param,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let source = interner.object(vec![PropertyInfo {
name,
type_id: narrow_func,
write_type: narrow_func,
optional: false,
readonly: false,
is_method: false,
visibility: Visibility::Public,
parent_id: None,
}]);
let target = interner.object(vec![PropertyInfo {
name,
type_id: wide_method,
write_type: wide_method,
optional: false,
readonly: false,
is_method: true,
visibility: Visibility::Public,
parent_id: None,
}]);
assert!(checker.is_subtype_of(source, target));
}
#[test]
#[ignore = "Method bivariance/strict function types not fully implemented"]
fn test_variance_optional_rest_method_optional_bivariant() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let method_name = interner.intern_string("m");
let wide_param = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let narrow_param = TypeId::STRING;
let wide_method = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: wide_param,
optional: true,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let narrow_method = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: narrow_param,
optional: true,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let wide_obj = interner.object(vec![PropertyInfo::method(method_name, wide_method)]);
let narrow_obj = interner.object(vec![PropertyInfo::method(method_name, narrow_method)]);
assert!(checker.is_subtype_of(wide_obj, narrow_obj));
assert!(checker.is_subtype_of(narrow_obj, wide_obj));
}
#[test]
#[ignore = "Method bivariance/strict function types not fully implemented"]
fn test_variance_optional_rest_method_rest_bivariant() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let method_name = interner.intern_string("m");
let wide_elem = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let narrow_elem = TypeId::STRING;
let wide_rest = interner.array(wide_elem);
let narrow_rest = interner.array(narrow_elem);
let wide_method = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("args")),
type_id: wide_rest,
optional: false,
rest: true,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let narrow_method = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("args")),
type_id: narrow_rest,
optional: false,
rest: true,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let wide_obj = interner.object(vec![PropertyInfo::method(method_name, wide_method)]);
let narrow_obj = interner.object(vec![PropertyInfo::method(method_name, narrow_method)]);
assert!(checker.is_subtype_of(wide_obj, narrow_obj));
assert!(checker.is_subtype_of(narrow_obj, wide_obj));
}
#[test]
#[ignore = "Method bivariance/strict function types not fully implemented"]
fn test_variance_optional_rest_method_optional_with_this_bivariant() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let method_name = interner.intern_string("m");
let wide_this = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let wide_param = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let narrow_param = TypeId::STRING;
let wide_method = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: wide_param,
optional: true,
rest: false,
}],
this_type: Some(wide_this),
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let narrow_method = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: narrow_param,
optional: true,
rest: false,
}],
this_type: Some(TypeId::STRING),
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let wide_obj = interner.object(vec![PropertyInfo::method(method_name, wide_method)]);
let narrow_obj = interner.object(vec![PropertyInfo::method(method_name, narrow_method)]);
assert!(checker.is_subtype_of(wide_obj, narrow_obj));
assert!(checker.is_subtype_of(narrow_obj, wide_obj));
}
#[test]
#[ignore = "Method bivariance/strict function types not fully implemented"]
fn test_variance_optional_rest_method_rest_with_this_bivariant() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let method_name = interner.intern_string("m");
let wide_this = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let wide_elem = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let narrow_elem = TypeId::STRING;
let wide_rest = interner.array(wide_elem);
let narrow_rest = interner.array(narrow_elem);
let wide_method = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("args")),
type_id: wide_rest,
optional: false,
rest: true,
}],
this_type: Some(wide_this),
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let narrow_method = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("args")),
type_id: narrow_rest,
optional: false,
rest: true,
}],
this_type: Some(TypeId::STRING),
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let wide_obj = interner.object(vec![PropertyInfo::method(method_name, wide_method)]);
let narrow_obj = interner.object(vec![PropertyInfo::method(method_name, narrow_method)]);
assert!(checker.is_subtype_of(wide_obj, narrow_obj));
assert!(checker.is_subtype_of(narrow_obj, wide_obj));
}
#[test]
fn test_variance_optional_rest_function_optional_with_this_contravariant() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let func_name = interner.intern_string("f");
let wide_this = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let wide_param = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let narrow_param = TypeId::STRING;
let wide_func = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: wide_param,
optional: true,
rest: false,
}],
this_type: Some(wide_this),
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let narrow_func = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: narrow_param,
optional: true,
rest: false,
}],
this_type: Some(TypeId::STRING),
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let wide_obj = interner.object(vec![PropertyInfo::new(func_name, wide_func)]);
let narrow_obj = interner.object(vec![PropertyInfo::new(func_name, narrow_func)]);
assert!(checker.is_subtype_of(wide_obj, narrow_obj));
assert!(!checker.is_subtype_of(narrow_obj, wide_obj));
}
#[test]
fn test_variance_optional_rest_function_rest_with_this_contravariant() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let func_name = interner.intern_string("f");
let wide_this = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let wide_elem = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let narrow_elem = TypeId::STRING;
let wide_rest = interner.array(wide_elem);
let narrow_rest = interner.array(narrow_elem);
let wide_func = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("args")),
type_id: wide_rest,
optional: false,
rest: true,
}],
this_type: Some(wide_this),
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let narrow_func = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("args")),
type_id: narrow_rest,
optional: false,
rest: true,
}],
this_type: Some(TypeId::STRING),
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let wide_obj = interner.object(vec![PropertyInfo::new(func_name, wide_func)]);
let narrow_obj = interner.object(vec![PropertyInfo::new(func_name, narrow_func)]);
assert!(checker.is_subtype_of(wide_obj, narrow_obj));
assert!(!checker.is_subtype_of(narrow_obj, wide_obj));
}
#[test]
#[ignore = "Method bivariance/strict function types not fully implemented"]
fn test_variance_optional_rest_constructor_optional_bivariant() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let wide_param = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let narrow_param = TypeId::STRING;
let wide_ctor = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: wide_param,
optional: true,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: true,
is_method: false,
});
let narrow_ctor = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: narrow_param,
optional: true,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: true,
is_method: false,
});
assert!(checker.is_subtype_of(wide_ctor, narrow_ctor));
assert!(checker.is_subtype_of(narrow_ctor, wide_ctor));
}
#[test]
#[ignore = "Method bivariance/strict function types not fully implemented"]
fn test_variance_optional_rest_constructor_rest_bivariant() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let wide_elem = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let narrow_elem = TypeId::STRING;
let wide_rest = interner.array(wide_elem);
let narrow_rest = interner.array(narrow_elem);
let wide_ctor = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("args")),
type_id: wide_rest,
optional: false,
rest: true,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: true,
is_method: false,
});
let narrow_ctor = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("args")),
type_id: narrow_rest,
optional: false,
rest: true,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: true,
is_method: false,
});
assert!(checker.is_subtype_of(wide_ctor, narrow_ctor));
assert!(checker.is_subtype_of(narrow_ctor, wide_ctor));
}
#[test]
fn test_function_required_count_allows_optional_source_extra() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let source = interner.function(FunctionShape {
type_params: vec![],
params: vec![
ParamInfo {
name: Some(interner.intern_string("x")),
type_id: TypeId::STRING,
optional: false,
rest: false,
},
ParamInfo {
name: Some(interner.intern_string("y")),
type_id: TypeId::NUMBER,
optional: true,
rest: false,
},
],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let target = 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::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(checker.is_subtype_of(source, target));
}
#[test]
fn test_function_required_count_rejects_required_source_extra() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let source = interner.function(FunctionShape {
type_params: vec![],
params: vec![
ParamInfo {
name: Some(interner.intern_string("x")),
type_id: TypeId::STRING,
optional: false,
rest: false,
},
ParamInfo {
name: Some(interner.intern_string("y")),
type_id: TypeId::NUMBER,
optional: false,
rest: false,
},
],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let target = interner.function(FunctionShape {
type_params: vec![],
params: vec![
ParamInfo {
name: Some(interner.intern_string("x")),
type_id: TypeId::STRING,
optional: false,
rest: false,
},
ParamInfo {
name: Some(interner.intern_string("y")),
type_id: TypeId::NUMBER,
optional: true,
rest: false,
},
],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(!checker.is_subtype_of(source, target));
}
#[test]
fn test_function_variance_param_contravariance() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let wide_param = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let narrow_param = TypeId::STRING;
let source = interner.function(FunctionShape {
type_params: vec![],
params: vec![
ParamInfo {
name: Some(interner.intern_string("x")),
type_id: wide_param,
optional: false,
rest: false,
},
ParamInfo {
name: Some(interner.intern_string("y")),
type_id: TypeId::BOOLEAN,
optional: false,
rest: false,
},
],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let target = interner.function(FunctionShape {
type_params: vec![],
params: vec![
ParamInfo {
name: Some(interner.intern_string("x")),
type_id: narrow_param,
optional: false,
rest: false,
},
ParamInfo {
name: Some(interner.intern_string("y")),
type_id: TypeId::BOOLEAN,
optional: false,
rest: false,
},
],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(checker.is_subtype_of(source, target));
assert!(!checker.is_subtype_of(target, source));
}
#[test]
fn test_function_variance_return_covariance() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let narrow_return = TypeId::STRING;
let wide_return = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let source = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: TypeId::BOOLEAN,
optional: false,
rest: false,
}],
this_type: None,
return_type: narrow_return,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let target = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: TypeId::BOOLEAN,
optional: false,
rest: false,
}],
this_type: None,
return_type: wide_return,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(checker.is_subtype_of(source, target));
assert!(!checker.is_subtype_of(target, source));
}
#[test]
fn test_function_return_covariance() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let returns_string = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::STRING,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let returns_string_or_number = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: interner.union(vec![TypeId::STRING, TypeId::NUMBER]),
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(checker.is_subtype_of(returns_string, returns_string_or_number));
assert!(!checker.is_subtype_of(returns_string_or_number, returns_string));
}
#[test]
fn test_void_return_exception_subtype() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let returns_number = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::NUMBER,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let returns_void = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(!checker.is_subtype_of(returns_number, returns_void));
checker.allow_void_return = true;
assert!(checker.is_subtype_of(returns_number, returns_void));
assert!(!checker.is_subtype_of(returns_void, returns_number));
}
#[test]
fn test_void_return_exception_method_property() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let method_name = interner.intern_string("m");
let returns_number = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::NUMBER,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let returns_void = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let source = interner.object(vec![PropertyInfo::method(method_name, returns_number)]);
let target = interner.object(vec![PropertyInfo::method(method_name, returns_void)]);
assert!(!checker.is_subtype_of(source, target));
checker.allow_void_return = true;
assert!(checker.is_subtype_of(source, target));
assert!(!checker.is_subtype_of(target, source));
}
#[test]
fn test_constructor_void_exception_subtype() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let instance = interner.object(vec![PropertyInfo::new(
interner.intern_string("x"),
TypeId::NUMBER,
)]);
let returns_instance = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: instance,
type_predicate: None,
is_constructor: true,
is_method: false,
});
let returns_void = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: true,
is_method: false,
});
assert!(!checker.is_subtype_of(returns_instance, returns_void));
checker.allow_void_return = true;
assert!(checker.is_subtype_of(returns_instance, returns_void));
assert!(!checker.is_subtype_of(returns_void, returns_instance));
}
#[test]
fn test_function_top_assignability() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let function_top = interner.callable(CallableShape {
symbol: None,
call_signatures: Vec::new(),
construct_signatures: Vec::new(),
properties: Vec::new(),
..Default::default()
});
let specific_fn = 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,
});
assert!(checker.is_subtype_of(specific_fn, function_top));
assert!(!checker.is_subtype_of(function_top, specific_fn));
}
#[test]
fn test_this_parameter_variance() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let union_this = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let union_this_fn = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: Some(union_this),
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let string_this_fn = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: Some(TypeId::STRING),
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(checker.is_subtype_of(union_this_fn, string_this_fn));
assert!(!checker.is_subtype_of(string_this_fn, union_this_fn));
}
#[test]
#[ignore = "Method bivariance/strict function types not fully implemented"]
fn test_this_parameter_method_property_bivariant() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let method_name = interner.intern_string("m");
let wide_this = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let wide_method = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: Some(wide_this),
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let narrow_method = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: Some(TypeId::STRING),
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let wide_obj = interner.object(vec![PropertyInfo::method(method_name, wide_method)]);
let narrow_obj = interner.object(vec![PropertyInfo::method(method_name, narrow_method)]);
assert!(checker.is_subtype_of(wide_obj, narrow_obj));
assert!(checker.is_subtype_of(narrow_obj, wide_obj));
}
#[test]
fn test_this_parameter_function_property_contravariant() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let func_name = interner.intern_string("f");
let wide_this = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let wide_func = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: Some(wide_this),
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let narrow_func = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: Some(TypeId::STRING),
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let wide_obj = interner.object(vec![PropertyInfo::new(func_name, wide_func)]);
let narrow_obj = interner.object(vec![PropertyInfo::new(func_name, narrow_func)]);
assert!(checker.is_subtype_of(wide_obj, narrow_obj));
assert!(!checker.is_subtype_of(narrow_obj, wide_obj));
}
#[test]
#[ignore = "Method bivariance/strict function types not fully implemented"]
fn test_this_parameter_method_source_bivariant_against_function_property() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let name = interner.intern_string("m");
let wide_this = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let narrow_this = TypeId::STRING;
let narrow_method = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: Some(narrow_this),
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let wide_func = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: Some(wide_this),
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let source = interner.object(vec![PropertyInfo {
name,
type_id: narrow_method,
write_type: narrow_method,
optional: false,
readonly: false,
is_method: true,
visibility: Visibility::Public,
parent_id: None,
}]);
let target = interner.object(vec![PropertyInfo {
name,
type_id: wide_func,
write_type: wide_func,
optional: false,
readonly: false,
is_method: false,
visibility: Visibility::Public,
parent_id: None,
}]);
assert!(checker.is_subtype_of(source, target));
}
#[test]
#[ignore = "Method bivariance/strict function types not fully implemented"]
fn test_this_parameter_function_source_bivariant_against_method_property() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let name = interner.intern_string("m");
let wide_this = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let narrow_this = TypeId::STRING;
let narrow_func = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: Some(narrow_this),
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let wide_method = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: Some(wide_this),
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let source = interner.object(vec![PropertyInfo {
name,
type_id: narrow_func,
write_type: narrow_func,
optional: false,
readonly: false,
is_method: false,
visibility: Visibility::Public,
parent_id: None,
}]);
let target = interner.object(vec![PropertyInfo {
name,
type_id: wide_method,
write_type: wide_method,
optional: false,
readonly: false,
is_method: true,
visibility: Visibility::Public,
parent_id: None,
}]);
assert!(checker.is_subtype_of(source, target));
}
#[test]
fn test_this_type_in_param_covariant() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let func_name = interner.intern_string("compare");
let this_type = interner.intern(TypeData::ThisType);
let this_or_number = interner.union(vec![this_type, TypeId::NUMBER]);
let narrow_fn = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("other")),
type_id: this_type,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let wide_fn = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("other")),
type_id: this_or_number,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let narrow_obj = interner.object(vec![PropertyInfo::new(func_name, narrow_fn)]);
let wide_obj = interner.object(vec![PropertyInfo::new(func_name, wide_fn)]);
assert!(checker.is_subtype_of(narrow_obj, wide_obj));
assert!(!checker.is_subtype_of(wide_obj, narrow_obj));
}
#[test]
fn test_class_like_subtyping_this_param_covariant() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let compare = interner.intern_string("compare");
let id = interner.intern_string("id");
let extra = interner.intern_string("extra");
let this_type = interner.intern(TypeData::ThisType);
let this_or_number = interner.union(vec![this_type, TypeId::NUMBER]);
let base_compare = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("other")),
type_id: this_or_number,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let derived_compare = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("other")),
type_id: this_type,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let base = interner.object(vec![
PropertyInfo::new(id, TypeId::STRING),
PropertyInfo::new(compare, base_compare),
]);
let derived = interner.object(vec![
PropertyInfo::new(id, TypeId::STRING),
PropertyInfo::new(extra, TypeId::NUMBER),
PropertyInfo::new(compare, derived_compare),
]);
assert!(checker.is_subtype_of(derived, base));
assert!(!checker.is_subtype_of(base, derived));
}
#[test]
fn test_function_fixed_to_rest_subtyping() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let source = interner.function(FunctionShape {
type_params: vec![],
params: vec![
ParamInfo {
name: Some(interner.intern_string("name")),
type_id: TypeId::STRING,
optional: false,
rest: false,
},
ParamInfo {
name: Some(interner.intern_string("mixed")),
type_id: TypeId::ANY,
optional: false,
rest: false,
},
ParamInfo {
name: Some(interner.intern_string("arg")),
type_id: TypeId::ANY,
optional: false,
rest: false,
},
],
this_type: None,
return_type: TypeId::ANY,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let any_array = interner.array(TypeId::ANY);
let target = interner.function(FunctionShape {
type_params: vec![],
params: vec![
ParamInfo {
name: Some(interner.intern_string("name")),
type_id: TypeId::STRING,
optional: false,
rest: false,
},
ParamInfo {
name: Some(interner.intern_string("mixed")),
type_id: TypeId::ANY,
optional: false,
rest: false,
},
ParamInfo {
name: Some(interner.intern_string("args")),
type_id: any_array,
optional: false,
rest: true,
},
],
this_type: None,
return_type: TypeId::ANY,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(
checker.is_subtype_of(source, target),
"Function with 3 fixed params should be subtype of function with 2 fixed + rest params"
);
}
#[test]
fn test_function_fixed_to_rest_extra_param_accepts_undefined() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let num_or_undef = interner.union(vec![TypeId::NUMBER, TypeId::UNDEFINED]);
let source = interner.function(FunctionShape {
type_params: vec![],
params: vec![
ParamInfo {
name: Some(interner.intern_string("name")),
type_id: TypeId::STRING,
optional: false,
rest: false,
},
ParamInfo {
name: Some(interner.intern_string("value")),
type_id: num_or_undef,
optional: false,
rest: false,
},
],
this_type: None,
return_type: TypeId::ANY,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let number_array = interner.array(TypeId::NUMBER);
let target = interner.function(FunctionShape {
type_params: vec![],
params: vec![
ParamInfo {
name: Some(interner.intern_string("name")),
type_id: TypeId::STRING,
optional: false,
rest: false,
},
ParamInfo {
name: Some(interner.intern_string("args")),
type_id: number_array,
optional: false,
rest: true,
},
],
this_type: None,
return_type: TypeId::ANY,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(checker.is_subtype_of(source, target));
}
#[test]
fn test_rest_any_three_fixed_to_two_fixed_plus_rest() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
checker.allow_bivariant_rest = true;
let rest_any = interner.array(TypeId::ANY);
let source = interner.function(FunctionShape {
type_params: vec![],
params: vec![
ParamInfo {
name: Some(interner.intern_string("name")),
type_id: TypeId::STRING,
optional: false,
rest: false,
},
ParamInfo {
name: Some(interner.intern_string("mixed")),
type_id: TypeId::ANY,
optional: false,
rest: false,
},
ParamInfo {
name: Some(interner.intern_string("args_0")),
type_id: TypeId::ANY,
optional: false,
rest: false,
},
],
this_type: None,
return_type: TypeId::ANY,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let target = interner.function(FunctionShape {
type_params: vec![],
params: vec![
ParamInfo {
name: Some(interner.intern_string("name")),
type_id: TypeId::STRING,
optional: false,
rest: false,
},
ParamInfo {
name: Some(interner.intern_string("mixed")),
type_id: TypeId::ANY,
optional: false,
rest: false,
},
ParamInfo {
name: Some(interner.intern_string("args")),
type_id: rest_any,
optional: false,
rest: true,
},
],
this_type: None,
return_type: TypeId::ANY,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(checker.is_subtype_of(source, target));
}
#[test]
fn test_function_fixed_to_rest_extra_param_compatible() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let source = interner.function(FunctionShape {
type_params: vec![],
params: vec![
ParamInfo {
name: Some(interner.intern_string("name")),
type_id: TypeId::STRING,
optional: false,
rest: false,
},
ParamInfo {
name: Some(interner.intern_string("value")),
type_id: TypeId::NUMBER,
optional: false,
rest: false,
},
],
this_type: None,
return_type: TypeId::ANY,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let number_array = interner.array(TypeId::NUMBER);
let target = interner.function(FunctionShape {
type_params: vec![],
params: vec![
ParamInfo {
name: Some(interner.intern_string("name")),
type_id: TypeId::STRING,
optional: false,
rest: false,
},
ParamInfo {
name: Some(interner.intern_string("args")),
type_id: number_array,
optional: false,
rest: true,
},
],
this_type: None,
return_type: TypeId::ANY,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(checker.is_subtype_of(source, target));
}
#[test]
fn test_function_rest_tuple_to_rest_array_subtyping() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let tuple_one_any = interner.tuple(vec![TupleElement {
type_id: TypeId::ANY,
name: None,
optional: false,
rest: false,
}]);
let source = interner.function(FunctionShape {
type_params: vec![],
params: vec![
ParamInfo {
name: Some(interner.intern_string("name")),
type_id: TypeId::STRING,
optional: false,
rest: false,
},
ParamInfo {
name: Some(interner.intern_string("mixed")),
type_id: TypeId::ANY,
optional: false,
rest: false,
},
ParamInfo {
name: Some(interner.intern_string("args")),
type_id: tuple_one_any,
optional: false,
rest: true,
},
],
this_type: None,
return_type: TypeId::ANY,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let any_array = interner.array(TypeId::ANY);
let target = interner.function(FunctionShape {
type_params: vec![],
params: vec![
ParamInfo {
name: Some(interner.intern_string("name")),
type_id: TypeId::STRING,
optional: false,
rest: false,
},
ParamInfo {
name: Some(interner.intern_string("mixed")),
type_id: TypeId::ANY,
optional: false,
rest: false,
},
ParamInfo {
name: Some(interner.intern_string("args")),
type_id: any_array,
optional: false,
rest: true,
},
],
this_type: None,
return_type: TypeId::ANY,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(
checker.is_subtype_of(source, target),
"Function with rest tuple [any] should be subtype of function with rest array any[]"
);
}
#[test]
fn test_keyof_intersection_contravariant() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
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 intersection = interner.intersection(vec![obj_a, obj_b]);
let keyof_a = interner.intern(TypeData::KeyOf(obj_a));
let keyof_intersection = interner.intern(TypeData::KeyOf(intersection));
assert!(checker.is_subtype_of(keyof_a, keyof_intersection));
assert!(!checker.is_subtype_of(keyof_intersection, keyof_a));
}
#[test]
fn test_keyof_contravariant_object_subtyping() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let obj_a = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
TypeId::NUMBER,
)]);
let obj_ab = interner.object(vec![
PropertyInfo::new(interner.intern_string("a"), TypeId::NUMBER),
PropertyInfo::new(interner.intern_string("b"), TypeId::STRING),
]);
assert!(checker.is_subtype_of(obj_ab, obj_a));
let keyof_a = interner.intern(TypeData::KeyOf(obj_a));
let keyof_ab = interner.intern(TypeData::KeyOf(obj_ab));
assert!(checker.is_subtype_of(keyof_a, keyof_ab));
assert!(!checker.is_subtype_of(keyof_ab, keyof_a));
}
#[test]
fn test_keyof_intersection_union_of_keys() {
use crate::evaluate_keyof;
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 intersection = interner.intersection(vec![obj_a, obj_b]);
let result = evaluate_keyof(&interner, intersection);
let expected = interner.union(vec![
interner.literal_string("a"),
interner.literal_string("b"),
]);
assert_eq!(result, expected);
}
#[test]
fn test_keyof_union_disjoint_object_keys_is_never() {
use crate::evaluate_keyof;
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 union = interner.union(vec![obj_a, obj_b]);
let result = evaluate_keyof(&interner, union);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_keyof_union_index_signature_contravariant() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let string_index = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: Vec::new(),
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: TypeId::NUMBER,
readonly: false,
}),
number_index: None,
});
let number_index = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: Vec::new(),
string_index: None,
number_index: Some(IndexSignature {
key_type: TypeId::NUMBER,
value_type: TypeId::NUMBER,
readonly: false,
}),
});
let union = interner.union(vec![string_index, number_index]);
let keyof_union = interner.intern(TypeData::KeyOf(union));
assert!(checker.is_subtype_of(keyof_union, TypeId::NUMBER));
assert!(!checker.is_subtype_of(keyof_union, TypeId::STRING));
}
#[test]
fn test_keyof_union_string_index_and_literal_narrows() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let string_index = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: Vec::new(),
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: TypeId::NUMBER,
readonly: false,
}),
number_index: None,
});
let key_a = interner.intern_string("a");
let obj_a = interner.object(vec![PropertyInfo::new(key_a, TypeId::NUMBER)]);
let union = interner.union(vec![string_index, obj_a]);
let keyof_union = interner.intern(TypeData::KeyOf(union));
let key_a_literal = interner.literal_string("a");
assert!(checker.is_subtype_of(keyof_union, key_a_literal));
assert!(checker.is_subtype_of(keyof_union, TypeId::STRING));
assert!(!checker.is_subtype_of(keyof_union, TypeId::NUMBER));
assert!(checker.is_subtype_of(key_a_literal, keyof_union));
assert!(!checker.is_subtype_of(TypeId::STRING, keyof_union));
}
#[test]
fn test_keyof_union_overlapping_keys_is_common() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let key_a = interner.intern_string("a");
let key_b = interner.intern_string("b");
let key_c = interner.intern_string("c");
let obj_ab = interner.object(vec![
PropertyInfo::new(key_a, TypeId::NUMBER),
PropertyInfo::new(key_b, TypeId::STRING),
]);
let obj_ac = interner.object(vec![
PropertyInfo::new(key_a, TypeId::NUMBER),
PropertyInfo::new(key_c, TypeId::BOOLEAN),
]);
let union = interner.union(vec![obj_ab, obj_ac]);
let keyof_union = interner.intern(TypeData::KeyOf(union));
let key_a_literal = interner.literal_string("a");
let key_b_literal = interner.literal_string("b");
let key_c_literal = interner.literal_string("c");
assert!(checker.is_subtype_of(keyof_union, key_a_literal));
assert!(!checker.is_subtype_of(keyof_union, key_b_literal));
assert!(!checker.is_subtype_of(keyof_union, key_c_literal));
assert!(checker.is_subtype_of(key_a_literal, keyof_union));
}
#[test]
fn test_keyof_union_optional_key_is_common() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let key_a = interner.intern_string("a");
let key_b = interner.intern_string("b");
let obj_optional_a = interner.object(vec![PropertyInfo::opt(key_a, TypeId::NUMBER)]);
let obj_ab = interner.object(vec![
PropertyInfo::new(key_a, TypeId::NUMBER),
PropertyInfo::new(key_b, TypeId::STRING),
]);
let union = interner.union(vec![obj_optional_a, obj_ab]);
let keyof_union = interner.intern(TypeData::KeyOf(union));
let key_a_literal = interner.literal_string("a");
let key_b_literal = interner.literal_string("b");
assert!(checker.is_subtype_of(keyof_union, key_a_literal));
assert!(!checker.is_subtype_of(keyof_union, key_b_literal));
assert!(checker.is_subtype_of(key_a_literal, keyof_union));
}
#[test]
fn test_keyof_deferred_not_subtype_of_string() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let type_param = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: interner.intern_string("T"),
constraint: None,
default: None,
is_const: false,
}));
let keyof_param = interner.intern(TypeData::KeyOf(type_param));
assert!(!checker.is_subtype_of(keyof_param, TypeId::STRING));
}
#[test]
fn test_keyof_deferred_subtype_of_string_number_symbol_union() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let type_param = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: interner.intern_string("T"),
constraint: None,
default: None,
is_const: false,
}));
let keyof_param = interner.intern(TypeData::KeyOf(type_param));
let key_union = interner.union(vec![TypeId::STRING, TypeId::NUMBER, TypeId::SYMBOL]);
assert!(checker.is_subtype_of(keyof_param, key_union));
}
#[test]
fn test_keyof_deferred_not_subtype_of_string_number_union() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let type_param = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: interner.intern_string("T"),
constraint: None,
default: None,
is_const: false,
}));
let keyof_param = interner.intern(TypeData::KeyOf(type_param));
let key_union = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert!(!checker.is_subtype_of(keyof_param, key_union));
}
#[test]
fn test_keyof_any_subtyping_union() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let keyof_any = interner.intern(TypeData::KeyOf(TypeId::ANY));
let key_union = interner.union(vec![TypeId::STRING, TypeId::NUMBER, TypeId::SYMBOL]);
let string_number_union = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert!(checker.is_subtype_of(keyof_any, key_union));
assert!(!checker.is_subtype_of(keyof_any, string_number_union));
}
#[test]
fn test_intersection_reduction_disjoint_discriminant_subtyping() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
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 intersection = interner.intersection(vec![obj_a, obj_b]);
assert!(checker.is_subtype_of(intersection, TypeId::NEVER));
assert!(checker.is_subtype_of(intersection, TypeId::STRING));
}
#[test]
fn test_intersection_reduction_disjoint_intrinsics() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let intersection = interner.intersection(vec![TypeId::STRING, TypeId::NUMBER]);
assert!(checker.is_subtype_of(intersection, TypeId::NEVER));
}
#[test]
fn test_mapped_type_over_number_keys_subtyping() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let constraint = interner.intern(TypeData::KeyOf(TypeId::NUMBER));
let mapped = interner.mapped(MappedType {
type_param: TypeParamInfo {
name: interner.intern_string("K"),
constraint: None,
default: None,
is_const: false,
},
constraint,
name_type: None,
template: TypeId::BOOLEAN,
readonly_modifier: None,
optional_modifier: None,
});
let to_fixed = interner.intern_string("toFixed");
let expected = interner.object(vec![PropertyInfo::new(to_fixed, TypeId::BOOLEAN)]);
let mismatch = interner.object(vec![PropertyInfo::new(to_fixed, TypeId::NUMBER)]);
let to_upper = interner.intern_string("toUpperCase");
let wrong_key = interner.object(vec![PropertyInfo::new(to_upper, TypeId::BOOLEAN)]);
assert!(checker.is_subtype_of(mapped, expected));
assert!(!checker.is_subtype_of(mapped, mismatch));
assert!(!checker.is_subtype_of(mapped, wrong_key));
assert!(!checker.is_subtype_of(expected, mapped));
}
#[test]
fn test_mapped_type_over_number_keys_optional_readonly_add_subtyping() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let constraint = interner.intern(TypeData::KeyOf(TypeId::NUMBER));
let mapped = interner.mapped(MappedType {
type_param: TypeParamInfo {
name: interner.intern_string("K"),
constraint: None,
default: None,
is_const: false,
},
constraint,
name_type: None,
template: TypeId::BOOLEAN,
readonly_modifier: Some(MappedModifier::Add),
optional_modifier: Some(MappedModifier::Add),
});
let to_fixed = interner.intern_string("toFixed");
let optional_readonly = interner.object(vec![PropertyInfo {
name: to_fixed,
type_id: TypeId::BOOLEAN,
write_type: TypeId::BOOLEAN,
optional: true,
readonly: true,
is_method: false,
visibility: Visibility::Public,
parent_id: None,
}]);
let required_readonly =
interner.object(vec![PropertyInfo::readonly(to_fixed, TypeId::BOOLEAN)]);
let optional_mutable = interner.object(vec![PropertyInfo::opt(to_fixed, TypeId::BOOLEAN)]);
assert!(checker.is_subtype_of(mapped, optional_readonly));
assert!(!checker.is_subtype_of(mapped, required_readonly));
assert!(checker.is_subtype_of(mapped, optional_mutable));
}
#[test]
fn test_mapped_type_over_string_keys_subtyping() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let constraint = interner.intern(TypeData::KeyOf(TypeId::STRING));
let mapped = interner.mapped(MappedType {
type_param: TypeParamInfo {
name: interner.intern_string("K"),
constraint: None,
default: None,
is_const: false,
},
constraint,
name_type: None,
template: TypeId::BOOLEAN,
readonly_modifier: None,
optional_modifier: None,
});
let to_upper = interner.intern_string("toUpperCase");
let expected = interner.object(vec![PropertyInfo::new(to_upper, TypeId::BOOLEAN)]);
let mismatch = interner.object(vec![PropertyInfo::new(to_upper, TypeId::STRING)]);
assert!(checker.is_subtype_of(mapped, expected));
assert!(!checker.is_subtype_of(mapped, mismatch));
assert!(!checker.is_subtype_of(expected, mapped));
}
#[test]
fn test_mapped_type_over_string_keys_number_index_subtyping() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let constraint = interner.intern(TypeData::KeyOf(TypeId::STRING));
let mapped = interner.mapped(MappedType {
type_param: TypeParamInfo {
name: interner.intern_string("K"),
constraint: None,
default: None,
is_const: false,
},
constraint,
name_type: None,
template: TypeId::BOOLEAN,
readonly_modifier: None,
optional_modifier: None,
});
let number_index = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: Vec::new(),
string_index: None,
number_index: Some(IndexSignature {
key_type: TypeId::NUMBER,
value_type: TypeId::BOOLEAN,
readonly: false,
}),
});
let mismatch = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: Vec::new(),
string_index: None,
number_index: Some(IndexSignature {
key_type: TypeId::NUMBER,
value_type: TypeId::STRING,
readonly: false,
}),
});
assert!(checker.is_subtype_of(mapped, number_index));
assert!(!checker.is_subtype_of(mapped, mismatch));
}
#[test]
fn test_mapped_type_over_string_keys_key_remap_omit_length() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let constraint = interner.intern(TypeData::KeyOf(TypeId::STRING));
let key_param = TypeParamInfo {
name: interner.intern_string("K"),
constraint: None,
default: None,
is_const: false,
};
let key_param_id = interner.intern(TypeData::TypeParameter(key_param.clone()));
let length_key = interner.literal_string("length");
let name_type = interner.conditional(ConditionalType {
check_type: key_param_id,
extends_type: length_key,
true_type: TypeId::NEVER,
false_type: key_param_id,
is_distributive: true,
});
let mapped = interner.mapped(MappedType {
type_param: key_param,
constraint,
name_type: Some(name_type),
template: TypeId::BOOLEAN,
readonly_modifier: None,
optional_modifier: None,
});
let to_upper = interner.intern_string("toUpperCase");
let expected = interner.object(vec![PropertyInfo::new(to_upper, TypeId::BOOLEAN)]);
let length = interner.intern_string("length");
let requires_length = interner.object(vec![PropertyInfo::new(length, TypeId::BOOLEAN)]);
assert!(checker.is_subtype_of(mapped, expected));
assert!(!checker.is_subtype_of(mapped, requires_length));
}
#[test]
fn test_mapped_type_over_boolean_keys_subtyping() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let constraint = interner.intern(TypeData::KeyOf(TypeId::BOOLEAN));
let mapped = interner.mapped(MappedType {
type_param: TypeParamInfo {
name: interner.intern_string("K"),
constraint: None,
default: None,
is_const: false,
},
constraint,
name_type: None,
template: TypeId::NUMBER,
readonly_modifier: None,
optional_modifier: None,
});
let value_of = interner.intern_string("valueOf");
let expected = interner.object(vec![PropertyInfo::new(value_of, TypeId::NUMBER)]);
let mismatch = interner.object(vec![PropertyInfo::new(value_of, TypeId::STRING)]);
assert!(checker.is_subtype_of(mapped, expected));
assert!(!checker.is_subtype_of(mapped, mismatch));
assert!(!checker.is_subtype_of(expected, mapped));
}
#[test]
fn test_mapped_type_over_symbol_keys_subtyping() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let constraint = interner.intern(TypeData::KeyOf(TypeId::SYMBOL));
let mapped = interner.mapped(MappedType {
type_param: TypeParamInfo {
name: interner.intern_string("K"),
constraint: None,
default: None,
is_const: false,
},
constraint,
name_type: None,
template: TypeId::NUMBER,
readonly_modifier: None,
optional_modifier: None,
});
let description = interner.intern_string("description");
let expected = interner.object(vec![PropertyInfo::new(description, TypeId::NUMBER)]);
let mismatch = interner.object(vec![PropertyInfo::new(description, TypeId::STRING)]);
assert!(checker.is_subtype_of(mapped, expected));
assert!(!checker.is_subtype_of(mapped, mismatch));
assert!(!checker.is_subtype_of(expected, mapped));
}
#[test]
fn test_mapped_type_over_bigint_keys_subtyping() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let constraint = interner.intern(TypeData::KeyOf(TypeId::BIGINT));
let mapped = interner.mapped(MappedType {
type_param: TypeParamInfo {
name: interner.intern_string("K"),
constraint: None,
default: None,
is_const: false,
},
constraint,
name_type: None,
template: TypeId::NUMBER,
readonly_modifier: None,
optional_modifier: None,
});
let to_string = interner.intern_string("toString");
let expected = interner.object(vec![PropertyInfo::new(to_string, TypeId::NUMBER)]);
let mismatch = interner.object(vec![PropertyInfo::new(to_string, TypeId::STRING)]);
let to_upper = interner.intern_string("toUpperCase");
let wrong_key = interner.object(vec![PropertyInfo::new(to_upper, TypeId::NUMBER)]);
assert!(checker.is_subtype_of(mapped, expected));
assert!(!checker.is_subtype_of(mapped, mismatch));
assert!(!checker.is_subtype_of(mapped, wrong_key));
assert!(!checker.is_subtype_of(expected, mapped));
}
#[test]
fn test_mapped_type_optional_modifier_add_subtyping() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let key_a = interner.literal_string("a");
let key_b = interner.literal_string("b");
let keys = interner.union(vec![key_a, key_b]);
let mapped = interner.mapped(MappedType {
type_param: TypeParamInfo {
name: interner.intern_string("K"),
constraint: None,
default: None,
is_const: false,
},
constraint: keys,
name_type: None,
template: TypeId::NUMBER,
readonly_modifier: None,
optional_modifier: Some(MappedModifier::Add),
});
let name_a = interner.intern_string("a");
let name_b = interner.intern_string("b");
let optional_target = interner.object(vec![
PropertyInfo::opt(name_a, TypeId::NUMBER),
PropertyInfo::opt(name_b, TypeId::NUMBER),
]);
let required_target = interner.object(vec![
PropertyInfo::new(name_a, TypeId::NUMBER),
PropertyInfo::new(name_b, TypeId::NUMBER),
]);
assert!(checker.is_subtype_of(mapped, optional_target));
assert!(!checker.is_subtype_of(mapped, required_target));
}
#[test]
fn test_mapped_type_readonly_modifier_add_subtyping() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let key_a = interner.literal_string("a");
let key_b = interner.literal_string("b");
let keys = interner.union(vec![key_a, key_b]);
let mapped = interner.mapped(MappedType {
type_param: TypeParamInfo {
name: interner.intern_string("K"),
constraint: None,
default: None,
is_const: false,
},
constraint: keys,
name_type: None,
template: TypeId::NUMBER,
readonly_modifier: Some(MappedModifier::Add),
optional_modifier: None,
});
let name_a = interner.intern_string("a");
let name_b = interner.intern_string("b");
let readonly_target = interner.object(vec![
PropertyInfo::readonly(name_a, TypeId::NUMBER),
PropertyInfo::readonly(name_b, TypeId::NUMBER),
]);
let mutable_target = interner.object(vec![
PropertyInfo::new(name_a, TypeId::NUMBER),
PropertyInfo::new(name_b, TypeId::NUMBER),
]);
assert!(checker.is_subtype_of(mapped, readonly_target));
assert!(checker.is_subtype_of(mapped, mutable_target));
}
#[test]
fn test_mapped_type_optional_readonly_add_subtyping() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let key_a = interner.literal_string("a");
let key_b = interner.literal_string("b");
let keys = interner.union(vec![key_a, key_b]);
let mapped = interner.mapped(MappedType {
type_param: TypeParamInfo {
name: interner.intern_string("K"),
constraint: None,
default: None,
is_const: false,
},
constraint: keys,
name_type: None,
template: TypeId::NUMBER,
readonly_modifier: Some(MappedModifier::Add),
optional_modifier: Some(MappedModifier::Add),
});
let name_a = interner.intern_string("a");
let name_b = interner.intern_string("b");
let optional_readonly_target = interner.object(vec![
PropertyInfo {
name: name_a,
type_id: TypeId::NUMBER,
write_type: TypeId::NUMBER,
optional: true,
readonly: true,
is_method: false,
visibility: Visibility::Public,
parent_id: None,
},
PropertyInfo {
name: name_b,
type_id: TypeId::NUMBER,
write_type: TypeId::NUMBER,
optional: true,
readonly: true,
is_method: false,
visibility: Visibility::Public,
parent_id: None,
},
]);
let mutable_required_target = interner.object(vec![
PropertyInfo::new(name_a, TypeId::NUMBER),
PropertyInfo::new(name_b, TypeId::NUMBER),
]);
assert!(checker.is_subtype_of(mapped, optional_readonly_target));
assert!(!checker.is_subtype_of(mapped, mutable_required_target));
}
#[test]
fn test_mapped_type_optional_readonly_remove_subtyping() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let key_a = interner.literal_string("a");
let keys = interner.union(vec![key_a]);
let mapped = interner.mapped(MappedType {
type_param: TypeParamInfo {
name: interner.intern_string("K"),
constraint: None,
default: None,
is_const: false,
},
constraint: keys,
name_type: None,
template: TypeId::NUMBER,
readonly_modifier: Some(MappedModifier::Remove),
optional_modifier: Some(MappedModifier::Remove),
});
let name_a = interner.intern_string("a");
let mutable_required_target = interner.object(vec![PropertyInfo::new(name_a, TypeId::NUMBER)]);
let readonly_optional_target = interner.object(vec![PropertyInfo {
name: name_a,
type_id: TypeId::NUMBER,
write_type: TypeId::NUMBER,
optional: true,
readonly: true,
is_method: false,
visibility: Visibility::Public,
parent_id: None,
}]);
assert!(checker.is_subtype_of(mapped, mutable_required_target));
assert!(checker.is_subtype_of(mapped, readonly_optional_target));
assert!(!checker.is_subtype_of(readonly_optional_target, mapped));
}
#[test]
fn test_mapped_type_optional_modifier_remove_subtyping() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let key_a = interner.literal_string("a");
let keys = interner.union(vec![key_a]);
let mapped = interner.mapped(MappedType {
type_param: TypeParamInfo {
name: interner.intern_string("K"),
constraint: None,
default: None,
is_const: false,
},
constraint: keys,
name_type: None,
template: TypeId::NUMBER,
readonly_modifier: None,
optional_modifier: Some(MappedModifier::Remove),
});
let name_a = interner.intern_string("a");
let required_target = interner.object(vec![PropertyInfo::new(name_a, TypeId::NUMBER)]);
let optional_target = interner.object(vec![PropertyInfo::opt(name_a, TypeId::NUMBER)]);
assert!(checker.is_subtype_of(mapped, required_target));
assert!(checker.is_subtype_of(mapped, optional_target));
assert!(!checker.is_subtype_of(optional_target, mapped));
}
#[test]
fn test_mapped_type_optional_remove_from_optional_keyof() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let key_a = interner.intern_string("a");
let source_obj = interner.object(vec![PropertyInfo::opt(key_a, TypeId::NUMBER)]);
let keys = interner.intern(TypeData::KeyOf(source_obj));
let mapped = interner.mapped(MappedType {
type_param: TypeParamInfo {
name: interner.intern_string("K"),
constraint: None,
default: None,
is_const: false,
},
constraint: keys,
name_type: None,
template: TypeId::STRING,
readonly_modifier: None,
optional_modifier: Some(MappedModifier::Remove),
});
let required_target = interner.object(vec![PropertyInfo::new(key_a, TypeId::STRING)]);
let optional_target = interner.object(vec![PropertyInfo::opt(key_a, TypeId::STRING)]);
assert!(checker.is_subtype_of(mapped, required_target));
assert!(checker.is_subtype_of(mapped, optional_target));
assert!(!checker.is_subtype_of(optional_target, mapped));
}
#[test]
fn test_mapped_type_readonly_remove_from_readonly_keyof() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let key_a = interner.intern_string("a");
let source_obj = interner.object(vec![PropertyInfo::readonly(key_a, TypeId::STRING)]);
let keys = interner.intern(TypeData::KeyOf(source_obj));
let mapped = interner.mapped(MappedType {
type_param: TypeParamInfo {
name: interner.intern_string("K"),
constraint: None,
default: None,
is_const: false,
},
constraint: keys,
name_type: None,
template: TypeId::NUMBER,
readonly_modifier: Some(MappedModifier::Remove),
optional_modifier: None,
});
let mutable_target = interner.object(vec![PropertyInfo::new(key_a, TypeId::NUMBER)]);
let readonly_target = interner.object(vec![PropertyInfo::readonly(key_a, TypeId::NUMBER)]);
assert!(checker.is_subtype_of(mapped, mutable_target));
assert!(checker.is_subtype_of(mapped, readonly_target));
assert!(checker.is_subtype_of(readonly_target, mapped));
}
#[test]
fn test_mapped_type_readonly_modifier_remove_subtyping() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let key_a = interner.literal_string("a");
let keys = interner.union(vec![key_a]);
let mapped = interner.mapped(MappedType {
type_param: TypeParamInfo {
name: interner.intern_string("K"),
constraint: None,
default: None,
is_const: false,
},
constraint: keys,
name_type: None,
template: TypeId::NUMBER,
readonly_modifier: Some(MappedModifier::Remove),
optional_modifier: None,
});
let name_a = interner.intern_string("a");
let mutable_target = interner.object(vec![PropertyInfo::new(name_a, TypeId::NUMBER)]);
let readonly_target = interner.object(vec![PropertyInfo::readonly(name_a, TypeId::NUMBER)]);
assert!(checker.is_subtype_of(mapped, mutable_target));
assert!(checker.is_subtype_of(mapped, readonly_target));
assert!(checker.is_subtype_of(readonly_target, mapped));
}
#[test]
fn test_mapped_type_key_remap_subtyping() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let prop_a = PropertyInfo::new(interner.intern_string("a"), TypeId::STRING);
let prop_b = PropertyInfo::new(interner.intern_string("b"), TypeId::NUMBER);
let obj = interner.object(vec![prop_a.clone(), prop_b.clone()]);
let key_a = interner.literal_string("a");
let key_b = interner.literal_string("b");
let keys = interner.union(vec![key_a, key_b]);
let key_param = TypeParamInfo {
name: interner.intern_string("K"),
constraint: Some(keys),
default: None,
is_const: false,
};
let key_param_id = interner.intern(TypeData::TypeParameter(key_param.clone()));
let name_type = interner.conditional(ConditionalType {
check_type: key_param_id,
extends_type: key_a,
true_type: TypeId::NEVER,
false_type: key_param_id,
is_distributive: true,
});
let template = interner.intern(TypeData::IndexAccess(obj, key_param_id));
let mapped = interner.mapped(MappedType {
type_param: key_param,
constraint: keys,
name_type: Some(name_type),
template,
readonly_modifier: None,
optional_modifier: None,
});
let expected = interner.object(vec![PropertyInfo::new(prop_b.name, TypeId::NUMBER)]);
let requires_a = interner.object(vec![PropertyInfo::new(prop_a.name, TypeId::STRING)]);
assert!(checker.is_subtype_of(mapped, expected));
assert!(!checker.is_subtype_of(mapped, requires_a));
}
#[test]
fn test_mapped_type_key_remap_optional_add_subtyping() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let prop_a = PropertyInfo::new(interner.intern_string("a"), TypeId::STRING);
let prop_b = PropertyInfo::new(interner.intern_string("b"), TypeId::NUMBER);
let obj = interner.object(vec![prop_a, prop_b.clone()]);
let key_a = interner.literal_string("a");
let key_b = interner.literal_string("b");
let keys = interner.union(vec![key_a, key_b]);
let key_param = TypeParamInfo {
name: interner.intern_string("K"),
constraint: Some(keys),
default: None,
is_const: false,
};
let key_param_id = interner.intern(TypeData::TypeParameter(key_param.clone()));
let name_type = interner.conditional(ConditionalType {
check_type: key_param_id,
extends_type: key_a,
true_type: TypeId::NEVER,
false_type: key_param_id,
is_distributive: true,
});
let template = interner.intern(TypeData::IndexAccess(obj, key_param_id));
let mapped = interner.mapped(MappedType {
type_param: key_param,
constraint: keys,
name_type: Some(name_type),
template,
readonly_modifier: None,
optional_modifier: Some(MappedModifier::Add),
});
let optional_b = interner.object(vec![PropertyInfo::opt(prop_b.name, TypeId::NUMBER)]);
let required_b = interner.object(vec![PropertyInfo::new(prop_b.name, TypeId::NUMBER)]);
assert!(checker.is_subtype_of(mapped, optional_b));
assert!(!checker.is_subtype_of(mapped, required_b));
}
#[test]
fn test_mapped_type_key_remap_optional_remove_subtyping() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let prop_a = PropertyInfo::new(interner.intern_string("a"), TypeId::STRING);
let prop_b = PropertyInfo::opt(interner.intern_string("b"), TypeId::NUMBER);
let obj = interner.object(vec![prop_a, prop_b.clone()]);
let key_a = interner.literal_string("a");
let key_b = interner.literal_string("b");
let keys = interner.union(vec![key_a, key_b]);
let key_param = TypeParamInfo {
name: interner.intern_string("K"),
constraint: Some(keys),
default: None,
is_const: false,
};
let key_param_id = interner.intern(TypeData::TypeParameter(key_param.clone()));
let name_type = interner.conditional(ConditionalType {
check_type: key_param_id,
extends_type: key_a,
true_type: TypeId::NEVER,
false_type: key_param_id,
is_distributive: true,
});
let template = interner.intern(TypeData::IndexAccess(obj, key_param_id));
let mapped = interner.mapped(MappedType {
type_param: key_param,
constraint: keys,
name_type: Some(name_type),
template,
readonly_modifier: None,
optional_modifier: Some(MappedModifier::Remove),
});
let required_b = interner.object(vec![PropertyInfo::new(prop_b.name, TypeId::NUMBER)]);
let optional_b = interner.object(vec![PropertyInfo::opt(prop_b.name, TypeId::NUMBER)]);
assert!(!checker.is_subtype_of(mapped, required_b));
assert!(checker.is_subtype_of(mapped, optional_b));
}
#[test]
fn test_mapped_type_key_remap_optional_readonly_add_subtyping() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let prop_a = PropertyInfo::new(interner.intern_string("a"), TypeId::STRING);
let prop_b = PropertyInfo::new(interner.intern_string("b"), TypeId::NUMBER);
let obj = interner.object(vec![prop_a, prop_b.clone()]);
let key_a = interner.literal_string("a");
let key_b = interner.literal_string("b");
let keys = interner.union(vec![key_a, key_b]);
let key_param = TypeParamInfo {
name: interner.intern_string("K"),
constraint: Some(keys),
default: None,
is_const: false,
};
let key_param_id = interner.intern(TypeData::TypeParameter(key_param.clone()));
let name_type = interner.conditional(ConditionalType {
check_type: key_param_id,
extends_type: key_a,
true_type: TypeId::NEVER,
false_type: key_param_id,
is_distributive: true,
});
let template = interner.intern(TypeData::IndexAccess(obj, key_param_id));
let mapped = interner.mapped(MappedType {
type_param: key_param,
constraint: keys,
name_type: Some(name_type),
template,
readonly_modifier: Some(MappedModifier::Add),
optional_modifier: Some(MappedModifier::Add),
});
let optional_readonly_b = interner.object(vec![PropertyInfo {
name: prop_b.name,
type_id: TypeId::NUMBER,
write_type: TypeId::NUMBER,
optional: true,
readonly: true,
is_method: false,
visibility: Visibility::Public,
parent_id: None,
}]);
let required_readonly_b =
interner.object(vec![PropertyInfo::readonly(prop_b.name, TypeId::NUMBER)]);
let optional_mutable_b = interner.object(vec![PropertyInfo::opt(prop_b.name, TypeId::NUMBER)]);
assert!(checker.is_subtype_of(mapped, optional_readonly_b));
assert!(!checker.is_subtype_of(mapped, required_readonly_b));
assert!(checker.is_subtype_of(mapped, optional_mutable_b));
}
#[test]
fn test_mapped_type_key_remap_optional_readonly_remove_subtyping() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let prop_a = PropertyInfo::new(interner.intern_string("a"), TypeId::STRING);
let prop_b = PropertyInfo {
name: interner.intern_string("b"),
type_id: TypeId::NUMBER,
write_type: TypeId::NUMBER,
optional: true,
readonly: true,
is_method: false,
visibility: Visibility::Public,
parent_id: None,
};
let obj = interner.object(vec![prop_a, prop_b.clone()]);
let key_a = interner.literal_string("a");
let key_b = interner.literal_string("b");
let keys = interner.union(vec![key_a, key_b]);
let key_param = TypeParamInfo {
name: interner.intern_string("K"),
constraint: Some(keys),
default: None,
is_const: false,
};
let key_param_id = interner.intern(TypeData::TypeParameter(key_param.clone()));
let name_type = interner.conditional(ConditionalType {
check_type: key_param_id,
extends_type: key_a,
true_type: TypeId::NEVER,
false_type: key_param_id,
is_distributive: true,
});
let template = interner.intern(TypeData::IndexAccess(obj, key_param_id));
let mapped = interner.mapped(MappedType {
type_param: key_param,
constraint: keys,
name_type: Some(name_type),
template,
readonly_modifier: Some(MappedModifier::Remove),
optional_modifier: Some(MappedModifier::Remove),
});
let required_mutable_b = interner.object(vec![PropertyInfo::new(prop_b.name, TypeId::NUMBER)]);
let number_or_undefined = interner.union(vec![TypeId::NUMBER, TypeId::UNDEFINED]);
let required_mutable_b_with_undef =
interner.object(vec![PropertyInfo::new(prop_b.name, number_or_undefined)]);
let optional_mutable_b = interner.object(vec![PropertyInfo::opt(prop_b.name, TypeId::NUMBER)]);
assert!(!checker.is_subtype_of(mapped, required_mutable_b));
assert!(checker.is_subtype_of(mapped, required_mutable_b_with_undef));
assert!(checker.is_subtype_of(mapped, optional_mutable_b));
assert!(!checker.is_subtype_of(optional_mutable_b, mapped));
}
#[test]
fn test_mapped_type_key_remap_readonly_add_subtyping() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let prop_a = PropertyInfo::new(interner.intern_string("a"), TypeId::STRING);
let prop_b = PropertyInfo::new(interner.intern_string("b"), TypeId::NUMBER);
let obj = interner.object(vec![prop_a, prop_b.clone()]);
let key_a = interner.literal_string("a");
let key_b = interner.literal_string("b");
let keys = interner.union(vec![key_a, key_b]);
let key_param = TypeParamInfo {
name: interner.intern_string("K"),
constraint: Some(keys),
default: None,
is_const: false,
};
let key_param_id = interner.intern(TypeData::TypeParameter(key_param.clone()));
let name_type = interner.conditional(ConditionalType {
check_type: key_param_id,
extends_type: key_a,
true_type: TypeId::NEVER,
false_type: key_param_id,
is_distributive: true,
});
let template = interner.intern(TypeData::IndexAccess(obj, key_param_id));
let mapped = interner.mapped(MappedType {
type_param: key_param,
constraint: keys,
name_type: Some(name_type),
template,
readonly_modifier: Some(MappedModifier::Add),
optional_modifier: None,
});
let readonly_b = interner.object(vec![PropertyInfo::readonly(prop_b.name, TypeId::NUMBER)]);
let mutable_b = interner.object(vec![PropertyInfo::new(prop_b.name, TypeId::NUMBER)]);
assert!(checker.is_subtype_of(mapped, readonly_b));
assert!(checker.is_subtype_of(mapped, mutable_b));
}
#[test]
fn test_mapped_type_key_remap_readonly_remove_subtyping() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let prop_a = PropertyInfo::new(interner.intern_string("a"), TypeId::STRING);
let prop_b = PropertyInfo::readonly(interner.intern_string("b"), TypeId::NUMBER);
let obj = interner.object(vec![prop_a, prop_b.clone()]);
let key_a = interner.literal_string("a");
let key_b = interner.literal_string("b");
let keys = interner.union(vec![key_a, key_b]);
let key_param = TypeParamInfo {
name: interner.intern_string("K"),
constraint: Some(keys),
default: None,
is_const: false,
};
let key_param_id = interner.intern(TypeData::TypeParameter(key_param.clone()));
let name_type = interner.conditional(ConditionalType {
check_type: key_param_id,
extends_type: key_a,
true_type: TypeId::NEVER,
false_type: key_param_id,
is_distributive: true,
});
let template = interner.intern(TypeData::IndexAccess(obj, key_param_id));
let mapped = interner.mapped(MappedType {
type_param: key_param,
constraint: keys,
name_type: Some(name_type),
template,
readonly_modifier: Some(MappedModifier::Remove),
optional_modifier: None,
});
let mutable_b = interner.object(vec![PropertyInfo::new(prop_b.name, TypeId::NUMBER)]);
let readonly_b = interner.object(vec![PropertyInfo::readonly(prop_b.name, TypeId::NUMBER)]);
assert!(checker.is_subtype_of(mapped, mutable_b));
assert!(checker.is_subtype_of(mapped, readonly_b));
assert!(checker.is_subtype_of(readonly_b, mapped));
}
#[test]
fn test_mapped_type_key_remap_all_never_empty_object() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let key_a = interner.literal_string("a");
let key_b = interner.literal_string("b");
let keys = interner.union(vec![key_a, key_b]);
let key_param = TypeParamInfo {
name: interner.intern_string("K"),
constraint: Some(keys),
default: None,
is_const: false,
};
let key_param_id = interner.intern(TypeData::TypeParameter(key_param.clone()));
let name_type = interner.conditional(ConditionalType {
check_type: key_param_id,
extends_type: TypeId::STRING,
true_type: TypeId::NEVER,
false_type: key_param_id,
is_distributive: true,
});
let mapped = interner.mapped(MappedType {
type_param: key_param,
constraint: keys,
name_type: Some(name_type),
template: TypeId::BOOLEAN,
readonly_modifier: None,
optional_modifier: None,
});
let empty_object = interner.object(Vec::new());
assert!(checker.is_subtype_of(mapped, empty_object));
assert!(checker.is_subtype_of(empty_object, mapped));
}
#[test]
fn test_generic_function_constraint_directionality() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
checker.strict_function_types = true;
let t = TypeParamInfo {
name: interner.intern_string("T"),
constraint: Some(TypeId::OBJECT),
default: None,
is_const: false,
};
let t_id = interner.intern(TypeData::TypeParameter(t));
let t1 = TypeParamInfo {
name: interner.intern_string("T1"),
constraint: Some(t_id),
default: None,
is_const: false,
};
let t1_id = interner.intern(TypeData::TypeParameter(t1));
let u = TypeParamInfo {
name: interner.intern_string("U"),
constraint: Some(t_id),
default: None,
is_const: false,
};
let u_id = interner.intern(TypeData::TypeParameter(u.clone()));
let v = TypeParamInfo {
name: interner.intern_string("V"),
constraint: Some(t1_id),
default: None,
is_const: false,
};
let v_id = interner.intern(TypeData::TypeParameter(v.clone()));
let fn_t = interner.function(FunctionShape {
type_params: vec![u],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: u_id,
optional: false,
rest: false,
}],
this_type: None,
return_type: u_id,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let fn_t1 = interner.function(FunctionShape {
type_params: vec![v],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: v_id,
optional: false,
rest: false,
}],
this_type: None,
return_type: v_id,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(checker.is_subtype_of(fn_t, fn_t1)); assert!(!checker.is_subtype_of(fn_t1, fn_t)); }
#[test]
fn test_generic_covariant_return_position() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let get_name = interner.intern_string("get");
let get_string = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::STRING,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let get_union = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: interner.union(vec![TypeId::STRING, TypeId::NUMBER]),
type_predicate: None,
is_constructor: false,
is_method: false,
});
let producer_string = interner.object(vec![PropertyInfo {
name: get_name,
type_id: get_string,
write_type: get_string,
optional: false,
readonly: true,
is_method: true,
visibility: Visibility::Public,
parent_id: None,
}]);
let producer_union = interner.object(vec![PropertyInfo {
name: get_name,
type_id: get_union,
write_type: get_union,
optional: false,
readonly: true,
is_method: true,
visibility: Visibility::Public,
parent_id: None,
}]);
assert!(checker.is_subtype_of(producer_string, producer_union));
assert!(!checker.is_subtype_of(producer_union, producer_string));
}
#[test]
fn test_generic_contravariant_param_position() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let accept_name = interner.intern_string("accept");
let accept_string = 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::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let accept_union = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: interner.union(vec![TypeId::STRING, TypeId::NUMBER]),
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let consumer_string = interner.object(vec![PropertyInfo::readonly(accept_name, accept_string)]);
let consumer_union = interner.object(vec![PropertyInfo::readonly(accept_name, accept_union)]);
assert!(checker.is_subtype_of(consumer_union, consumer_string));
assert!(!checker.is_subtype_of(consumer_string, consumer_union));
}
#[test]
fn test_generic_mixed_variance_positions() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let process_name = interner.intern_string("process");
let wide_type = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let process_wide_in_narrow_out = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("input")),
type_id: wide_type,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::STRING,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let process_narrow_in_wide_out = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("input")),
type_id: TypeId::STRING,
optional: false,
rest: false,
}],
this_type: None,
return_type: wide_type,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let transform_a = interner.object(vec![PropertyInfo {
name: process_name,
type_id: process_wide_in_narrow_out,
write_type: process_wide_in_narrow_out,
optional: false,
readonly: true,
is_method: true,
visibility: Visibility::Public,
parent_id: None,
}]);
let transform_b = interner.object(vec![PropertyInfo {
name: process_name,
type_id: process_narrow_in_wide_out,
write_type: process_narrow_in_wide_out,
optional: false,
readonly: true,
is_method: true,
visibility: Visibility::Public,
parent_id: None,
}]);
assert!(checker.is_subtype_of(transform_a, transform_b));
assert!(!checker.is_subtype_of(transform_b, transform_a));
}
#[test]
#[ignore = "Method bivariance/strict function types not fully implemented"]
fn test_method_bivariant_wider_param() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let method_name = interner.intern_string("handler");
let wide_param = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let method_narrow = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("e")),
type_id: TypeId::STRING,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let method_wide = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("e")),
type_id: wide_param,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let obj_narrow_method = interner.object(vec![PropertyInfo::method(method_name, method_narrow)]);
let obj_wide_method = interner.object(vec![PropertyInfo::method(method_name, method_wide)]);
assert!(checker.is_subtype_of(obj_narrow_method, obj_wide_method));
assert!(checker.is_subtype_of(obj_wide_method, obj_narrow_method));
}
#[test]
#[ignore = "Method bivariance/strict function types not fully implemented"]
fn test_method_bivariant_callback_param() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let method_name = interner.intern_string("on");
let callback_narrow = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("data")),
type_id: TypeId::STRING,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let callback_wide = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("data")),
type_id: interner.union(vec![TypeId::STRING, TypeId::NUMBER]),
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let method_with_narrow_cb = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("cb")),
type_id: callback_narrow,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let method_with_wide_cb = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("cb")),
type_id: callback_wide,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let obj_narrow_cb = interner.object(vec![PropertyInfo::method(
method_name,
method_with_narrow_cb,
)]);
let obj_wide_cb = interner.object(vec![PropertyInfo::method(method_name, method_with_wide_cb)]);
assert!(checker.is_subtype_of(obj_narrow_cb, obj_wide_cb));
assert!(checker.is_subtype_of(obj_wide_cb, obj_narrow_cb));
}
#[test]
fn test_function_property_contravariant_not_bivariant() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let prop_name = interner.intern_string("handler");
let wide_param = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let fn_narrow = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("e")),
type_id: TypeId::STRING,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let fn_wide = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("e")),
type_id: wide_param,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let obj_narrow_fn = interner.object(vec![PropertyInfo::new(prop_name, fn_narrow)]);
let obj_wide_fn = interner.object(vec![PropertyInfo::new(prop_name, fn_wide)]);
assert!(checker.is_subtype_of(obj_wide_fn, obj_narrow_fn));
assert!(!checker.is_subtype_of(obj_narrow_fn, obj_wide_fn));
}
#[test]
fn test_mutable_property_invariant_same_type() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let prop_name = interner.intern_string("value");
let obj_string = interner.object(vec![PropertyInfo::new(prop_name, TypeId::STRING)]);
let obj_string_2 = interner.object(vec![PropertyInfo::new(prop_name, TypeId::STRING)]);
assert!(checker.is_subtype_of(obj_string, obj_string_2));
assert!(checker.is_subtype_of(obj_string_2, obj_string));
}
#[test]
fn test_mutable_property_invariant_different_types() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let prop_name = interner.intern_string("value");
let wide_type = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let obj_narrow = interner.object(vec![PropertyInfo::new(prop_name, TypeId::STRING)]);
let obj_wide = interner.object(vec![PropertyInfo::new(prop_name, wide_type)]);
assert!(checker.is_subtype_of(obj_narrow, obj_wide));
assert!(!checker.is_subtype_of(obj_wide, obj_narrow));
}
#[test]
fn test_mutable_property_split_accessor_wider_write() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let prop_name = interner.intern_string("value");
let wide_type = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let obj_split = interner.object(vec![PropertyInfo {
name: prop_name,
type_id: TypeId::STRING, write_type: wide_type, optional: false,
readonly: false,
is_method: false,
visibility: Visibility::Public,
parent_id: None,
}]);
let obj_normal = interner.object(vec![PropertyInfo::new(prop_name, TypeId::STRING)]);
assert!(checker.is_subtype_of(obj_split, obj_normal));
assert!(!checker.is_subtype_of(obj_normal, obj_split));
}
#[test]
fn test_readonly_property_covariant() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let prop_name = interner.intern_string("value");
let wide_type = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let obj_narrow_readonly =
interner.object(vec![PropertyInfo::readonly(prop_name, TypeId::STRING)]);
let obj_wide_readonly = interner.object(vec![PropertyInfo::readonly(prop_name, wide_type)]);
assert!(checker.is_subtype_of(obj_narrow_readonly, obj_wide_readonly));
assert!(!checker.is_subtype_of(obj_wide_readonly, obj_narrow_readonly));
}
#[test]
fn test_mutable_array_element_invariant() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let string_array = interner.array(TypeId::STRING);
let wide_array = interner.array(interner.union(vec![TypeId::STRING, TypeId::NUMBER]));
assert!(checker.is_subtype_of(string_array, wide_array));
assert!(!checker.is_subtype_of(wide_array, string_array));
}
#[test]
fn test_intersection_flattening_nested() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a_name = interner.intern_string("a");
let b_name = interner.intern_string("b");
let c_name = interner.intern_string("c");
let obj_a = interner.object(vec![PropertyInfo::new(a_name, TypeId::STRING)]);
let obj_b = interner.object(vec![PropertyInfo::new(b_name, TypeId::NUMBER)]);
let obj_c = interner.object(vec![PropertyInfo::new(c_name, TypeId::BOOLEAN)]);
let ab = interner.intersection(vec![obj_a, obj_b]);
let nested = interner.intersection(vec![ab, obj_c]);
let flat = interner.intersection(vec![obj_a, obj_b, obj_c]);
assert!(checker.is_subtype_of(nested, flat));
assert!(checker.is_subtype_of(flat, nested));
}
#[test]
fn test_intersection_flattening_single_element() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a_name = interner.intern_string("a");
let obj_a = interner.object(vec![PropertyInfo::new(a_name, TypeId::STRING)]);
let single = interner.intersection(vec![obj_a]);
assert!(checker.is_subtype_of(single, obj_a));
assert!(checker.is_subtype_of(obj_a, single));
}
#[test]
fn test_intersection_flattening_duplicates() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a_name = interner.intern_string("a");
let obj_a = interner.object(vec![PropertyInfo::new(a_name, TypeId::STRING)]);
let duplicated = interner.intersection(vec![obj_a, obj_a]);
assert!(checker.is_subtype_of(duplicated, obj_a));
assert!(checker.is_subtype_of(obj_a, duplicated));
}
#[test]
fn test_intersection_with_never_is_never() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a_name = interner.intern_string("a");
let obj_a = interner.object(vec![PropertyInfo::new(a_name, TypeId::STRING)]);
let with_never = interner.intersection(vec![obj_a, TypeId::NEVER]);
assert!(checker.is_subtype_of(with_never, TypeId::NEVER));
assert!(checker.is_subtype_of(TypeId::NEVER, with_never));
}
#[test]
fn test_intersection_never_absorbs_all() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let multi_with_never = interner.intersection(vec![
TypeId::STRING,
TypeId::NUMBER,
TypeId::BOOLEAN,
TypeId::NEVER,
]);
assert!(checker.is_subtype_of(multi_with_never, TypeId::NEVER));
}
#[test]
fn test_intersection_never_at_any_position() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let at_start = interner.intersection(vec![TypeId::NEVER, TypeId::STRING, TypeId::NUMBER]);
let at_middle = interner.intersection(vec![TypeId::STRING, TypeId::NEVER, TypeId::NUMBER]);
let at_end = interner.intersection(vec![TypeId::STRING, TypeId::NUMBER, TypeId::NEVER]);
assert!(checker.is_subtype_of(at_start, TypeId::NEVER));
assert!(checker.is_subtype_of(at_middle, TypeId::NEVER));
assert!(checker.is_subtype_of(at_end, TypeId::NEVER));
}
#[test]
fn test_object_intersection_merges_properties() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a_name = interner.intern_string("a");
let b_name = interner.intern_string("b");
let obj_a = interner.object(vec![PropertyInfo::new(a_name, TypeId::STRING)]);
let obj_b = interner.object(vec![PropertyInfo::new(b_name, TypeId::NUMBER)]);
let intersection = interner.intersection(vec![obj_a, obj_b]);
let merged = interner.object(vec![
PropertyInfo::new(a_name, TypeId::STRING),
PropertyInfo::new(b_name, TypeId::NUMBER),
]);
assert!(checker.is_subtype_of(intersection, merged));
assert!(checker.is_subtype_of(merged, intersection));
}
#[test]
fn test_object_intersection_same_property_narrowing() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let x_name = interner.intern_string("x");
let wide_type = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let obj_wide = interner.object(vec![PropertyInfo::new(x_name, wide_type)]);
let obj_narrow = interner.object(vec![PropertyInfo::new(x_name, TypeId::STRING)]);
let intersection = interner.intersection(vec![obj_wide, obj_narrow]);
assert!(checker.is_subtype_of(intersection, obj_narrow));
}
#[test]
fn test_object_intersection_three_objects() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a_name = interner.intern_string("a");
let b_name = interner.intern_string("b");
let c_name = interner.intern_string("c");
let obj_a = interner.object(vec![PropertyInfo::new(a_name, TypeId::STRING)]);
let obj_b = interner.object(vec![PropertyInfo::new(b_name, TypeId::NUMBER)]);
let obj_c = interner.object(vec![PropertyInfo::new(c_name, TypeId::BOOLEAN)]);
let intersection = interner.intersection(vec![obj_a, obj_b, obj_c]);
assert!(checker.is_subtype_of(intersection, obj_a));
assert!(checker.is_subtype_of(intersection, obj_b));
assert!(checker.is_subtype_of(intersection, obj_c));
}
#[test]
fn test_object_intersection_with_optional_property() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a_name = interner.intern_string("a");
let b_name = interner.intern_string("b");
let obj_a = interner.object(vec![PropertyInfo::new(a_name, TypeId::STRING)]);
let obj_b_optional = interner.object(vec![PropertyInfo::opt(b_name, TypeId::NUMBER)]);
let intersection = interner.intersection(vec![obj_a, obj_b_optional]);
assert!(checker.is_subtype_of(intersection, obj_a));
assert!(checker.is_subtype_of(intersection, obj_b_optional));
}
#[test]
fn test_intersection_subtype_of_each_member() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a_name = interner.intern_string("a");
let b_name = interner.intern_string("b");
let obj_a = interner.object(vec![PropertyInfo::new(a_name, TypeId::STRING)]);
let obj_b = interner.object(vec![PropertyInfo::new(b_name, TypeId::NUMBER)]);
let intersection = interner.intersection(vec![obj_a, obj_b]);
assert!(checker.is_subtype_of(intersection, obj_a));
assert!(checker.is_subtype_of(intersection, obj_b));
assert!(!checker.is_subtype_of(obj_a, intersection));
assert!(!checker.is_subtype_of(obj_b, intersection));
}
#[test]
fn test_string_literal_narrows_to_union() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a = interner.literal_string("a");
let b = interner.literal_string("b");
let c = interner.literal_string("c");
let union = interner.union(vec![a, b, c]);
assert!(checker.is_subtype_of(a, union));
assert!(checker.is_subtype_of(b, union));
assert!(checker.is_subtype_of(c, union));
assert!(!checker.is_subtype_of(union, a));
assert!(!checker.is_subtype_of(union, b));
assert!(!checker.is_subtype_of(union, c));
}
#[test]
fn test_string_literal_not_subtype_of_different_literal() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let hello = interner.literal_string("hello");
let world = interner.literal_string("world");
assert!(!checker.is_subtype_of(hello, world));
assert!(!checker.is_subtype_of(world, hello));
}
#[test]
fn test_string_literal_subtype_of_string() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let hello = interner.literal_string("hello");
let empty = interner.literal_string("");
let special = interner.literal_string("!@#$%^&*()");
assert!(checker.is_subtype_of(hello, TypeId::STRING));
assert!(checker.is_subtype_of(empty, TypeId::STRING));
assert!(checker.is_subtype_of(special, TypeId::STRING));
assert!(!checker.is_subtype_of(TypeId::STRING, hello));
}
#[test]
fn test_string_literal_union_subtype_of_string() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a = interner.literal_string("a");
let b = interner.literal_string("b");
let union = interner.union(vec![a, b]);
assert!(checker.is_subtype_of(union, TypeId::STRING));
assert!(!checker.is_subtype_of(TypeId::STRING, union));
}
#[test]
fn test_numeric_literal_types() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let one = interner.literal_number(1.0);
let two = interner.literal_number(2.0);
let zero = interner.literal_number(0.0);
let negative = interner.literal_number(-42.0);
const APPROX_FLOAT: f64 = 3.15;
let float = interner.literal_number(APPROX_FLOAT);
assert!(checker.is_subtype_of(one, one));
assert!(checker.is_subtype_of(two, two));
assert!(!checker.is_subtype_of(one, two));
assert!(!checker.is_subtype_of(two, one));
assert!(checker.is_subtype_of(one, TypeId::NUMBER));
assert!(checker.is_subtype_of(zero, TypeId::NUMBER));
assert!(checker.is_subtype_of(negative, TypeId::NUMBER));
assert!(checker.is_subtype_of(float, TypeId::NUMBER));
assert!(!checker.is_subtype_of(TypeId::NUMBER, one));
}
#[test]
fn test_numeric_literal_union() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let one = interner.literal_number(1.0);
let two = interner.literal_number(2.0);
let three = interner.literal_number(3.0);
let union = interner.union(vec![one, two, three]);
assert!(checker.is_subtype_of(union, TypeId::NUMBER));
assert!(checker.is_subtype_of(one, union));
assert!(checker.is_subtype_of(two, union));
assert!(checker.is_subtype_of(three, union));
assert!(!checker.is_subtype_of(TypeId::NUMBER, union));
}
#[test]
fn test_numeric_literal_special_values() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let zero = interner.literal_number(0.0);
let neg_zero = interner.literal_number(-0.0);
assert!(checker.is_subtype_of(zero, TypeId::NUMBER));
assert!(checker.is_subtype_of(neg_zero, TypeId::NUMBER));
}
#[test]
fn test_template_literal_pattern_prefix() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let template = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("prefix-")),
TemplateSpan::Type(TypeId::STRING),
]);
assert!(checker.is_subtype_of(template, TypeId::STRING));
let matching = interner.literal_string("prefix-hello");
assert!(checker.is_subtype_of(matching, TypeId::STRING));
assert!(checker.is_subtype_of(matching, template));
}
#[test]
fn test_template_literal_pattern_suffix() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let template = interner.template_literal(vec![
TemplateSpan::Type(TypeId::STRING),
TemplateSpan::Text(interner.intern_string("-suffix")),
]);
assert!(checker.is_subtype_of(template, TypeId::STRING));
let matching = interner.literal_string("hello-suffix");
assert!(checker.is_subtype_of(matching, template));
let not_matching = interner.literal_string("hello-other");
assert!(!checker.is_subtype_of(not_matching, template));
}
#[test]
fn test_template_literal_pattern_with_union() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let red = interner.literal_string("red");
let blue = interner.literal_string("blue");
let colors = interner.union(vec![red, blue]);
let template = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("color-")),
TemplateSpan::Type(colors),
]);
assert!(checker.is_subtype_of(template, TypeId::STRING));
let color_red = interner.literal_string("color-red");
let color_blue = interner.literal_string("color-blue");
assert!(checker.is_subtype_of(color_red, template));
assert!(checker.is_subtype_of(color_blue, template));
let color_green = interner.literal_string("color-green");
assert!(!checker.is_subtype_of(color_green, template));
}
#[test]
fn test_template_literal_pattern_multiple_parts() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let template = interner.template_literal(vec![
TemplateSpan::Type(TypeId::STRING),
TemplateSpan::Text(interner.intern_string("-")),
TemplateSpan::Type(TypeId::NUMBER),
]);
assert!(checker.is_subtype_of(template, TypeId::STRING));
let matching = interner.literal_string("hello-42");
assert!(checker.is_subtype_of(matching, template));
}
#[test]
fn test_template_literal_empty_parts() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let template = interner.template_literal(vec![TemplateSpan::Type(TypeId::STRING)]);
assert!(checker.is_subtype_of(template, TypeId::STRING));
let hello = interner.literal_string("hello");
assert!(checker.is_subtype_of(hello, template));
}
#[test]
fn test_boolean_literal_types() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let type_true = interner.literal_boolean(true);
let type_false = interner.literal_boolean(false);
assert!(checker.is_subtype_of(type_true, TypeId::BOOLEAN));
assert!(checker.is_subtype_of(type_false, TypeId::BOOLEAN));
assert!(!checker.is_subtype_of(type_true, type_false));
assert!(!checker.is_subtype_of(type_false, type_true));
assert!(!checker.is_subtype_of(TypeId::BOOLEAN, type_true));
assert!(!checker.is_subtype_of(TypeId::BOOLEAN, type_false));
}
#[test]
fn test_covariant_return_type_subtype() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let fn_return_string = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::STRING,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let union = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let fn_return_union = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: union,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(checker.is_subtype_of(fn_return_string, fn_return_union));
assert!(!checker.is_subtype_of(fn_return_union, fn_return_string));
}
#[test]
fn test_covariant_return_type_literal() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let hello = interner.literal_string("hello");
let fn_return_literal = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: hello,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let fn_return_string = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::STRING,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(checker.is_subtype_of(fn_return_literal, fn_return_string));
assert!(!checker.is_subtype_of(fn_return_string, fn_return_literal));
}
#[test]
fn test_covariant_return_type_object() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a_prop = interner.intern_string("a");
let b_prop = interner.intern_string("b");
let obj_ab = interner.object(vec![
PropertyInfo::new(a_prop, TypeId::STRING),
PropertyInfo::new(b_prop, TypeId::NUMBER),
]);
let obj_a = interner.object(vec![PropertyInfo::new(a_prop, TypeId::STRING)]);
let fn_return_ab = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: obj_ab,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let fn_return_a = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: obj_a,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(checker.is_subtype_of(fn_return_ab, fn_return_a));
assert!(!checker.is_subtype_of(fn_return_a, fn_return_ab));
}
#[test]
fn test_covariant_return_type_array() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let string_array = interner.array(TypeId::STRING);
let union = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let union_array = interner.array(union);
let fn_return_string_arr = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: string_array,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let fn_return_union_arr = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: union_array,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(checker.is_subtype_of(fn_return_string_arr, fn_return_union_arr));
assert!(!checker.is_subtype_of(fn_return_union_arr, fn_return_string_arr));
}
#[test]
fn test_covariant_return_never() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let fn_return_never = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::NEVER,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let fn_return_string = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::STRING,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(checker.is_subtype_of(fn_return_never, fn_return_string));
assert!(!checker.is_subtype_of(fn_return_string, fn_return_never));
}
#[test]
fn test_covariant_return_void_undefined() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let fn_return_undefined = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::UNDEFINED,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let fn_return_void = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(checker.is_subtype_of(fn_return_undefined, fn_return_void));
}
#[test]
fn test_contravariant_param_wider_is_subtype() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let union = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let fn_param_union = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: union,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let fn_param_string = 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::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(checker.is_subtype_of(fn_param_union, fn_param_string));
assert!(!checker.is_subtype_of(fn_param_string, fn_param_union));
}
#[test]
fn test_contravariant_param_base_class() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let base_prop = interner.intern_string("base");
let derived_prop = interner.intern_string("derived");
let base = interner.object(vec![PropertyInfo::new(base_prop, TypeId::STRING)]);
let derived = interner.object(vec![
PropertyInfo::new(base_prop, TypeId::STRING),
PropertyInfo::new(derived_prop, TypeId::NUMBER),
]);
let fn_param_base = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: base,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let fn_param_derived = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: derived,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(checker.is_subtype_of(fn_param_base, fn_param_derived));
assert!(!checker.is_subtype_of(fn_param_derived, fn_param_base));
}
#[test]
fn test_contravariant_param_unknown() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let fn_param_unknown = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: TypeId::UNKNOWN,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let fn_param_string = 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::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(checker.is_subtype_of(fn_param_unknown, fn_param_string));
}
#[test]
fn test_contravariant_multiple_params() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let union = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let fn_wider = interner.function(FunctionShape {
type_params: vec![],
params: vec![
ParamInfo {
name: Some(interner.intern_string("a")),
type_id: union,
optional: false,
rest: false,
},
ParamInfo {
name: Some(interner.intern_string("b")),
type_id: TypeId::UNKNOWN,
optional: false,
rest: false,
},
],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let fn_narrower = interner.function(FunctionShape {
type_params: vec![],
params: vec![
ParamInfo {
name: Some(interner.intern_string("a")),
type_id: TypeId::STRING,
optional: false,
rest: false,
},
ParamInfo {
name: Some(interner.intern_string("b")),
type_id: TypeId::NUMBER,
optional: false,
rest: false,
},
],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(checker.is_subtype_of(fn_wider, fn_narrower));
assert!(!checker.is_subtype_of(fn_narrower, fn_wider));
}
#[test]
fn test_contravariant_callback_param() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let union = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let cb_narrow = 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::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let cb_wide = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: union,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let fn_with_cb_narrow = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("cb")),
type_id: cb_narrow,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let fn_with_cb_wide = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("cb")),
type_id: cb_wide,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(checker.is_subtype_of(fn_with_cb_narrow, fn_with_cb_wide));
assert!(!checker.is_subtype_of(fn_with_cb_wide, fn_with_cb_narrow));
}
#[test]
fn test_invariant_mutable_property() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let value_prop = interner.intern_string("value");
let obj_string = interner.object(vec![PropertyInfo::new(value_prop, TypeId::STRING)]);
let union = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let obj_union = interner.object(vec![PropertyInfo::new(value_prop, union)]);
let is_subtype = checker.is_subtype_of(obj_string, obj_union);
let is_super = checker.is_subtype_of(obj_union, obj_string);
assert!(!(is_subtype && is_super) || obj_string == obj_union);
}
#[test]
fn test_invariant_array_element() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let string_array = interner.array(TypeId::STRING);
let union = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let union_array = interner.array(union);
let allows_covariant = checker.is_subtype_of(string_array, union_array);
assert!(allows_covariant); }
#[test]
fn test_invariant_generic_mutable_box() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let value_prop = interner.intern_string("value");
let box_string = interner.object(vec![PropertyInfo::new(value_prop, TypeId::STRING)]);
let box_number = interner.object(vec![PropertyInfo::new(value_prop, TypeId::NUMBER)]);
assert!(!checker.is_subtype_of(box_string, box_number));
assert!(!checker.is_subtype_of(box_number, box_string));
}
#[test]
#[ignore = "Method bivariance/strict function types not fully implemented"]
fn test_invariant_ref_cell_pattern() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let get_name = interner.intern_string("get");
let set_name = interner.intern_string("set");
let get_string = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::STRING,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let set_string = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("v")),
type_id: TypeId::STRING,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let refcell_string = interner.object(vec![
PropertyInfo {
name: get_name,
type_id: get_string,
write_type: get_string,
optional: false,
readonly: true,
is_method: true,
visibility: Visibility::Public,
parent_id: None,
},
PropertyInfo {
name: set_name,
type_id: set_string,
write_type: set_string,
optional: false,
readonly: true,
is_method: true,
visibility: Visibility::Public,
parent_id: None,
},
]);
let union = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let get_union = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: union,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let set_union = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("v")),
type_id: union,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let refcell_union = interner.object(vec![
PropertyInfo {
name: get_name,
type_id: get_union,
write_type: get_union,
optional: false,
readonly: true,
is_method: true,
visibility: Visibility::Public,
parent_id: None,
},
PropertyInfo {
name: set_name,
type_id: set_union,
write_type: set_union,
optional: false,
readonly: true,
is_method: true,
visibility: Visibility::Public,
parent_id: None,
},
]);
assert!(checker.is_subtype_of(refcell_string, refcell_union));
assert!(!checker.is_subtype_of(refcell_union, refcell_string));
}
#[test]
fn test_invariant_in_out_parameter() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let fn_string = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("ref")),
type_id: TypeId::STRING,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::STRING,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let union = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let fn_union = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("ref")),
type_id: union,
optional: false,
rest: false,
}],
this_type: None,
return_type: union,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(!checker.is_subtype_of(fn_string, fn_union));
assert!(!checker.is_subtype_of(fn_union, fn_string));
}
#[test]
fn test_bivariant_method_param_wider() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let handler_name = interner.intern_string("handler");
let union = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let method_narrow = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("e")),
type_id: TypeId::STRING,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let method_wide = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("e")),
type_id: union,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let obj_narrow = interner.object(vec![PropertyInfo::method(handler_name, method_narrow)]);
let obj_wide = interner.object(vec![PropertyInfo::method(handler_name, method_wide)]);
let narrow_to_wide = checker.is_subtype_of(obj_narrow, obj_wide);
let wide_to_narrow = checker.is_subtype_of(obj_wide, obj_narrow);
assert!(narrow_to_wide || wide_to_narrow);
}
#[test]
fn test_bivariant_method_vs_function_property() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let handler_name = interner.intern_string("handler");
let union = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let fn_narrow = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("e")),
type_id: TypeId::STRING,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let fn_wide = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("e")),
type_id: union,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let obj_method = interner.object(vec![PropertyInfo::method(handler_name, fn_narrow)]);
let obj_fn_prop = interner.object(vec![PropertyInfo::new(handler_name, fn_wide)]);
let result = checker.is_subtype_of(obj_method, obj_fn_prop);
let _ = result;
}
#[test]
fn test_bivariant_event_handler_pattern() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let on_event_name = interner.intern_string("onEvent");
let event_prop = interner.intern_string("type");
let base_event = interner.object(vec![PropertyInfo::readonly(event_prop, TypeId::STRING)]);
let target_prop = interner.intern_string("target");
let derived_event = interner.object(vec![
PropertyInfo::readonly(event_prop, TypeId::STRING),
PropertyInfo::readonly(target_prop, TypeId::STRING),
]);
let handler_base = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("e")),
type_id: base_event,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let handler_derived = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("e")),
type_id: derived_event,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let obj_base_handler = interner.object(vec![PropertyInfo::method(on_event_name, handler_base)]);
let obj_derived_handler =
interner.object(vec![PropertyInfo::method(on_event_name, handler_derived)]);
let derived_to_base = checker.is_subtype_of(obj_derived_handler, obj_base_handler);
let _ = derived_to_base;
}
#[test]
fn test_bivariant_overload_callback() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let cb_name = interner.intern_string("callback");
let cb_string = 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::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let cb_number = 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::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let obj_cb_string = interner.object(vec![PropertyInfo::method(cb_name, cb_string)]);
let obj_cb_number = interner.object(vec![PropertyInfo::method(cb_name, cb_number)]);
assert!(!checker.is_subtype_of(obj_cb_string, obj_cb_number));
assert!(!checker.is_subtype_of(obj_cb_number, obj_cb_string));
}
#[test]
fn test_bivariant_optional_method_param() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let method_name = interner.intern_string("process");
let method_required = 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::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let method_optional = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: TypeId::STRING,
optional: true,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let obj_required = interner.object(vec![PropertyInfo::method(method_name, method_required)]);
let obj_optional = interner.object(vec![PropertyInfo::method(method_name, method_optional)]);
let optional_to_required = checker.is_subtype_of(obj_optional, obj_required);
let required_to_optional = checker.is_subtype_of(obj_required, obj_optional);
assert!(optional_to_required || required_to_optional);
}
#[test]
fn test_intersection_associativity() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a_name = interner.intern_string("a");
let b_name = interner.intern_string("b");
let c_name = interner.intern_string("c");
let obj_a = interner.object(vec![PropertyInfo::new(a_name, TypeId::STRING)]);
let obj_b = interner.object(vec![PropertyInfo::new(b_name, TypeId::NUMBER)]);
let obj_c = interner.object(vec![PropertyInfo::new(c_name, TypeId::BOOLEAN)]);
let ab = interner.intersection(vec![obj_a, obj_b]);
let left_assoc = interner.intersection(vec![ab, obj_c]);
let bc = interner.intersection(vec![obj_b, obj_c]);
let right_assoc = interner.intersection(vec![obj_a, bc]);
assert!(checker.is_subtype_of(left_assoc, right_assoc));
assert!(checker.is_subtype_of(right_assoc, left_assoc));
}
#[test]
fn test_intersection_commutativity() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a_name = interner.intern_string("a");
let b_name = interner.intern_string("b");
let obj_a = interner.object(vec![PropertyInfo::new(a_name, TypeId::STRING)]);
let obj_b = interner.object(vec![PropertyInfo::new(b_name, TypeId::NUMBER)]);
let ab = interner.intersection(vec![obj_a, obj_b]);
let ba = interner.intersection(vec![obj_b, obj_a]);
assert!(checker.is_subtype_of(ab, ba));
assert!(checker.is_subtype_of(ba, ab));
}
#[test]
fn test_intersection_four_types() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a_name = interner.intern_string("a");
let b_name = interner.intern_string("b");
let c_name = interner.intern_string("c");
let d_name = interner.intern_string("d");
let obj_a = interner.object(vec![PropertyInfo::new(a_name, TypeId::STRING)]);
let obj_b = interner.object(vec![PropertyInfo::new(b_name, TypeId::NUMBER)]);
let obj_c = interner.object(vec![PropertyInfo::new(c_name, TypeId::BOOLEAN)]);
let obj_d = interner.object(vec![PropertyInfo::new(d_name, TypeId::STRING)]);
let flat = interner.intersection(vec![obj_a, obj_b, obj_c, obj_d]);
let ab = interner.intersection(vec![obj_a, obj_b]);
let abc = interner.intersection(vec![ab, obj_c]);
let nested = interner.intersection(vec![abc, obj_d]);
assert!(checker.is_subtype_of(flat, nested));
assert!(checker.is_subtype_of(nested, flat));
}
#[test]
fn test_intersection_with_unknown_identity() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a_name = interner.intern_string("a");
let obj_a = interner.object(vec![PropertyInfo::new(a_name, TypeId::STRING)]);
let with_unknown = interner.intersection(vec![obj_a, TypeId::UNKNOWN]);
assert!(checker.is_subtype_of(with_unknown, obj_a));
assert!(checker.is_subtype_of(obj_a, with_unknown));
}
#[test]
fn test_intersection_intrinsics_flatten() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let intrinsic_intersection =
interner.intersection(vec![TypeId::STRING, TypeId::NUMBER, TypeId::BOOLEAN]);
assert!(checker.is_subtype_of(intrinsic_intersection, TypeId::NEVER));
}
#[test]
fn test_intersection_equals_merged_object() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a_name = interner.intern_string("a");
let b_name = interner.intern_string("b");
let obj_a = interner.object(vec![PropertyInfo::new(a_name, TypeId::STRING)]);
let obj_b = interner.object(vec![PropertyInfo::new(b_name, TypeId::NUMBER)]);
let intersection = interner.intersection(vec![obj_a, obj_b]);
let merged = interner.object(vec![
PropertyInfo::new(a_name, TypeId::STRING),
PropertyInfo::new(b_name, TypeId::NUMBER),
]);
assert!(checker.is_subtype_of(intersection, merged));
assert!(checker.is_subtype_of(merged, intersection));
}
#[test]
fn test_intersection_wider_object_not_subtype() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a_name = interner.intern_string("a");
let b_name = interner.intern_string("b");
let c_name = interner.intern_string("c");
let obj_a = interner.object(vec![PropertyInfo::new(a_name, TypeId::STRING)]);
let obj_b = interner.object(vec![PropertyInfo::new(b_name, TypeId::NUMBER)]);
let intersection = interner.intersection(vec![obj_a, obj_b]);
let obj_abc = interner.object(vec![
PropertyInfo::new(a_name, TypeId::STRING),
PropertyInfo::new(b_name, TypeId::NUMBER),
PropertyInfo::new(c_name, TypeId::BOOLEAN),
]);
assert!(checker.is_subtype_of(obj_abc, intersection));
assert!(!checker.is_subtype_of(obj_a, intersection));
}
#[test]
fn test_intersection_overlapping_properties() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let x_name = interner.intern_string("x");
let y_name = interner.intern_string("y");
let z_name = interner.intern_string("z");
let obj_xy = interner.object(vec![
PropertyInfo::new(x_name, TypeId::STRING),
PropertyInfo::new(y_name, TypeId::NUMBER),
]);
let obj_yz = interner.object(vec![
PropertyInfo::new(y_name, TypeId::NUMBER),
PropertyInfo::new(z_name, TypeId::BOOLEAN),
]);
let intersection = interner.intersection(vec![obj_xy, obj_yz]);
let obj_xyz = interner.object(vec![
PropertyInfo::new(x_name, TypeId::STRING),
PropertyInfo::new(y_name, TypeId::NUMBER),
PropertyInfo::new(z_name, TypeId::BOOLEAN),
]);
assert!(checker.is_subtype_of(intersection, obj_xyz));
assert!(checker.is_subtype_of(obj_xyz, intersection));
}
#[test]
fn test_intersection_conflicting_property_types() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let x_name = interner.intern_string("x");
let obj_x_string = interner.object(vec![PropertyInfo::new(x_name, TypeId::STRING)]);
let obj_x_number = interner.object(vec![PropertyInfo::new(x_name, TypeId::NUMBER)]);
let _intersection = interner.intersection(vec![obj_x_string, obj_x_number]);
assert!(!checker.is_subtype_of(obj_x_string, obj_x_number));
assert!(!checker.is_subtype_of(obj_x_number, obj_x_string));
}
#[test]
fn test_object_subtype_of_intersection() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a_name = interner.intern_string("a");
let b_name = interner.intern_string("b");
let obj_a = interner.object(vec![PropertyInfo::new(a_name, TypeId::STRING)]);
let obj_b = interner.object(vec![PropertyInfo::new(b_name, TypeId::NUMBER)]);
let intersection = interner.intersection(vec![obj_a, obj_b]);
let obj_ab = interner.object(vec![
PropertyInfo::new(a_name, TypeId::STRING),
PropertyInfo::new(b_name, TypeId::NUMBER),
]);
assert!(checker.is_subtype_of(obj_ab, intersection));
assert!(checker.is_subtype_of(intersection, obj_ab));
}
#[test]
fn test_intersection_never_with_object() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a_name = interner.intern_string("a");
let obj_a = interner.object(vec![PropertyInfo::new(a_name, TypeId::STRING)]);
let with_never = interner.intersection(vec![obj_a, TypeId::NEVER]);
assert!(checker.is_subtype_of(with_never, TypeId::NEVER));
assert!(checker.is_subtype_of(TypeId::NEVER, with_never));
}
#[test]
fn test_intersection_never_with_function() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let fn_type = 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 with_never = interner.intersection(vec![fn_type, TypeId::NEVER]);
assert!(checker.is_subtype_of(with_never, TypeId::NEVER));
}
#[test]
fn test_intersection_never_with_union() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let union = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let with_never = interner.intersection(vec![union, TypeId::NEVER]);
assert!(checker.is_subtype_of(with_never, TypeId::NEVER));
}
#[test]
fn test_intersection_nested_never() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a_name = interner.intern_string("a");
let b_name = interner.intern_string("b");
let obj_a = interner.object(vec![PropertyInfo::new(a_name, TypeId::STRING)]);
let obj_b = interner.object(vec![PropertyInfo::new(b_name, TypeId::NUMBER)]);
let a_and_never = interner.intersection(vec![obj_a, TypeId::NEVER]);
let nested = interner.intersection(vec![a_and_never, obj_b]);
assert!(checker.is_subtype_of(nested, TypeId::NEVER));
}
#[test]
fn test_intersection_never_zero_element() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let just_never = interner.intersection(vec![TypeId::NEVER]);
assert!(checker.is_subtype_of(just_never, TypeId::NEVER));
assert!(checker.is_subtype_of(TypeId::NEVER, just_never));
}
#[test]
fn test_intersection_multiple_nevers() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let double_never = interner.intersection(vec![TypeId::NEVER, TypeId::NEVER]);
assert!(checker.is_subtype_of(double_never, TypeId::NEVER));
assert!(checker.is_subtype_of(TypeId::NEVER, double_never));
}
#[test]
fn test_intersection_access_from_first_member() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a_name = interner.intern_string("a");
let b_name = interner.intern_string("b");
let obj_a = interner.object(vec![PropertyInfo::new(a_name, TypeId::STRING)]);
let obj_b = interner.object(vec![PropertyInfo::new(b_name, TypeId::NUMBER)]);
let intersection = interner.intersection(vec![obj_a, obj_b]);
assert!(checker.is_subtype_of(intersection, obj_a));
}
#[test]
fn test_intersection_access_from_second_member() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a_name = interner.intern_string("a");
let b_name = interner.intern_string("b");
let obj_a = interner.object(vec![PropertyInfo::new(a_name, TypeId::STRING)]);
let obj_b = interner.object(vec![PropertyInfo::new(b_name, TypeId::NUMBER)]);
let intersection = interner.intersection(vec![obj_a, obj_b]);
assert!(checker.is_subtype_of(intersection, obj_b));
}
#[test]
fn test_intersection_access_all_members() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a_name = interner.intern_string("a");
let b_name = interner.intern_string("b");
let c_name = interner.intern_string("c");
let obj_a = interner.object(vec![PropertyInfo::new(a_name, TypeId::STRING)]);
let obj_b = interner.object(vec![PropertyInfo::new(b_name, TypeId::NUMBER)]);
let obj_c = interner.object(vec![PropertyInfo::new(c_name, TypeId::BOOLEAN)]);
let intersection = interner.intersection(vec![obj_a, obj_b, obj_c]);
assert!(checker.is_subtype_of(intersection, obj_a));
assert!(checker.is_subtype_of(intersection, obj_b));
assert!(checker.is_subtype_of(intersection, obj_c));
}
#[test]
fn test_intersection_method_access() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a_name = interner.intern_string("a");
let method_name = interner.intern_string("doSomething");
let method = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::STRING,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let obj_a = interner.object(vec![PropertyInfo::new(a_name, TypeId::STRING)]);
let obj_method = interner.object(vec![PropertyInfo::method(method_name, method)]);
let intersection = interner.intersection(vec![obj_a, obj_method]);
assert!(checker.is_subtype_of(intersection, obj_a));
assert!(checker.is_subtype_of(intersection, obj_method));
}
#[test]
fn test_intersection_narrowed_property_access() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let x_name = interner.intern_string("x");
let wide_type = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let obj_wide = interner.object(vec![PropertyInfo::new(x_name, wide_type)]);
let obj_narrow = interner.object(vec![PropertyInfo::new(x_name, TypeId::STRING)]);
let intersection = interner.intersection(vec![obj_wide, obj_narrow]);
assert!(checker.is_subtype_of(intersection, obj_narrow));
}
#[test]
fn test_intersection_function_member_access() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let fn_string = 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::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let fn_number = 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::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let fn_intersection = interner.intersection(vec![fn_string, fn_number]);
let union_param = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let fn_union_param = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: union_param,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(checker.is_subtype_of(fn_union_param, fn_intersection));
}
#[test]
fn test_intersection_readonly_property_access() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a_name = interner.intern_string("a");
let b_name = interner.intern_string("b");
let obj_a_readonly = interner.object(vec![PropertyInfo::readonly(a_name, TypeId::STRING)]);
let obj_b = interner.object(vec![PropertyInfo::new(b_name, TypeId::NUMBER)]);
let intersection = interner.intersection(vec![obj_a_readonly, obj_b]);
assert!(checker.is_subtype_of(intersection, obj_a_readonly));
assert!(checker.is_subtype_of(intersection, obj_b));
}
#[test]
fn test_intersection_optional_property_access() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a_name = interner.intern_string("a");
let obj_a_optional = interner.object(vec![PropertyInfo::opt(a_name, TypeId::STRING)]);
let obj_a_required = interner.object(vec![PropertyInfo::new(a_name, TypeId::STRING)]);
let intersection = interner.intersection(vec![obj_a_optional, obj_a_required]);
assert!(checker.is_subtype_of(intersection, obj_a_required));
}
#[test]
fn test_fn_param_contravariance_wider_param_is_subtype() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let param_union = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let fn_string_param = 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::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let fn_union_param = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: param_union,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(checker.is_subtype_of(fn_union_param, fn_string_param));
assert!(!checker.is_subtype_of(fn_string_param, fn_union_param));
}
#[test]
fn test_fn_param_contravariance_unknown_accepts_all() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let fn_string_param = 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::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let fn_unknown_param = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: TypeId::UNKNOWN,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(checker.is_subtype_of(fn_unknown_param, fn_string_param));
}
#[test]
fn test_fn_param_contravariance_multiple_params() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let fn_specific = interner.function(FunctionShape {
type_params: vec![],
params: vec![
ParamInfo {
name: Some(interner.intern_string("a")),
type_id: TypeId::STRING,
optional: false,
rest: false,
},
ParamInfo {
name: Some(interner.intern_string("b")),
type_id: TypeId::NUMBER,
optional: false,
rest: false,
},
],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let fn_wide = interner.function(FunctionShape {
type_params: vec![],
params: vec![
ParamInfo {
name: Some(interner.intern_string("a")),
type_id: TypeId::UNKNOWN,
optional: false,
rest: false,
},
ParamInfo {
name: Some(interner.intern_string("b")),
type_id: TypeId::UNKNOWN,
optional: false,
rest: false,
},
],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(checker.is_subtype_of(fn_wide, fn_specific));
}
#[test]
fn test_fn_param_contravariance_object_type() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a_name = interner.intern_string("a");
let b_name = interner.intern_string("b");
let obj_a = interner.object(vec![PropertyInfo::new(a_name, TypeId::STRING)]);
let obj_ab = interner.object(vec![
PropertyInfo::new(a_name, TypeId::STRING),
PropertyInfo::new(b_name, TypeId::NUMBER),
]);
let fn_obj_a = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: obj_a,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let fn_obj_ab = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: obj_ab,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(checker.is_subtype_of(fn_obj_a, fn_obj_ab));
assert!(!checker.is_subtype_of(fn_obj_ab, fn_obj_a));
}
#[test]
fn test_fn_param_contravariance_never_param() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let fn_string_param = 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::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let fn_never_param = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: TypeId::NEVER,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(checker.is_subtype_of(fn_string_param, fn_never_param));
}
#[test]
fn test_fn_param_contravariance_literal_type() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let hello = interner.literal_string("hello");
let fn_string_param = 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::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let fn_literal_param = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: hello,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(checker.is_subtype_of(fn_string_param, fn_literal_param));
assert!(!checker.is_subtype_of(fn_literal_param, fn_string_param));
}
#[test]
fn test_fn_return_covariance_narrower_return_is_subtype() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let return_union = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let fn_return_string = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::STRING,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let fn_return_union = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: return_union,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(checker.is_subtype_of(fn_return_string, fn_return_union));
assert!(!checker.is_subtype_of(fn_return_union, fn_return_string));
}
#[test]
fn test_fn_return_covariance_literal_return() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let hello = interner.literal_string("hello");
let fn_return_literal = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: hello,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let fn_return_string = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::STRING,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(checker.is_subtype_of(fn_return_literal, fn_return_string));
}
#[test]
fn test_fn_return_covariance_never_return() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let fn_return_never = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::NEVER,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let fn_return_string = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::STRING,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let fn_return_number = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::NUMBER,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(checker.is_subtype_of(fn_return_never, fn_return_string));
assert!(checker.is_subtype_of(fn_return_never, fn_return_number));
}
#[test]
fn test_fn_return_covariance_object_return() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a_name = interner.intern_string("a");
let b_name = interner.intern_string("b");
let obj_a = interner.object(vec![PropertyInfo::new(a_name, TypeId::STRING)]);
let obj_ab = interner.object(vec![
PropertyInfo::new(a_name, TypeId::STRING),
PropertyInfo::new(b_name, TypeId::NUMBER),
]);
let fn_return_a = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: obj_a,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let fn_return_ab = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: obj_ab,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(checker.is_subtype_of(fn_return_ab, fn_return_a));
assert!(!checker.is_subtype_of(fn_return_a, fn_return_ab));
}
#[test]
fn test_fn_return_covariance_void_return() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let fn_return_undefined = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::UNDEFINED,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let fn_return_void = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(checker.is_subtype_of(fn_return_undefined, fn_return_void));
}
#[test]
fn test_fn_return_covariance_unknown_return() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let fn_return_string = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::STRING,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let fn_return_unknown = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::UNKNOWN,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(checker.is_subtype_of(fn_return_string, fn_return_unknown));
}
#[test]
fn test_fn_optional_param_fewer_params_is_subtype() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let fn_no_params = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let fn_optional_param = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: TypeId::STRING,
optional: true,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(checker.is_subtype_of(fn_no_params, fn_optional_param));
}
#[test]
fn test_fn_optional_param_required_to_optional() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let fn_required = 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::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let fn_optional = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: TypeId::STRING,
optional: true,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(!checker.is_subtype_of(fn_required, fn_optional));
}
#[test]
fn test_fn_optional_param_optional_to_required_is_subtype() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let fn_required = 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::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let fn_optional = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: TypeId::STRING,
optional: true,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(checker.is_subtype_of(fn_optional, fn_required));
}
#[test]
fn test_fn_optional_param_multiple_optional() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let fn_one_required = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("a")),
type_id: TypeId::STRING,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let fn_two_optional = interner.function(FunctionShape {
type_params: vec![],
params: vec![
ParamInfo {
name: Some(interner.intern_string("a")),
type_id: TypeId::STRING,
optional: true,
rest: false,
},
ParamInfo {
name: Some(interner.intern_string("b")),
type_id: TypeId::NUMBER,
optional: true,
rest: false,
},
],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(!checker.is_subtype_of(fn_one_required, fn_two_optional));
}
#[test]
fn test_fn_optional_param_mixed_required_optional() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let fn_both_required = interner.function(FunctionShape {
type_params: vec![],
params: vec![
ParamInfo {
name: Some(interner.intern_string("a")),
type_id: TypeId::STRING,
optional: false,
rest: false,
},
ParamInfo {
name: Some(interner.intern_string("b")),
type_id: TypeId::NUMBER,
optional: false,
rest: false,
},
],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let fn_one_optional = interner.function(FunctionShape {
type_params: vec![],
params: vec![
ParamInfo {
name: Some(interner.intern_string("a")),
type_id: TypeId::STRING,
optional: false,
rest: false,
},
ParamInfo {
name: Some(interner.intern_string("b")),
type_id: TypeId::NUMBER,
optional: true,
rest: false,
},
],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(!checker.is_subtype_of(fn_both_required, fn_one_optional));
}
#[test]
fn test_fn_optional_param_with_undefined_union() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let string_or_undefined = interner.union(vec![TypeId::STRING, TypeId::UNDEFINED]);
let fn_union_param = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: string_or_undefined,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let fn_optional_param = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: TypeId::STRING,
optional: true,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let _union_to_optional = checker.is_subtype_of(fn_union_param, fn_optional_param);
let _optional_to_union = checker.is_subtype_of(fn_optional_param, fn_union_param);
}
#[test]
fn test_fn_rest_param_basic() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let string_array = interner.array(TypeId::STRING);
let fn_rest = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("args")),
type_id: string_array,
optional: false,
rest: true,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let fn_no_params = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(checker.is_subtype_of(fn_no_params, fn_rest));
}
#[test]
fn test_fn_rest_param_fixed_params_to_rest() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let string_array = interner.array(TypeId::STRING);
let fn_two_strings = interner.function(FunctionShape {
type_params: vec![],
params: vec![
ParamInfo {
name: Some(interner.intern_string("a")),
type_id: TypeId::STRING,
optional: false,
rest: false,
},
ParamInfo {
name: Some(interner.intern_string("b")),
type_id: TypeId::STRING,
optional: false,
rest: false,
},
],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let fn_rest = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("args")),
type_id: string_array,
optional: false,
rest: true,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(checker.is_subtype_of(fn_two_strings, fn_rest));
}
#[test]
fn test_fn_rest_param_wider_element_type() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let string_array = interner.array(TypeId::STRING);
let unknown_array = interner.array(TypeId::UNKNOWN);
let fn_rest_string = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("args")),
type_id: string_array,
optional: false,
rest: true,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let fn_rest_unknown = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("args")),
type_id: unknown_array,
optional: false,
rest: true,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(checker.is_subtype_of(fn_rest_unknown, fn_rest_string));
}
#[test]
fn test_fn_rest_param_with_leading_params() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let number_array = interner.array(TypeId::NUMBER);
let fn_with_rest = interner.function(FunctionShape {
type_params: vec![],
params: vec![
ParamInfo {
name: Some(interner.intern_string("a")),
type_id: TypeId::STRING,
optional: false,
rest: false,
},
ParamInfo {
name: Some(interner.intern_string("rest")),
type_id: number_array,
optional: false,
rest: true,
},
],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let fn_just_string = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("a")),
type_id: TypeId::STRING,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(checker.is_subtype_of(fn_just_string, fn_with_rest));
}
#[test]
fn test_fn_rest_param_union_element_type() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let string_array = interner.array(TypeId::STRING);
let union_type = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let union_array = interner.array(union_type);
let fn_rest_string = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("args")),
type_id: string_array,
optional: false,
rest: true,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let fn_rest_union = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("args")),
type_id: union_array,
optional: false,
rest: true,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(checker.is_subtype_of(fn_rest_union, fn_rest_string));
}
#[test]
fn test_fn_rest_to_rest_same_type() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let string_array = interner.array(TypeId::STRING);
let fn_rest1 = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("args")),
type_id: string_array,
optional: false,
rest: true,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let fn_rest2 = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("args")),
type_id: string_array,
optional: false,
rest: true,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(checker.is_subtype_of(fn_rest1, fn_rest2));
assert!(checker.is_subtype_of(fn_rest2, fn_rest1));
}
#[test]
fn test_fn_rest_combined_with_optional() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let number_array = interner.array(TypeId::NUMBER);
let fn_optional_and_rest = interner.function(FunctionShape {
type_params: vec![],
params: vec![
ParamInfo {
name: Some(interner.intern_string("a")),
type_id: TypeId::STRING,
optional: true,
rest: false,
},
ParamInfo {
name: Some(interner.intern_string("rest")),
type_id: number_array,
optional: false,
rest: true,
},
],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let fn_no_params = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(checker.is_subtype_of(fn_no_params, fn_optional_and_rest));
}
#[test]
fn test_excess_property_structural_subtype() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a_name = interner.intern_string("a");
let b_name = interner.intern_string("b");
let obj_a = interner.object(vec![PropertyInfo::new(a_name, TypeId::STRING)]);
let obj_ab = interner.object(vec![
PropertyInfo::new(a_name, TypeId::STRING),
PropertyInfo::new(b_name, TypeId::NUMBER),
]);
assert!(checker.is_subtype_of(obj_ab, obj_a));
assert!(!checker.is_subtype_of(obj_a, obj_ab));
}
#[test]
fn test_excess_property_three_extra() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a_name = interner.intern_string("a");
let b_name = interner.intern_string("b");
let c_name = interner.intern_string("c");
let d_name = interner.intern_string("d");
let obj_a = interner.object(vec![PropertyInfo::new(a_name, TypeId::STRING)]);
let obj_abcd = interner.object(vec![
PropertyInfo::new(a_name, TypeId::STRING),
PropertyInfo::new(b_name, TypeId::NUMBER),
PropertyInfo::new(c_name, TypeId::BOOLEAN),
PropertyInfo::new(d_name, TypeId::STRING),
]);
assert!(checker.is_subtype_of(obj_abcd, obj_a));
}
#[test]
fn test_excess_property_different_required() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a_name = interner.intern_string("a");
let b_name = interner.intern_string("b");
let c_name = interner.intern_string("c");
let obj_ab = interner.object(vec![
PropertyInfo::new(a_name, TypeId::STRING),
PropertyInfo::new(b_name, TypeId::NUMBER),
]);
let obj_ac = interner.object(vec![
PropertyInfo::new(a_name, TypeId::STRING),
PropertyInfo::new(c_name, TypeId::BOOLEAN),
]);
assert!(!checker.is_subtype_of(obj_ab, obj_ac));
assert!(!checker.is_subtype_of(obj_ac, obj_ab));
}
#[test]
fn test_excess_property_with_method() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a_name = interner.intern_string("a");
let method_name = interner.intern_string("method");
let method = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let obj_a = interner.object(vec![PropertyInfo::new(a_name, TypeId::STRING)]);
let obj_a_method = interner.object(vec![
PropertyInfo::new(a_name, TypeId::STRING),
PropertyInfo::method(method_name, method),
]);
assert!(checker.is_subtype_of(obj_a_method, obj_a));
}
#[test]
fn test_excess_property_narrower_type() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a_name = interner.intern_string("a");
let hello = interner.literal_string("hello");
let obj_a_literal = interner.object(vec![PropertyInfo::new(a_name, hello)]);
let obj_a_string = interner.object(vec![PropertyInfo::new(a_name, TypeId::STRING)]);
assert!(checker.is_subtype_of(obj_a_literal, obj_a_string));
assert!(!checker.is_subtype_of(obj_a_string, obj_a_literal));
}
#[test]
fn test_excess_property_empty_object() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a_name = interner.intern_string("a");
let obj_a = interner.object(vec![PropertyInfo::new(a_name, TypeId::STRING)]);
let empty_obj = interner.object(vec![]);
assert!(checker.is_subtype_of(obj_a, empty_obj));
}
#[test]
fn test_optional_property_required_to_optional() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a_name = interner.intern_string("a");
let obj_required = interner.object(vec![PropertyInfo::new(a_name, TypeId::STRING)]);
let obj_optional = interner.object(vec![PropertyInfo::opt(a_name, TypeId::STRING)]);
assert!(checker.is_subtype_of(obj_required, obj_optional));
}
#[test]
fn test_optional_property_optional_to_required_not_subtype() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a_name = interner.intern_string("a");
let obj_required = interner.object(vec![PropertyInfo::new(a_name, TypeId::STRING)]);
let obj_optional = interner.object(vec![PropertyInfo::opt(a_name, TypeId::STRING)]);
assert!(!checker.is_subtype_of(obj_optional, obj_required));
}
#[test]
fn test_optional_property_missing_optional() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a_name = interner.intern_string("a");
let empty_obj = interner.object(vec![]);
let obj_optional = interner.object(vec![PropertyInfo::opt(a_name, TypeId::STRING)]);
assert!(checker.is_subtype_of(empty_obj, obj_optional));
}
#[test]
fn test_optional_property_mixed_required_optional() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a_name = interner.intern_string("a");
let b_name = interner.intern_string("b");
let obj_both_required = interner.object(vec![
PropertyInfo::new(a_name, TypeId::STRING),
PropertyInfo::new(b_name, TypeId::NUMBER),
]);
let obj_b_optional = interner.object(vec![
PropertyInfo::new(a_name, TypeId::STRING),
PropertyInfo::opt(b_name, TypeId::NUMBER),
]);
assert!(checker.is_subtype_of(obj_both_required, obj_b_optional));
}
#[test]
fn test_optional_property_all_optional() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a_name = interner.intern_string("a");
let b_name = interner.intern_string("b");
let obj = interner.object(vec![
PropertyInfo::opt(a_name, TypeId::STRING),
PropertyInfo::opt(b_name, TypeId::NUMBER),
]);
assert!(checker.is_subtype_of(obj, obj));
}
#[test]
fn test_optional_property_type_mismatch() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a_name = interner.intern_string("a");
let obj_optional_string = interner.object(vec![PropertyInfo::opt(a_name, TypeId::STRING)]);
let obj_optional_number = interner.object(vec![PropertyInfo::opt(a_name, TypeId::NUMBER)]);
assert!(!checker.is_subtype_of(obj_optional_string, obj_optional_number));
assert!(!checker.is_subtype_of(obj_optional_number, obj_optional_string));
}
#[test]
fn test_index_signature_string_basic() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let indexed_number = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: TypeId::NUMBER,
readonly: false,
}),
number_index: None,
});
let indexed_string = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: TypeId::STRING,
readonly: false,
}),
number_index: None,
});
assert!(!checker.is_subtype_of(indexed_number, indexed_string));
assert!(!checker.is_subtype_of(indexed_string, indexed_number));
}
#[test]
fn test_index_signature_covariant_value() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let hello = interner.literal_string("hello");
let indexed_literal = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: hello,
readonly: false,
}),
number_index: None,
});
let indexed_string = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: TypeId::STRING,
readonly: false,
}),
number_index: None,
});
assert!(checker.is_subtype_of(indexed_literal, indexed_string));
}
#[test]
fn test_index_signature_with_known_property() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a_name = interner.intern_string("a");
let indexed_with_prop = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![PropertyInfo::new(a_name, TypeId::STRING)],
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: TypeId::STRING,
readonly: false,
}),
number_index: None,
});
let indexed_only = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: TypeId::STRING,
readonly: false,
}),
number_index: None,
});
assert!(checker.is_subtype_of(indexed_with_prop, indexed_only));
}
#[test]
fn test_index_signature_number_index() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let number_indexed = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
string_index: None,
number_index: Some(IndexSignature {
key_type: TypeId::NUMBER,
value_type: TypeId::STRING,
readonly: false,
}),
});
let string_indexed = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: TypeId::STRING,
readonly: false,
}),
number_index: None,
});
let _result = checker.is_subtype_of(number_indexed, string_indexed);
}
#[test]
fn test_index_signature_union_value() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let union_value = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let indexed_union = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: union_value,
readonly: false,
}),
number_index: None,
});
let indexed_string = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: TypeId::STRING,
readonly: false,
}),
number_index: None,
});
assert!(checker.is_subtype_of(indexed_string, indexed_union));
}
#[test]
fn test_index_signature_object_to_indexed() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a_name = interner.intern_string("a");
let b_name = interner.intern_string("b");
let obj_ab = interner.object(vec![
PropertyInfo::new(a_name, TypeId::STRING),
PropertyInfo::new(b_name, TypeId::STRING),
]);
let indexed = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: TypeId::STRING,
readonly: false,
}),
number_index: None,
});
assert!(checker.is_subtype_of(obj_ab, indexed));
}
#[test]
fn test_readonly_mutable_to_readonly() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a_name = interner.intern_string("a");
let obj_mutable = interner.object(vec![PropertyInfo::new(a_name, TypeId::STRING)]);
let obj_readonly = interner.object(vec![PropertyInfo::readonly(a_name, TypeId::STRING)]);
assert!(checker.is_subtype_of(obj_mutable, obj_readonly));
}
#[test]
fn test_readonly_to_mutable() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a_name = interner.intern_string("a");
let obj_mutable = interner.object(vec![PropertyInfo::new(a_name, TypeId::STRING)]);
let obj_readonly = interner.object(vec![PropertyInfo::readonly(a_name, TypeId::STRING)]);
let _readonly_to_mutable = checker.is_subtype_of(obj_readonly, obj_mutable);
let _mutable_to_readonly = checker.is_subtype_of(obj_mutable, obj_readonly);
}
#[test]
fn test_readonly_both_readonly() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a_name = interner.intern_string("a");
let obj_readonly = interner.object(vec![PropertyInfo::readonly(a_name, TypeId::STRING)]);
assert!(checker.is_subtype_of(obj_readonly, obj_readonly));
}
#[test]
fn test_readonly_mixed_properties() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a_name = interner.intern_string("a");
let b_name = interner.intern_string("b");
let obj = interner.object(vec![
PropertyInfo::new(a_name, TypeId::STRING),
PropertyInfo::readonly(b_name, TypeId::NUMBER),
]);
assert!(checker.is_subtype_of(obj, obj));
}
#[test]
fn test_readonly_narrower_type() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a_name = interner.intern_string("a");
let hello = interner.literal_string("hello");
let obj_literal = interner.object(vec![PropertyInfo::readonly(a_name, hello)]);
let obj_string = interner.object(vec![PropertyInfo::readonly(a_name, TypeId::STRING)]);
assert!(checker.is_subtype_of(obj_literal, obj_string));
}
#[test]
fn test_readonly_with_optional() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a_name = interner.intern_string("a");
let obj_readonly_optional = interner.object(vec![PropertyInfo {
name: a_name,
type_id: TypeId::STRING,
write_type: TypeId::STRING,
optional: true,
readonly: true,
is_method: false,
visibility: Visibility::Public,
parent_id: None,
}]);
let obj_readonly_required =
interner.object(vec![PropertyInfo::readonly(a_name, TypeId::STRING)]);
assert!(checker.is_subtype_of(obj_readonly_required, obj_readonly_optional));
assert!(!checker.is_subtype_of(obj_readonly_optional, obj_readonly_required));
}
#[test]
fn test_readonly_array_like() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let length_name = interner.intern_string("length");
let readonly_array_like =
interner.object(vec![PropertyInfo::readonly(length_name, TypeId::NUMBER)]);
let mutable_array_like = interner.object(vec![PropertyInfo::new(length_name, TypeId::NUMBER)]);
assert!(checker.is_subtype_of(mutable_array_like, readonly_array_like));
}
#[test]
fn test_readonly_method_property() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let method_name = interner.intern_string("method");
let method = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let obj_readonly_method = interner.object(vec![PropertyInfo {
name: method_name,
type_id: method,
write_type: method,
optional: false,
readonly: true,
is_method: true,
visibility: Visibility::Public,
parent_id: None,
}]);
let obj_mutable_method = interner.object(vec![PropertyInfo::method(method_name, method)]);
assert!(checker.is_subtype_of(obj_mutable_method, obj_readonly_method));
}
#[test]
fn test_tuple_fixed_same_length_same_types() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let tuple1 = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
},
]);
let tuple2 = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
},
]);
assert!(checker.is_subtype_of(tuple1, tuple2));
assert!(checker.is_subtype_of(tuple2, tuple1));
}
#[test]
fn test_tuple_fixed_covariant_elements() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let hello = interner.literal_string("hello");
let forty_two = interner.literal_number(42.0);
let literal_tuple = interner.tuple(vec![
TupleElement {
type_id: hello,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: forty_two,
name: None,
optional: false,
rest: false,
},
]);
let wide_tuple = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
},
]);
assert!(checker.is_subtype_of(literal_tuple, wide_tuple));
assert!(!checker.is_subtype_of(wide_tuple, literal_tuple));
}
#[test]
fn test_tuple_fixed_different_lengths_not_subtype() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let tuple_3 = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::BOOLEAN,
name: None,
optional: false,
rest: false,
},
]);
let tuple_2 = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
},
]);
assert!(!checker.is_subtype_of(tuple_3, tuple_2));
assert!(!checker.is_subtype_of(tuple_2, tuple_3));
}
#[test]
fn test_tuple_fixed_type_mismatch() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let tuple_ss = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
]);
let tuple_sn = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
},
]);
assert!(!checker.is_subtype_of(tuple_ss, tuple_sn));
assert!(!checker.is_subtype_of(tuple_sn, tuple_ss));
}
#[test]
fn test_tuple_fixed_empty_tuple() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let empty_tuple = interner.tuple(vec![]);
assert!(checker.is_subtype_of(empty_tuple, empty_tuple));
}
#[test]
fn test_tuple_fixed_single_element() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let single = interner.tuple(vec![TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
}]);
assert!(checker.is_subtype_of(single, single));
}
#[test]
fn test_tuple_fixed_union_element() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let union = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let tuple_union = interner.tuple(vec![TupleElement {
type_id: union,
name: None,
optional: false,
rest: false,
}]);
let tuple_string = interner.tuple(vec![TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
}]);
assert!(checker.is_subtype_of(tuple_string, tuple_union));
assert!(!checker.is_subtype_of(tuple_union, tuple_string));
}
#[test]
fn test_tuple_rest_basic() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let number_array = interner.array(TypeId::NUMBER);
let tuple_with_rest = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: number_array,
name: None,
optional: false,
rest: true,
},
]);
let tuple_string_number = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
},
]);
assert!(checker.is_subtype_of(tuple_string_number, tuple_with_rest));
}
#[test]
fn test_tuple_rest_accepts_multiple() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let number_array = interner.array(TypeId::NUMBER);
let tuple_with_rest = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: number_array,
name: None,
optional: false,
rest: true,
},
]);
let tuple_four = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
},
]);
assert!(checker.is_subtype_of(tuple_four, tuple_with_rest));
}
#[test]
fn test_tuple_rest_accepts_zero() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let number_array = interner.array(TypeId::NUMBER);
let tuple_with_rest = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: number_array,
name: None,
optional: false,
rest: true,
},
]);
let tuple_one = interner.tuple(vec![TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
}]);
assert!(checker.is_subtype_of(tuple_one, tuple_with_rest));
}
#[test]
fn test_tuple_rest_type_mismatch() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let number_array = interner.array(TypeId::NUMBER);
let tuple_with_rest = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: number_array,
name: None,
optional: false,
rest: true,
},
]);
let tuple_bool = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::BOOLEAN,
name: None,
optional: false,
rest: false,
},
]);
assert!(!checker.is_subtype_of(tuple_bool, tuple_with_rest));
}
#[test]
fn test_tuple_rest_to_rest() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let string_array = interner.array(TypeId::STRING);
let tuple_rest1 = interner.tuple(vec![TupleElement {
type_id: string_array,
name: None,
optional: false,
rest: true,
}]);
let tuple_rest2 = interner.tuple(vec![TupleElement {
type_id: string_array,
name: None,
optional: false,
rest: true,
}]);
assert!(checker.is_subtype_of(tuple_rest1, tuple_rest2));
assert!(checker.is_subtype_of(tuple_rest2, tuple_rest1));
}
#[test]
fn test_tuple_rest_covariant() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let hello = interner.literal_string("hello");
let hello_array = interner.array(hello);
let string_array = interner.array(TypeId::STRING);
let tuple_literal_rest = interner.tuple(vec![TupleElement {
type_id: hello_array,
name: None,
optional: false,
rest: true,
}]);
let tuple_string_rest = interner.tuple(vec![TupleElement {
type_id: string_array,
name: None,
optional: false,
rest: true,
}]);
assert!(checker.is_subtype_of(tuple_literal_rest, tuple_string_rest));
}
#[test]
fn test_tuple_rest_middle_position() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let number_array = interner.array(TypeId::NUMBER);
let tuple_middle_rest = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: number_array,
name: None,
optional: false,
rest: true,
},
TupleElement {
type_id: TypeId::BOOLEAN,
name: None,
optional: false,
rest: false,
},
]);
let tuple_three = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::BOOLEAN,
name: None,
optional: false,
rest: false,
},
]);
assert!(checker.is_subtype_of(tuple_three, tuple_middle_rest));
}
#[test]
fn test_tuple_optional_basic() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let tuple_optional = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: true,
rest: false,
},
]);
let tuple_one = interner.tuple(vec![TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
}]);
assert!(checker.is_subtype_of(tuple_one, tuple_optional));
}
#[test]
fn test_tuple_optional_provided() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let tuple_optional = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: true,
rest: false,
},
]);
let tuple_both = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
},
]);
assert!(checker.is_subtype_of(tuple_both, tuple_optional));
}
#[test]
fn test_tuple_optional_all_optional() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let tuple_all_optional = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: true,
rest: false,
},
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: true,
rest: false,
},
]);
let empty_tuple = interner.tuple(vec![]);
assert!(checker.is_subtype_of(empty_tuple, tuple_all_optional));
}
#[test]
fn test_tuple_optional_type_mismatch() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let tuple_optional_number = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: true,
rest: false,
},
]);
let tuple_with_bool = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::BOOLEAN,
name: None,
optional: false,
rest: false,
},
]);
assert!(!checker.is_subtype_of(tuple_with_bool, tuple_optional_number));
}
#[test]
fn test_tuple_optional_required_to_optional() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let tuple_required = interner.tuple(vec![TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
}]);
let tuple_optional = interner.tuple(vec![TupleElement {
type_id: TypeId::STRING,
name: None,
optional: true,
rest: false,
}]);
assert!(checker.is_subtype_of(tuple_required, tuple_optional));
}
#[test]
fn test_tuple_optional_to_required_not_subtype() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let tuple_required = interner.tuple(vec![TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
}]);
let tuple_optional = interner.tuple(vec![TupleElement {
type_id: TypeId::STRING,
name: None,
optional: true,
rest: false,
}]);
assert!(!checker.is_subtype_of(tuple_optional, tuple_required));
}
#[test]
fn test_tuple_optional_multiple() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let tuple_multi_optional = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: true,
rest: false,
},
TupleElement {
type_id: TypeId::BOOLEAN,
name: None,
optional: true,
rest: false,
},
]);
let tuple_one = interner.tuple(vec![TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
}]);
let tuple_two = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
},
]);
assert!(checker.is_subtype_of(tuple_one, tuple_multi_optional));
assert!(checker.is_subtype_of(tuple_two, tuple_multi_optional));
}
#[test]
fn test_tuple_labeled_same_labels() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let x_name = interner.intern_string("x");
let y_name = interner.intern_string("y");
let tuple1 = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: Some(x_name),
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::NUMBER,
name: Some(y_name),
optional: false,
rest: false,
},
]);
let tuple2 = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: Some(x_name),
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::NUMBER,
name: Some(y_name),
optional: false,
rest: false,
},
]);
assert!(checker.is_subtype_of(tuple1, tuple2));
assert!(checker.is_subtype_of(tuple2, tuple1));
}
#[test]
fn test_tuple_labeled_to_unlabeled() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let x_name = interner.intern_string("x");
let y_name = interner.intern_string("y");
let labeled = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: Some(x_name),
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::NUMBER,
name: Some(y_name),
optional: false,
rest: false,
},
]);
let unlabeled = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
},
]);
assert!(checker.is_subtype_of(labeled, unlabeled));
assert!(checker.is_subtype_of(unlabeled, labeled));
}
#[test]
fn test_tuple_labeled_different_labels() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a_name = interner.intern_string("a");
let b_name = interner.intern_string("b");
let x_name = interner.intern_string("x");
let y_name = interner.intern_string("y");
let tuple_ab = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: Some(a_name),
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::NUMBER,
name: Some(b_name),
optional: false,
rest: false,
},
]);
let tuple_xy = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: Some(x_name),
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::NUMBER,
name: Some(y_name),
optional: false,
rest: false,
},
]);
assert!(checker.is_subtype_of(tuple_ab, tuple_xy));
assert!(checker.is_subtype_of(tuple_xy, tuple_ab));
}
#[test]
fn test_tuple_labeled_optional() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let x_name = interner.intern_string("x");
let y_name = interner.intern_string("y");
let labeled_optional = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: Some(x_name),
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::NUMBER,
name: Some(y_name),
optional: true,
rest: false,
},
]);
let labeled_one = interner.tuple(vec![TupleElement {
type_id: TypeId::STRING,
name: Some(x_name),
optional: false,
rest: false,
}]);
assert!(checker.is_subtype_of(labeled_one, labeled_optional));
}
#[test]
fn test_tuple_labeled_rest() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let x_name = interner.intern_string("x");
let rest_name = interner.intern_string("rest");
let number_array = interner.array(TypeId::NUMBER);
let labeled_rest = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: Some(x_name),
optional: false,
rest: false,
},
TupleElement {
type_id: number_array,
name: Some(rest_name),
optional: false,
rest: true,
},
]);
let labeled_two = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: Some(x_name),
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
},
]);
assert!(checker.is_subtype_of(labeled_two, labeled_rest));
}
#[test]
fn test_tuple_labeled_covariant() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let x_name = interner.intern_string("x");
let hello = interner.literal_string("hello");
let literal_labeled = interner.tuple(vec![TupleElement {
type_id: hello,
name: Some(x_name),
optional: false,
rest: false,
}]);
let string_labeled = interner.tuple(vec![TupleElement {
type_id: TypeId::STRING,
name: Some(x_name),
optional: false,
rest: false,
}]);
assert!(checker.is_subtype_of(literal_labeled, string_labeled));
}
#[test]
fn test_tuple_labeled_mixed() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let x_name = interner.intern_string("x");
let y_name = interner.intern_string("y");
let mixed = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: Some(x_name),
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::BOOLEAN,
name: Some(y_name),
optional: false,
rest: false,
},
]);
let all_unlabeled = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::BOOLEAN,
name: None,
optional: false,
rest: false,
},
]);
assert!(checker.is_subtype_of(mixed, all_unlabeled));
assert!(checker.is_subtype_of(all_unlabeled, mixed));
}
#[test]
fn test_class_inheritance_derived_extends_base() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let base_prop = interner.intern_string("base");
let derived_prop = interner.intern_string("derived");
let base = interner.object(vec![PropertyInfo::new(base_prop, TypeId::STRING)]);
let derived = interner.object(vec![
PropertyInfo::new(base_prop, TypeId::STRING),
PropertyInfo::new(derived_prop, TypeId::NUMBER),
]);
assert!(checker.is_subtype_of(derived, base));
assert!(!checker.is_subtype_of(base, derived));
}
#[test]
fn test_class_inheritance_multi_level() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a_prop = interner.intern_string("a");
let b_prop = interner.intern_string("b");
let c_prop = interner.intern_string("c");
let class_a = interner.object(vec![PropertyInfo::new(a_prop, TypeId::STRING)]);
let class_b = interner.object(vec![
PropertyInfo::new(a_prop, TypeId::STRING),
PropertyInfo::new(b_prop, TypeId::NUMBER),
]);
let class_c = interner.object(vec![
PropertyInfo::new(a_prop, TypeId::STRING),
PropertyInfo::new(b_prop, TypeId::NUMBER),
PropertyInfo::new(c_prop, TypeId::BOOLEAN),
]);
assert!(checker.is_subtype_of(class_c, class_b));
assert!(checker.is_subtype_of(class_b, class_a));
assert!(checker.is_subtype_of(class_c, class_a));
assert!(!checker.is_subtype_of(class_a, class_b));
assert!(!checker.is_subtype_of(class_b, class_c));
}
#[test]
fn test_class_inheritance_method_override() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let method_name = interner.intern_string("method");
let hello = interner.literal_string("hello");
let base_method = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::STRING,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let derived_method = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: hello,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let base = interner.object(vec![PropertyInfo::method(method_name, base_method)]);
let derived = interner.object(vec![PropertyInfo::method(method_name, derived_method)]);
assert!(checker.is_subtype_of(derived, base));
}
#[test]
fn test_class_inheritance_same_structure() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let prop = interner.intern_string("value");
let class1 = interner.object(vec![PropertyInfo::new(prop, TypeId::STRING)]);
let class2 = interner.object(vec![PropertyInfo::new(prop, TypeId::STRING)]);
assert!(checker.is_subtype_of(class1, class2));
assert!(checker.is_subtype_of(class2, class1));
}
#[test]
fn test_class_inheritance_property_type_mismatch() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let prop = interner.intern_string("value");
let class1 = interner.object(vec![PropertyInfo::new(prop, TypeId::STRING)]);
let class2 = interner.object(vec![PropertyInfo::new(prop, TypeId::NUMBER)]);
assert!(!checker.is_subtype_of(class1, class2));
assert!(!checker.is_subtype_of(class2, class1));
}
#[test]
fn test_class_inheritance_with_constructor() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let name_prop = interner.intern_string("name");
let age_prop = interner.intern_string("age");
let person = interner.object(vec![
PropertyInfo::new(name_prop, TypeId::STRING),
PropertyInfo::new(age_prop, TypeId::NUMBER),
]);
let employee = interner.object(vec![
PropertyInfo::new(name_prop, TypeId::STRING),
PropertyInfo::new(age_prop, TypeId::NUMBER),
PropertyInfo::new(interner.intern_string("employeeId"), TypeId::STRING),
]);
assert!(checker.is_subtype_of(employee, person));
assert!(!checker.is_subtype_of(person, employee));
}
#[test]
fn test_class_inheritance_diamond() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a_prop = interner.intern_string("a");
let b_prop = interner.intern_string("b");
let c_prop = interner.intern_string("c");
let d_prop = interner.intern_string("d");
let class_a = interner.object(vec![PropertyInfo::new(a_prop, TypeId::STRING)]);
let class_d = interner.object(vec![
PropertyInfo::new(a_prop, TypeId::STRING),
PropertyInfo::new(b_prop, TypeId::NUMBER),
PropertyInfo::new(c_prop, TypeId::BOOLEAN),
PropertyInfo::new(d_prop, TypeId::STRING),
]);
assert!(checker.is_subtype_of(class_d, class_a));
}
#[test]
fn test_implements_simple_interface() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let greet = interner.intern_string("greet");
let greet_method = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::STRING,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let interface = interner.object(vec![PropertyInfo::method(greet, greet_method)]);
let class_impl = interner.object(vec![
PropertyInfo::method(greet, greet_method),
PropertyInfo::new(interner.intern_string("name"), TypeId::STRING),
]);
assert!(checker.is_subtype_of(class_impl, interface));
}
#[test]
fn test_implements_multiple_interfaces() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a_method_name = interner.intern_string("a");
let b_method_name = interner.intern_string("b");
let void_method = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let interface_a = interner.object(vec![PropertyInfo::method(a_method_name, void_method)]);
let interface_b = interner.object(vec![PropertyInfo::method(b_method_name, void_method)]);
let class_c = interner.object(vec![
PropertyInfo::method(a_method_name, void_method),
PropertyInfo::method(b_method_name, void_method),
]);
assert!(checker.is_subtype_of(class_c, interface_a));
assert!(checker.is_subtype_of(class_c, interface_b));
}
#[test]
fn test_implements_missing_method() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let required = interner.intern_string("required");
let void_method = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let interface = interner.object(vec![PropertyInfo::method(required, void_method)]);
let class_c = interner.object(vec![]);
assert!(!checker.is_subtype_of(class_c, interface));
}
#[test]
fn test_implements_optional_method() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let optional = interner.intern_string("optional");
let void_method = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let interface = interner.object(vec![PropertyInfo {
name: optional,
type_id: void_method,
write_type: void_method,
optional: true,
readonly: false,
is_method: true,
visibility: Visibility::Public,
parent_id: None,
}]);
let class_c = interner.object(vec![]);
assert!(checker.is_subtype_of(class_c, interface));
}
#[test]
fn test_implements_wrong_signature() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let method_name = interner.intern_string("method");
let interface_method = 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::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let class_method = 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::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let interface = interner.object(vec![PropertyInfo::method(method_name, interface_method)]);
let class_c = interner.object(vec![PropertyInfo::method(method_name, class_method)]);
assert!(!checker.is_subtype_of(class_c, interface));
}
#[test]
fn test_implements_interface_extends_interface() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a_prop = interner.intern_string("a");
let b_prop = interner.intern_string("b");
let interface_a = interner.object(vec![PropertyInfo::new(a_prop, TypeId::STRING)]);
let interface_b = interner.object(vec![
PropertyInfo::new(a_prop, TypeId::STRING),
PropertyInfo::new(b_prop, TypeId::NUMBER),
]);
let class_c = interner.object(vec![
PropertyInfo::new(a_prop, TypeId::STRING),
PropertyInfo::new(b_prop, TypeId::NUMBER),
]);
assert!(checker.is_subtype_of(class_c, interface_a));
assert!(checker.is_subtype_of(class_c, interface_b));
}
#[test]
fn test_implements_property_with_getter() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let value = interner.intern_string("value");
let interface = interner.object(vec![PropertyInfo::readonly(value, TypeId::STRING)]);
let class_c = interner.object(vec![PropertyInfo::readonly(value, TypeId::STRING)]);
assert!(checker.is_subtype_of(class_c, interface));
}
#[test]
fn test_abstract_class_with_abstract_method() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let method_name = interner.intern_string("method");
let void_method = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let abstract_base = interner.object(vec![PropertyInfo::method(method_name, void_method)]);
let derived = interner.object(vec![PropertyInfo::method(method_name, void_method)]);
assert!(checker.is_subtype_of(derived, abstract_base));
}
#[test]
fn test_abstract_class_with_concrete_method() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let concrete_name = interner.intern_string("concrete");
let abstract_name = interner.intern_string("abs");
let string_method = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::STRING,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let void_method = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let abstract_base = interner.object(vec![
PropertyInfo::method(concrete_name, string_method),
PropertyInfo::method(abstract_name, void_method),
]);
let derived = interner.object(vec![
PropertyInfo::method(concrete_name, string_method),
PropertyInfo::method(abstract_name, void_method),
]);
assert!(checker.is_subtype_of(derived, abstract_base));
}
#[test]
fn test_abstract_class_to_abstract_class() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a_method = interner.intern_string("a");
let b_method = interner.intern_string("b");
let void_method = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let abstract_a = interner.object(vec![PropertyInfo::method(a_method, void_method)]);
let abstract_b = interner.object(vec![
PropertyInfo::method(a_method, void_method),
PropertyInfo::method(b_method, void_method),
]);
assert!(checker.is_subtype_of(abstract_b, abstract_a));
assert!(!checker.is_subtype_of(abstract_a, abstract_b));
}
#[test]
fn test_abstract_class_with_property() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let value = interner.intern_string("value");
let abstract_base = interner.object(vec![PropertyInfo::new(value, TypeId::STRING)]);
let hello = interner.literal_string("hello");
let derived = interner.object(vec![PropertyInfo::new(value, hello)]);
assert!(checker.is_subtype_of(derived, abstract_base));
}
#[test]
fn test_abstract_class_generic_method() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let process = interner.intern_string("process");
let string_process = 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::STRING,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let number_process = 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::NUMBER,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let base_string = interner.object(vec![PropertyInfo::method(process, string_process)]);
let base_number = interner.object(vec![PropertyInfo::method(process, number_process)]);
assert!(!checker.is_subtype_of(base_string, base_number));
assert!(!checker.is_subtype_of(base_number, base_string));
}
#[test]
fn test_abstract_class_missing_implementation() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let method_name = interner.intern_string("method");
let concrete_name = interner.intern_string("concrete");
let void_method = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let string_method = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::STRING,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let abstract_base = interner.object(vec![
PropertyInfo::method(method_name, void_method),
PropertyInfo::method(concrete_name, string_method),
]);
let incomplete = interner.object(vec![PropertyInfo::method(concrete_name, string_method)]);
assert!(!checker.is_subtype_of(incomplete, abstract_base));
}
#[test]
fn test_abstract_class_protected_member() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let value = interner.intern_string("value");
let base = interner.object(vec![PropertyInfo::new(value, TypeId::STRING)]);
let derived = interner.object(vec![PropertyInfo::new(value, TypeId::STRING)]);
assert!(checker.is_subtype_of(derived, base));
assert!(checker.is_subtype_of(base, derived));
}
#[test]
fn test_private_member_brand_pattern() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let brand_a = interner.intern_string("__brand_a");
let brand_b = interner.intern_string("__brand_b");
let value = interner.intern_string("value");
let class_a = interner.object(vec![
PropertyInfo::new(brand_a, TypeId::VOID),
PropertyInfo::new(value, TypeId::STRING),
]);
let class_b = interner.object(vec![
PropertyInfo::new(brand_b, TypeId::VOID),
PropertyInfo::new(value, TypeId::STRING),
]);
assert!(!checker.is_subtype_of(class_a, class_b));
assert!(!checker.is_subtype_of(class_b, class_a));
}
#[test]
fn test_private_member_same_brand() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let brand = interner.intern_string("__brand");
let value = interner.intern_string("value");
let class1 = interner.object(vec![
PropertyInfo::new(brand, TypeId::VOID),
PropertyInfo::new(value, TypeId::STRING),
]);
let class2 = interner.object(vec![
PropertyInfo::new(brand, TypeId::VOID),
PropertyInfo::new(value, TypeId::STRING),
]);
assert!(checker.is_subtype_of(class1, class2));
assert!(checker.is_subtype_of(class2, class1));
}
#[test]
fn test_private_member_derived_inherits_brand() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let brand = interner.intern_string("__brand");
let value = interner.intern_string("value");
let extra = interner.intern_string("extra");
let base = interner.object(vec![
PropertyInfo::new(brand, TypeId::VOID),
PropertyInfo::new(value, TypeId::STRING),
]);
let derived = interner.object(vec![
PropertyInfo::new(brand, TypeId::VOID),
PropertyInfo::new(value, TypeId::STRING),
PropertyInfo::new(extra, TypeId::NUMBER),
]);
assert!(checker.is_subtype_of(derived, base));
assert!(!checker.is_subtype_of(base, derived));
}
#[test]
fn test_private_member_missing_brand() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let brand = interner.intern_string("__brand");
let value = interner.intern_string("value");
let class_a = interner.object(vec![
PropertyInfo::new(brand, TypeId::VOID),
PropertyInfo::new(value, TypeId::STRING),
]);
let plain_object = interner.object(vec![PropertyInfo::new(value, TypeId::STRING)]);
assert!(checker.is_subtype_of(class_a, plain_object));
assert!(!checker.is_subtype_of(plain_object, class_a));
}
#[test]
fn test_private_member_unique_symbol_brand() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let brand = interner.intern_string("__brand");
let value = interner.intern_string("value");
let brand_a_type = interner.literal_string("brand_a");
let brand_b_type = interner.literal_string("brand_b");
let class_a = interner.object(vec![
PropertyInfo::new(brand, brand_a_type),
PropertyInfo::new(value, TypeId::STRING),
]);
let class_b = interner.object(vec![
PropertyInfo::new(brand, brand_b_type),
PropertyInfo::new(value, TypeId::STRING),
]);
assert!(!checker.is_subtype_of(class_a, class_b));
assert!(!checker.is_subtype_of(class_b, class_a));
}
#[test]
fn test_private_member_readonly_brand() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let brand = interner.intern_string("__brand");
let value = interner.intern_string("value");
let class_readonly = interner.object(vec![
PropertyInfo::readonly(brand, TypeId::VOID),
PropertyInfo::new(value, TypeId::STRING),
]);
let class_writable = interner.object(vec![
PropertyInfo::new(brand, TypeId::VOID),
PropertyInfo::new(value, TypeId::STRING),
]);
assert!(checker.is_subtype_of(class_writable, class_readonly));
}
#[test]
fn test_private_multiple_brands() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let brand1 = interner.intern_string("__brand1");
let brand2 = interner.intern_string("__brand2");
let value = interner.intern_string("value");
let class_both = interner.object(vec![
PropertyInfo::new(brand1, TypeId::VOID),
PropertyInfo::new(brand2, TypeId::VOID),
PropertyInfo::new(value, TypeId::STRING),
]);
let class_one = interner.object(vec![
PropertyInfo::new(brand1, TypeId::VOID),
PropertyInfo::new(value, TypeId::STRING),
]);
assert!(checker.is_subtype_of(class_both, class_one));
assert!(!checker.is_subtype_of(class_one, class_both));
}
#[test]
fn test_private_member_method_brand() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let brand_method = interner.intern_string("__isFoo");
let true_return = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::BOOLEAN,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let class_foo = interner.object(vec![PropertyInfo::method(brand_method, true_return)]);
let class_bar = interner.object(vec![]);
assert!(!checker.is_subtype_of(class_bar, class_foo));
assert!(checker.is_subtype_of(class_foo, class_bar));
}
#[test]
fn test_interface_extends_single() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a_prop = interner.intern_string("a");
let b_prop = interner.intern_string("b");
let interface_a = interner.object(vec![PropertyInfo::new(a_prop, TypeId::STRING)]);
let interface_b = interner.object(vec![
PropertyInfo::new(a_prop, TypeId::STRING),
PropertyInfo::new(b_prop, TypeId::NUMBER),
]);
assert!(checker.is_subtype_of(interface_b, interface_a));
assert!(!checker.is_subtype_of(interface_a, interface_b));
}
#[test]
fn test_interface_extends_chain() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a_prop = interner.intern_string("a");
let b_prop = interner.intern_string("b");
let c_prop = interner.intern_string("c");
let interface_a = interner.object(vec![PropertyInfo::new(a_prop, TypeId::STRING)]);
let interface_b = interner.object(vec![
PropertyInfo::new(a_prop, TypeId::STRING),
PropertyInfo::new(b_prop, TypeId::NUMBER),
]);
let interface_c = interner.object(vec![
PropertyInfo::new(a_prop, TypeId::STRING),
PropertyInfo::new(b_prop, TypeId::NUMBER),
PropertyInfo::new(c_prop, TypeId::BOOLEAN),
]);
assert!(checker.is_subtype_of(interface_c, interface_b));
assert!(checker.is_subtype_of(interface_b, interface_a));
assert!(checker.is_subtype_of(interface_c, interface_a));
}
#[test]
fn test_interface_extends_with_method() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let method_name = interner.intern_string("method");
let other_name = interner.intern_string("other");
let void_method = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let string_method = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::STRING,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let interface_a = interner.object(vec![PropertyInfo::method(method_name, void_method)]);
let interface_b = interner.object(vec![
PropertyInfo::method(method_name, void_method),
PropertyInfo::method(other_name, string_method),
]);
assert!(checker.is_subtype_of(interface_b, interface_a));
}
#[test]
fn test_interface_extends_override_method() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let method_name = interner.intern_string("method");
let hello = interner.literal_string("hello");
let string_method = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::STRING,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let hello_method = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: hello,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let interface_a = interner.object(vec![PropertyInfo::method(method_name, string_method)]);
let interface_b = interner.object(vec![PropertyInfo::method(method_name, hello_method)]);
assert!(checker.is_subtype_of(interface_b, interface_a));
}
#[test]
fn test_interface_extends_property_override() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let value = interner.intern_string("value");
let string_or_number = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let interface_a = interner.object(vec![PropertyInfo::new(value, string_or_number)]);
let interface_b = interner.object(vec![PropertyInfo::new(value, TypeId::STRING)]);
assert!(checker.is_subtype_of(interface_b, interface_a));
}
#[test]
fn test_interface_extends_optional_to_required() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let value = interner.intern_string("value");
let interface_a = interner.object(vec![PropertyInfo::opt(value, TypeId::STRING)]);
let interface_b = interner.object(vec![PropertyInfo::new(value, TypeId::STRING)]);
assert!(checker.is_subtype_of(interface_b, interface_a));
}
#[test]
fn test_interface_extends_readonly_property() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let value = interner.intern_string("value");
let interface_a = interner.object(vec![PropertyInfo::readonly(value, TypeId::STRING)]);
let interface_b = interner.object(vec![PropertyInfo::new(value, TypeId::STRING)]);
assert!(checker.is_subtype_of(interface_b, interface_a));
}
#[test]
fn test_interface_extends_multiple() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a_prop = interner.intern_string("a");
let b_prop = interner.intern_string("b");
let c_prop = interner.intern_string("c");
let interface_a = interner.object(vec![PropertyInfo::new(a_prop, TypeId::STRING)]);
let interface_b = interner.object(vec![PropertyInfo::new(b_prop, TypeId::NUMBER)]);
let interface_c = interner.object(vec![
PropertyInfo::new(a_prop, TypeId::STRING),
PropertyInfo::new(b_prop, TypeId::NUMBER),
PropertyInfo::new(c_prop, TypeId::BOOLEAN),
]);
assert!(checker.is_subtype_of(interface_c, interface_a));
assert!(checker.is_subtype_of(interface_c, interface_b));
}
#[test]
fn test_interface_extends_multiple_with_overlap() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let shared = interner.intern_string("shared");
let a_prop = interner.intern_string("a");
let b_prop = interner.intern_string("b");
let interface_a = interner.object(vec![
PropertyInfo::new(shared, TypeId::STRING),
PropertyInfo::new(a_prop, TypeId::NUMBER),
]);
let interface_b = interner.object(vec![
PropertyInfo::new(shared, TypeId::STRING),
PropertyInfo::new(b_prop, TypeId::BOOLEAN),
]);
let interface_c = interner.object(vec![
PropertyInfo::new(shared, TypeId::STRING),
PropertyInfo::new(a_prop, TypeId::NUMBER),
PropertyInfo::new(b_prop, TypeId::BOOLEAN),
]);
assert!(checker.is_subtype_of(interface_c, interface_a));
assert!(checker.is_subtype_of(interface_c, interface_b));
}
#[test]
fn test_interface_extends_multiple_methods() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let read = interner.intern_string("read");
let write = interner.intern_string("write");
let read_method = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::STRING,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let write_method = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("s")),
type_id: TypeId::STRING,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let readable = interner.object(vec![PropertyInfo::method(read, read_method)]);
let writable = interner.object(vec![PropertyInfo::method(write, write_method)]);
let read_writable = interner.object(vec![
PropertyInfo::method(read, read_method),
PropertyInfo::method(write, write_method),
]);
assert!(checker.is_subtype_of(read_writable, readable));
assert!(checker.is_subtype_of(read_writable, writable));
}
#[test]
fn test_interface_diamond_extends() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a_prop = interner.intern_string("a");
let b_prop = interner.intern_string("b");
let c_prop = interner.intern_string("c");
let interface_a = interner.object(vec![PropertyInfo::new(a_prop, TypeId::STRING)]);
let interface_b = interner.object(vec![
PropertyInfo::new(a_prop, TypeId::STRING),
PropertyInfo::new(b_prop, TypeId::NUMBER),
]);
let interface_c = interner.object(vec![
PropertyInfo::new(a_prop, TypeId::STRING),
PropertyInfo::new(c_prop, TypeId::BOOLEAN),
]);
let interface_d = interner.object(vec![
PropertyInfo::new(a_prop, TypeId::STRING),
PropertyInfo::new(b_prop, TypeId::NUMBER),
PropertyInfo::new(c_prop, TypeId::BOOLEAN),
]);
assert!(checker.is_subtype_of(interface_d, interface_a));
assert!(checker.is_subtype_of(interface_d, interface_b));
assert!(checker.is_subtype_of(interface_d, interface_c));
}
#[test]
fn test_interface_implements_partial() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a_prop = interner.intern_string("a");
let b_prop = interner.intern_string("b");
let interface_ab = interner.object(vec![
PropertyInfo::new(a_prop, TypeId::STRING),
PropertyInfo::new(b_prop, TypeId::NUMBER),
]);
let partial = interner.object(vec![PropertyInfo::new(a_prop, TypeId::STRING)]);
assert!(!checker.is_subtype_of(partial, interface_ab));
}
#[test]
fn test_interface_implements_extra_properties() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a_prop = interner.intern_string("a");
let extra_prop = interner.intern_string("extra");
let interface_a = interner.object(vec![PropertyInfo::new(a_prop, TypeId::STRING)]);
let with_extra = interner.object(vec![
PropertyInfo::new(a_prop, TypeId::STRING),
PropertyInfo::new(extra_prop, TypeId::NUMBER),
]);
assert!(checker.is_subtype_of(with_extra, interface_a));
}
#[test]
fn test_interface_implements_wrong_type() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let value = interner.intern_string("value");
let interface_string = interner.object(vec![PropertyInfo::new(value, TypeId::STRING)]);
let has_number = interner.object(vec![PropertyInfo::new(value, TypeId::NUMBER)]);
assert!(!checker.is_subtype_of(has_number, interface_string));
}
#[test]
fn test_interface_merge_same_properties() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a_prop = interner.intern_string("a");
let b_prop = interner.intern_string("b");
let interface_a1 = interner.object(vec![PropertyInfo::new(a_prop, TypeId::STRING)]);
let interface_merged = interner.object(vec![
PropertyInfo::new(a_prop, TypeId::STRING),
PropertyInfo::new(b_prop, TypeId::NUMBER),
]);
assert!(checker.is_subtype_of(interface_merged, interface_a1));
assert!(!checker.is_subtype_of(interface_a1, interface_merged));
}
#[test]
fn test_interface_merge_method_overloads() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let method_name = interner.intern_string("method");
let string_method = 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::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let number_method = 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::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let interface_string = interner.object(vec![PropertyInfo::method(method_name, string_method)]);
let interface_number = interner.object(vec![PropertyInfo::method(method_name, number_method)]);
assert!(!checker.is_subtype_of(interface_string, interface_number));
assert!(!checker.is_subtype_of(interface_number, interface_string));
}
#[test]
fn test_interface_merge_compatible_properties() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let value = interner.intern_string("value");
let string_or_number = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let interface_wide = interner.object(vec![PropertyInfo::new(value, string_or_number)]);
let interface_narrow = interner.object(vec![PropertyInfo::new(value, TypeId::STRING)]);
assert!(checker.is_subtype_of(interface_narrow, interface_wide));
}
#[test]
fn test_interface_merge_global_augmentation() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let document = interner.intern_string("document");
let my_prop = interner.intern_string("myProp");
let window_original = interner.object(vec![PropertyInfo::new(document, TypeId::STRING)]);
let window_augmented = interner.object(vec![
PropertyInfo::new(document, TypeId::STRING),
PropertyInfo::new(my_prop, TypeId::STRING),
]);
assert!(checker.is_subtype_of(window_augmented, window_original));
}
#[test]
fn test_interface_merge_namespace_merge() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let prop = interner.intern_string("prop");
let interface_part = interner.object(vec![PropertyInfo::new(prop, TypeId::STRING)]);
let same_structure = interner.object(vec![PropertyInfo::new(prop, TypeId::STRING)]);
assert!(checker.is_subtype_of(interface_part, same_structure));
assert!(checker.is_subtype_of(same_structure, interface_part));
}
#[test]
fn test_interface_merge_multiple_files() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let file1_prop = interner.intern_string("fromFile1");
let file2_prop = interner.intern_string("fromFile2");
let file1_view = interner.object(vec![PropertyInfo::new(file1_prop, TypeId::STRING)]);
let merged = interner.object(vec![
PropertyInfo::new(file1_prop, TypeId::STRING),
PropertyInfo::new(file2_prop, TypeId::NUMBER),
]);
assert!(checker.is_subtype_of(merged, file1_view));
}
#[test]
fn test_interface_merge_empty_interface() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let prop = interner.intern_string("prop");
let empty = interner.object(vec![]);
let with_prop = interner.object(vec![PropertyInfo::new(prop, TypeId::STRING)]);
assert!(checker.is_subtype_of(with_prop, empty));
assert!(checker.is_subtype_of(empty, empty));
}
#[test]
fn test_interface_vs_type_alias_same_structure() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a_prop = interner.intern_string("a");
let interface_i = interner.object(vec![PropertyInfo::new(a_prop, TypeId::STRING)]);
let type_t = interner.object(vec![PropertyInfo::new(a_prop, TypeId::STRING)]);
assert!(checker.is_subtype_of(interface_i, type_t));
assert!(checker.is_subtype_of(type_t, interface_i));
}
#[test]
fn test_interface_vs_type_alias_with_methods() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let method_name = interner.intern_string("method");
let void_method = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let interface_i = interner.object(vec![PropertyInfo::method(method_name, void_method)]);
let type_t = interner.object(vec![PropertyInfo::method(method_name, void_method)]);
assert!(checker.is_subtype_of(interface_i, type_t));
assert!(checker.is_subtype_of(type_t, interface_i));
}
#[test]
fn test_interface_vs_intersection_type() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a_prop = interner.intern_string("a");
let b_prop = interner.intern_string("b");
let interface_i = interner.object(vec![
PropertyInfo::new(a_prop, TypeId::STRING),
PropertyInfo::new(b_prop, TypeId::NUMBER),
]);
let obj_a = interner.object(vec![PropertyInfo::new(a_prop, TypeId::STRING)]);
let obj_b = interner.object(vec![PropertyInfo::new(b_prop, TypeId::NUMBER)]);
let type_intersection = interner.intersection(vec![obj_a, obj_b]);
assert!(checker.is_subtype_of(interface_i, type_intersection));
}
#[test]
fn test_interface_vs_type_alias_optional() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let value = interner.intern_string("value");
let interface_i = interner.object(vec![PropertyInfo::opt(value, TypeId::STRING)]);
let type_t = interner.object(vec![PropertyInfo::opt(value, TypeId::STRING)]);
assert!(checker.is_subtype_of(interface_i, type_t));
assert!(checker.is_subtype_of(type_t, interface_i));
}
#[test]
fn test_interface_vs_type_alias_readonly() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let value = interner.intern_string("value");
let interface_i = interner.object(vec![PropertyInfo::readonly(value, TypeId::STRING)]);
let type_t = interner.object(vec![PropertyInfo::readonly(value, TypeId::STRING)]);
assert!(checker.is_subtype_of(interface_i, type_t));
assert!(checker.is_subtype_of(type_t, interface_i));
}
#[test]
fn test_interface_vs_type_alias_index_signature() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let interface_i = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
string_index: Some(crate::types::IndexSignature {
key_type: TypeId::STRING,
value_type: TypeId::NUMBER,
readonly: false,
}),
number_index: None,
});
let type_t = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
string_index: Some(crate::types::IndexSignature {
key_type: TypeId::STRING,
value_type: TypeId::NUMBER,
readonly: false,
}),
number_index: None,
});
assert!(checker.is_subtype_of(interface_i, type_t));
assert!(checker.is_subtype_of(type_t, interface_i));
}
#[test]
fn test_interface_extends_type_alias() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a_prop = interner.intern_string("a");
let b_prop = interner.intern_string("b");
let type_base = interner.object(vec![PropertyInfo::new(a_prop, TypeId::STRING)]);
let interface_derived = interner.object(vec![
PropertyInfo::new(a_prop, TypeId::STRING),
PropertyInfo::new(b_prop, TypeId::NUMBER),
]);
assert!(checker.is_subtype_of(interface_derived, type_base));
}
#[test]
fn test_type_alias_intersection_with_interface() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a_prop = interner.intern_string("a");
let b_prop = interner.intern_string("b");
let interface_i = interner.object(vec![PropertyInfo::new(a_prop, TypeId::STRING)]);
let extra = interner.object(vec![PropertyInfo::new(b_prop, TypeId::NUMBER)]);
let type_t = interner.intersection(vec![interface_i, extra]);
assert!(checker.is_subtype_of(type_t, interface_i));
}
#[test]
fn test_never_is_bottom_type_for_primitives() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
assert!(checker.is_subtype_of(TypeId::NEVER, TypeId::STRING));
assert!(checker.is_subtype_of(TypeId::NEVER, TypeId::NUMBER));
assert!(checker.is_subtype_of(TypeId::NEVER, TypeId::BOOLEAN));
assert!(checker.is_subtype_of(TypeId::NEVER, TypeId::SYMBOL));
assert!(checker.is_subtype_of(TypeId::NEVER, TypeId::BIGINT));
assert!(!checker.is_subtype_of(TypeId::STRING, TypeId::NEVER));
assert!(!checker.is_subtype_of(TypeId::NUMBER, TypeId::NEVER));
assert!(!checker.is_subtype_of(TypeId::BOOLEAN, TypeId::NEVER));
}
#[test]
fn test_never_is_bottom_type_for_object_types() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let name = interner.intern_string("name");
let obj = interner.object(vec![PropertyInfo {
name,
type_id: TypeId::STRING,
write_type: TypeId::STRING,
optional: false,
readonly: false,
is_method: false,
visibility: Visibility::Public,
parent_id: None,
}]);
assert!(checker.is_subtype_of(TypeId::NEVER, obj));
assert!(!checker.is_subtype_of(obj, TypeId::NEVER));
}
#[test]
fn test_never_is_bottom_type_for_function_types() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let fn_type = 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,
});
assert!(checker.is_subtype_of(TypeId::NEVER, fn_type));
assert!(!checker.is_subtype_of(fn_type, TypeId::NEVER));
}
#[test]
fn test_never_is_bottom_type_for_tuple_types() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let tuple = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
},
]);
assert!(checker.is_subtype_of(TypeId::NEVER, tuple));
assert!(!checker.is_subtype_of(tuple, TypeId::NEVER));
}
#[test]
fn test_never_is_bottom_type_for_union_types() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let union = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert!(checker.is_subtype_of(TypeId::NEVER, union));
assert!(!checker.is_subtype_of(union, TypeId::NEVER));
}
#[test]
fn test_unknown_is_top_type_for_primitives() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
assert!(checker.is_subtype_of(TypeId::STRING, TypeId::UNKNOWN));
assert!(checker.is_subtype_of(TypeId::NUMBER, TypeId::UNKNOWN));
assert!(checker.is_subtype_of(TypeId::BOOLEAN, TypeId::UNKNOWN));
assert!(checker.is_subtype_of(TypeId::SYMBOL, TypeId::UNKNOWN));
assert!(checker.is_subtype_of(TypeId::BIGINT, TypeId::UNKNOWN));
assert!(!checker.is_subtype_of(TypeId::UNKNOWN, TypeId::STRING));
assert!(!checker.is_subtype_of(TypeId::UNKNOWN, TypeId::NUMBER));
assert!(!checker.is_subtype_of(TypeId::UNKNOWN, TypeId::BOOLEAN));
}
#[test]
fn test_unknown_is_top_type_for_object_types() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let name = interner.intern_string("name");
let obj = interner.object(vec![PropertyInfo {
name,
type_id: TypeId::STRING,
write_type: TypeId::STRING,
optional: false,
readonly: false,
is_method: false,
visibility: Visibility::Public,
parent_id: None,
}]);
assert!(checker.is_subtype_of(obj, TypeId::UNKNOWN));
assert!(!checker.is_subtype_of(TypeId::UNKNOWN, obj));
}
#[test]
fn test_unknown_is_top_type_for_function_types() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let fn_type = 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,
});
assert!(checker.is_subtype_of(fn_type, TypeId::UNKNOWN));
assert!(!checker.is_subtype_of(TypeId::UNKNOWN, fn_type));
}
#[test]
fn test_unknown_is_top_type_for_tuple_types() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let tuple = interner.tuple(vec![
TupleElement {
type_id: TypeId::BOOLEAN,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
]);
assert!(checker.is_subtype_of(tuple, TypeId::UNKNOWN));
assert!(!checker.is_subtype_of(TypeId::UNKNOWN, tuple));
}
#[test]
fn test_unknown_is_top_type_for_never() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
assert!(checker.is_subtype_of(TypeId::NEVER, TypeId::UNKNOWN));
assert!(!checker.is_subtype_of(TypeId::UNKNOWN, TypeId::NEVER));
}
#[test]
fn test_union_never_with_primitive_simplifies() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let union_with_never = interner.union(vec![TypeId::STRING, TypeId::NEVER]);
assert!(checker.is_subtype_of(union_with_never, TypeId::STRING));
assert!(checker.is_subtype_of(TypeId::STRING, union_with_never));
}
#[test]
fn test_union_never_with_multiple_types_simplifies() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let union_with_never = interner.union(vec![TypeId::STRING, TypeId::NUMBER, TypeId::NEVER]);
let union_without_never = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert!(checker.is_subtype_of(union_with_never, union_without_never));
assert!(checker.is_subtype_of(union_without_never, union_with_never));
}
#[test]
fn test_union_never_with_object_simplifies() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let x_name = interner.intern_string("x");
let obj = interner.object(vec![PropertyInfo::new(x_name, TypeId::NUMBER)]);
let union_with_never = interner.union(vec![obj, TypeId::NEVER]);
assert!(checker.is_subtype_of(union_with_never, obj));
assert!(checker.is_subtype_of(obj, union_with_never));
}
#[test]
fn test_union_only_never_remains_never() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let union_of_nevers = interner.union(vec![TypeId::NEVER, TypeId::NEVER]);
assert!(checker.is_subtype_of(union_of_nevers, TypeId::NEVER));
assert!(checker.is_subtype_of(TypeId::NEVER, union_of_nevers));
}
#[test]
fn test_union_never_first_position_simplifies() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let union_never_first = interner.union(vec![TypeId::NEVER, TypeId::BOOLEAN]);
assert!(checker.is_subtype_of(union_never_first, TypeId::BOOLEAN));
assert!(checker.is_subtype_of(TypeId::BOOLEAN, union_never_first));
}
#[test]
fn test_intersection_unknown_with_primitive_simplifies() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let intersection = interner.intersection(vec![TypeId::STRING, TypeId::UNKNOWN]);
assert!(checker.is_subtype_of(intersection, TypeId::STRING));
assert!(checker.is_subtype_of(TypeId::STRING, intersection));
}
#[test]
fn test_intersection_unknown_with_object_simplifies() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let x_name = interner.intern_string("x");
let obj = interner.object(vec![PropertyInfo::new(x_name, TypeId::STRING)]);
let intersection = interner.intersection(vec![obj, TypeId::UNKNOWN]);
assert!(checker.is_subtype_of(intersection, obj));
assert!(checker.is_subtype_of(obj, intersection));
}
#[test]
fn test_intersection_unknown_with_function_simplifies() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let fn_type = 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::BOOLEAN,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let intersection = interner.intersection(vec![fn_type, TypeId::UNKNOWN]);
assert!(checker.is_subtype_of(intersection, fn_type));
assert!(checker.is_subtype_of(fn_type, intersection));
}
#[test]
fn test_intersection_unknown_first_position_simplifies() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let intersection = interner.intersection(vec![TypeId::UNKNOWN, TypeId::NUMBER]);
assert!(checker.is_subtype_of(intersection, TypeId::NUMBER));
assert!(checker.is_subtype_of(TypeId::NUMBER, intersection));
}
#[test]
fn test_intersection_multiple_unknowns_simplifies() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let intersection =
interner.intersection(vec![TypeId::UNKNOWN, TypeId::STRING, TypeId::UNKNOWN]);
assert!(checker.is_subtype_of(intersection, TypeId::STRING));
assert!(checker.is_subtype_of(TypeId::STRING, intersection));
}
#[test]
fn test_numeric_enum_member_to_number() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let enum_a = interner.literal_number(0.0);
let enum_b = interner.literal_number(1.0);
assert!(checker.is_subtype_of(enum_a, TypeId::NUMBER));
assert!(checker.is_subtype_of(enum_b, TypeId::NUMBER));
}
#[test]
fn test_numeric_enum_union() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let enum_a = interner.literal_number(0.0);
let enum_b = interner.literal_number(1.0);
let enum_c = interner.literal_number(2.0);
let enum_type = interner.union(vec![enum_a, enum_b, enum_c]);
assert!(checker.is_subtype_of(enum_type, TypeId::NUMBER));
assert!(checker.is_subtype_of(enum_a, enum_type));
assert!(checker.is_subtype_of(enum_b, enum_type));
assert!(checker.is_subtype_of(enum_c, enum_type));
}
#[test]
fn test_numeric_enum_same_values_equal() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let e1_a = interner.literal_number(0.0);
let e2_a = interner.literal_number(0.0);
assert!(checker.is_subtype_of(e1_a, e2_a));
assert!(checker.is_subtype_of(e2_a, e1_a));
}
#[test]
fn test_numeric_enum_computed_values() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let enum_a = interner.literal_number(1.0);
let enum_b = interner.literal_number(2.0);
let enum_c = interner.literal_number(3.0);
let enum_type = interner.union(vec![enum_a, enum_b, enum_c]);
assert!(checker.is_subtype_of(enum_c, enum_type));
assert!(checker.is_subtype_of(enum_type, TypeId::NUMBER));
}
#[test]
fn test_numeric_enum_negative_values() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let enum_a = interner.literal_number(-1.0);
let enum_b = interner.literal_number(0.0);
let enum_c = interner.literal_number(1.0);
let enum_type = interner.union(vec![enum_a, enum_b, enum_c]);
assert!(checker.is_subtype_of(enum_a, TypeId::NUMBER));
assert!(checker.is_subtype_of(enum_a, enum_type));
}
#[test]
fn test_number_not_subtype_of_numeric_enum() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let enum_a = interner.literal_number(0.0);
let enum_b = interner.literal_number(1.0);
let enum_type = interner.union(vec![enum_a, enum_b]);
assert!(!checker.is_subtype_of(TypeId::NUMBER, enum_type));
}
#[test]
fn test_numeric_enum_single_member() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let only = interner.literal_number(42.0);
assert!(checker.is_subtype_of(only, TypeId::NUMBER));
let other = interner.literal_number(43.0);
assert!(!checker.is_subtype_of(other, only));
}
#[test]
fn test_string_enum_member_to_string() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let enum_a = interner.literal_string("a");
let enum_b = interner.literal_string("b");
assert!(checker.is_subtype_of(enum_a, TypeId::STRING));
assert!(checker.is_subtype_of(enum_b, TypeId::STRING));
}
#[test]
fn test_string_enum_union() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let up = interner.literal_string("UP");
let down = interner.literal_string("DOWN");
let left = interner.literal_string("LEFT");
let right = interner.literal_string("RIGHT");
let direction = interner.union(vec![up, down, left, right]);
assert!(checker.is_subtype_of(direction, TypeId::STRING));
assert!(checker.is_subtype_of(up, direction));
assert!(checker.is_subtype_of(down, direction));
}
#[test]
fn test_string_not_subtype_of_string_enum() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a = interner.literal_string("a");
let b = interner.literal_string("b");
let enum_type = interner.union(vec![a, b]);
assert!(!checker.is_subtype_of(TypeId::STRING, enum_type));
}
#[test]
fn test_string_enum_non_member_literal() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a = interner.literal_string("a");
let b = interner.literal_string("b");
let enum_type = interner.union(vec![a, b]);
let c = interner.literal_string("c");
assert!(!checker.is_subtype_of(c, enum_type));
}
#[test]
fn test_string_enum_case_sensitive() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let upper = interner.literal_string("UP");
let lower = interner.literal_string("up");
assert!(!checker.is_subtype_of(upper, lower));
assert!(!checker.is_subtype_of(lower, upper));
}
#[test]
fn test_string_enum_empty_string() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let empty = interner.literal_string("");
assert!(checker.is_subtype_of(empty, TypeId::STRING));
}
#[test]
fn test_string_enum_with_special_chars() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let special = interner.literal_string("hello-world_123");
assert!(checker.is_subtype_of(special, TypeId::STRING));
}
#[test]
fn test_const_enum_numeric_values() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a = interner.literal_number(0.0);
let b = interner.literal_number(1.0);
let c = interner.literal_number(2.0);
let const_enum = interner.union(vec![a, b, c]);
assert!(checker.is_subtype_of(const_enum, TypeId::NUMBER));
assert!(checker.is_subtype_of(a, const_enum));
}
#[test]
fn test_const_enum_string_values() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a = interner.literal_string("a");
let b = interner.literal_string("b");
let const_enum = interner.union(vec![a, b]);
assert!(checker.is_subtype_of(const_enum, TypeId::STRING));
assert!(checker.is_subtype_of(a, const_enum));
}
#[test]
fn test_const_enum_computed_member() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a = interner.literal_number(1.0); let b = interner.literal_number(2.0); let c = interner.literal_number(4.0);
let flags_enum = interner.union(vec![a, b, c]);
assert!(checker.is_subtype_of(flags_enum, TypeId::NUMBER));
}
#[test]
fn test_const_enum_single_value() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let only = interner.literal_number(42.0);
assert!(checker.is_subtype_of(only, TypeId::NUMBER));
}
#[test]
fn test_const_enum_mixed_types() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let num = interner.literal_number(0.0);
let str = interner.literal_string("b");
let mixed = interner.union(vec![num, str]);
let string_or_number = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert!(checker.is_subtype_of(mixed, string_or_number));
assert!(!checker.is_subtype_of(mixed, TypeId::STRING));
assert!(!checker.is_subtype_of(mixed, TypeId::NUMBER));
}
#[test]
fn test_const_enum_preserves_literal_types() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let val = interner.literal_number(42.0);
let other = interner.literal_number(42.0);
assert!(checker.is_subtype_of(val, other));
assert!(checker.is_subtype_of(other, val));
}
#[test]
fn test_const_enum_bitwise_flags() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let none = interner.literal_number(0.0);
let read = interner.literal_number(1.0);
let write = interner.literal_number(2.0);
let execute = interner.literal_number(4.0);
let all = interner.literal_number(7.0);
let flags = interner.union(vec![none, read, write, execute, all]);
assert!(checker.is_subtype_of(flags, TypeId::NUMBER));
assert!(checker.is_subtype_of(all, flags));
}
#[test]
fn test_enum_member_access_numeric() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let e_a = interner.literal_number(0.0);
let e_b = interner.literal_number(1.0);
assert!(!checker.is_subtype_of(e_a, e_b));
assert!(!checker.is_subtype_of(e_b, e_a));
assert!(checker.is_subtype_of(e_a, TypeId::NUMBER));
assert!(checker.is_subtype_of(e_b, TypeId::NUMBER));
}
#[test]
fn test_enum_member_access_string() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let e_a = interner.literal_string("a");
let e_b = interner.literal_string("b");
assert!(!checker.is_subtype_of(e_a, e_b));
assert!(checker.is_subtype_of(e_a, TypeId::STRING));
assert!(checker.is_subtype_of(e_b, TypeId::STRING));
}
#[test]
fn test_enum_member_in_object_property() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let status_prop = interner.intern_string("status");
let active = interner.literal_string("ACTIVE");
let inactive = interner.literal_string("INACTIVE");
let interface_active = interner.object(vec![PropertyInfo::new(status_prop, active)]);
let obj_active = interner.object(vec![PropertyInfo::new(status_prop, active)]);
let obj_inactive = interner.object(vec![PropertyInfo::new(status_prop, inactive)]);
assert!(checker.is_subtype_of(obj_active, interface_active));
assert!(!checker.is_subtype_of(obj_inactive, interface_active));
}
#[test]
fn test_enum_member_union_in_property() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let status_prop = interner.intern_string("status");
let active = interner.literal_string("ACTIVE");
let pending = interner.literal_string("PENDING");
let completed = interner.literal_string("COMPLETED");
let active_or_pending = interner.union(vec![active, pending]);
let interface_type = interner.object(vec![PropertyInfo::new(status_prop, active_or_pending)]);
let obj_active = interner.object(vec![PropertyInfo::new(status_prop, active)]);
let obj_completed = interner.object(vec![PropertyInfo::new(status_prop, completed)]);
assert!(checker.is_subtype_of(obj_active, interface_type));
assert!(!checker.is_subtype_of(obj_completed, interface_type));
}
#[test]
fn test_enum_member_as_function_param() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let active = interner.literal_string("ACTIVE");
let inactive = interner.literal_string("INACTIVE");
let fn_active_param = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("status")),
type_id: active,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let fn_inactive_param = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("status")),
type_id: inactive,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(!checker.is_subtype_of(fn_active_param, fn_inactive_param));
}
#[test]
fn test_enum_member_as_return_type() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let active = interner.literal_string("ACTIVE");
let fn_returns_active = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: active,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let fn_returns_string = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::STRING,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(checker.is_subtype_of(fn_returns_active, fn_returns_string));
}
#[test]
fn test_enum_member_narrowing() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let active = interner.literal_string("ACTIVE");
let inactive = interner.literal_string("INACTIVE");
let pending = interner.literal_string("PENDING");
let status_enum = interner.union(vec![active, inactive, pending]);
assert!(checker.is_subtype_of(active, status_enum));
assert!(!checker.is_subtype_of(status_enum, active));
}
#[test]
fn test_enum_reverse_mapping_numeric() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let key_name = interner.literal_string("A");
assert!(checker.is_subtype_of(key_name, TypeId::STRING));
}
#[test]
fn test_enum_reverse_mapping_multiple_keys() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let key_a = interner.literal_string("A");
let key_b = interner.literal_string("B");
let key_c = interner.literal_string("C");
let key_union = interner.union(vec![key_a, key_b, key_c]);
assert!(checker.is_subtype_of(key_union, TypeId::STRING));
assert!(checker.is_subtype_of(key_a, key_union));
}
#[test]
fn test_string_enum_no_reverse_mapping() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let enum_value = interner.literal_string("a");
let enum_key = interner.literal_string("A");
assert!(!checker.is_subtype_of(enum_value, enum_key));
assert!(!checker.is_subtype_of(enum_key, enum_value));
}
#[test]
fn test_heterogeneous_enum_mixed_types() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let enum_a = interner.literal_number(0.0);
let enum_b = interner.literal_string("b");
let enum_c = interner.literal_number(1.0);
let enum_type = interner.union(vec![enum_a, enum_b, enum_c]);
assert!(checker.is_subtype_of(enum_a, enum_type));
assert!(checker.is_subtype_of(enum_b, enum_type));
assert!(checker.is_subtype_of(enum_c, enum_type));
let string_or_number = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert!(checker.is_subtype_of(enum_type, string_or_number));
assert!(!checker.is_subtype_of(enum_type, TypeId::STRING));
assert!(!checker.is_subtype_of(enum_type, TypeId::NUMBER));
}
#[test]
fn test_const_enum_inlined_literal() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let const_a = interner.literal_number(1.0);
let _const_b = interner.literal_number(2.0);
assert!(checker.is_subtype_of(const_a, TypeId::NUMBER));
let same_literal = interner.literal_number(1.0);
assert!(checker.is_subtype_of(const_a, same_literal));
assert!(checker.is_subtype_of(same_literal, const_a));
}
#[test]
fn test_const_enum_string_inlined() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let const_a = interner.literal_string("a");
let const_b = interner.literal_string("b");
let const_enum = interner.union(vec![const_a, const_b]);
assert!(checker.is_subtype_of(const_a, TypeId::STRING));
assert!(checker.is_subtype_of(const_enum, TypeId::STRING));
}
#[test]
fn test_enum_cross_compatibility_same_shape() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let e1_a = interner.literal_number(0.0);
let e1_b = interner.literal_number(1.0);
let e1_type = interner.union(vec![e1_a, e1_b]);
let e2_x = interner.literal_number(0.0);
let e2_y = interner.literal_number(1.0);
let e2_type = interner.union(vec![e2_x, e2_y]);
assert!(checker.is_subtype_of(e1_type, e2_type));
assert!(checker.is_subtype_of(e2_type, e1_type));
assert!(checker.is_subtype_of(e1_a, e2_x));
}
#[test]
fn test_enum_partial_overlap() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let e1_a = interner.literal_number(0.0);
let e1_b = interner.literal_number(1.0);
let e1_c = interner.literal_number(2.0);
let e1_type = interner.union(vec![e1_a, e1_b, e1_c]);
let e2_x = interner.literal_number(0.0);
let e2_y = interner.literal_number(1.0);
let e2_type = interner.union(vec![e2_x, e2_y]);
assert!(checker.is_subtype_of(e2_type, e1_type));
assert!(!checker.is_subtype_of(e1_type, e2_type));
}
#[test]
fn test_enum_with_auto_increment() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let enum_a = interner.literal_number(0.0);
let enum_b = interner.literal_number(1.0);
let enum_c = interner.literal_number(2.0);
let enum_type = interner.union(vec![enum_a, enum_b, enum_c]);
assert!(checker.is_subtype_of(enum_a, enum_type));
assert!(checker.is_subtype_of(enum_type, TypeId::NUMBER));
}
#[test]
fn test_enum_with_explicit_and_auto() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let enum_a = interner.literal_number(10.0);
let enum_b = interner.literal_number(11.0);
let enum_c = interner.literal_number(12.0);
let enum_type = interner.union(vec![enum_a, enum_b, enum_c]);
assert!(checker.is_subtype_of(enum_a, enum_type));
assert!(checker.is_subtype_of(enum_b, enum_type));
assert!(checker.is_subtype_of(enum_c, enum_type));
}
#[test]
fn test_enum_member_in_conditional() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let enum_a = interner.literal_number(0.0);
assert!(checker.is_subtype_of(enum_a, TypeId::NUMBER));
let literal_zero = interner.literal_number(0.0);
assert!(checker.is_subtype_of(enum_a, literal_zero));
}
#[test]
fn test_const_enum_as_type_parameter_constraint() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let lit_0 = interner.literal_number(0.0);
let lit_1 = interner.literal_number(1.0);
let constraint = interner.union(vec![lit_0, lit_1]);
let lit_2 = interner.literal_number(2.0);
assert!(checker.is_subtype_of(lit_0, constraint));
assert!(checker.is_subtype_of(lit_1, constraint));
assert!(!checker.is_subtype_of(lit_2, constraint));
}
#[test]
fn test_enum_keyof() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let key_a = interner.literal_string("A");
let key_b = interner.literal_string("B");
let keyof_enum = interner.union(vec![key_a, key_b]);
assert!(checker.is_subtype_of(keyof_enum, TypeId::STRING));
assert!(checker.is_subtype_of(key_a, keyof_enum));
}
#[test]
fn test_enum_value_type() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let val_a = interner.literal_number(0.0);
let val_b = interner.literal_number(1.0);
let value_type = interner.union(vec![val_a, val_b]);
assert!(checker.is_subtype_of(value_type, TypeId::NUMBER));
assert!(!checker.is_subtype_of(TypeId::NUMBER, value_type));
}
#[test]
fn test_enum_with_bigint_like_value() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let big_val = interner.literal_number(9007199254740991.0);
assert!(checker.is_subtype_of(big_val, TypeId::NUMBER));
}
#[test]
fn test_enum_preserves_literal_identity() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let enum_a = interner.literal_number(1.0);
let literal_one = interner.literal_number(1.0);
assert!(checker.is_subtype_of(enum_a, literal_one));
assert!(checker.is_subtype_of(literal_one, enum_a));
}
#[test]
fn test_string_enum_unicode() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let emoji = interner.literal_string("🎉");
let symbol = interner.literal_string("→");
let enum_type = interner.union(vec![emoji, symbol]);
assert!(checker.is_subtype_of(emoji, TypeId::STRING));
assert!(checker.is_subtype_of(symbol, enum_type));
}
#[test]
fn test_enum_in_mapped_type_context() {
let interner = TypeInterner::new();
let _checker = SubtypeChecker::new(&interner);
let lit_a = interner.literal_string("a");
let lit_b = interner.literal_string("b");
let result = interner.object(vec![
PropertyInfo::new(interner.intern_string("a"), lit_a),
PropertyInfo::new(interner.intern_string("b"), lit_b),
]);
assert!(result != TypeId::ERROR);
}
#[test]
fn test_index_signature_string_to_string() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let obj_a = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: TypeId::NUMBER,
readonly: false,
}),
number_index: None,
});
let obj_b = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: TypeId::NUMBER,
readonly: false,
}),
number_index: None,
});
assert!(checker.is_subtype_of(obj_a, obj_b));
}
#[test]
fn test_index_signature_number_to_number() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let obj_a = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
string_index: None,
number_index: Some(IndexSignature {
key_type: TypeId::NUMBER,
value_type: TypeId::STRING,
readonly: false,
}),
});
let obj_b = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
string_index: None,
number_index: Some(IndexSignature {
key_type: TypeId::NUMBER,
value_type: TypeId::STRING,
readonly: false,
}),
});
assert!(checker.is_subtype_of(obj_a, obj_b));
}
#[test]
fn test_index_signature_covariant_value_type() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let literal_union = interner.union(vec![
interner.literal_string("a"),
interner.literal_string("b"),
]);
let obj_specific = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: literal_union,
readonly: false,
}),
number_index: None,
});
let obj_general = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: TypeId::STRING,
readonly: false,
}),
number_index: None,
});
assert!(checker.is_subtype_of(obj_specific, obj_general));
assert!(!checker.is_subtype_of(obj_general, obj_specific));
}
#[test]
fn test_index_signature_both_string_and_number() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let obj_both = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: TypeId::ANY,
readonly: false,
}),
number_index: Some(IndexSignature {
key_type: TypeId::NUMBER,
value_type: TypeId::STRING,
readonly: false,
}),
});
let obj_string_only = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: TypeId::ANY,
readonly: false,
}),
number_index: None,
});
assert!(checker.is_subtype_of(obj_both, obj_string_only));
}
#[test]
fn test_index_signature_number_subtype_of_string() {
let interner = TypeInterner::new();
let _checker = SubtypeChecker::new(&interner);
let obj = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: TypeId::ANY,
readonly: false,
}),
number_index: Some(IndexSignature {
key_type: TypeId::NUMBER,
value_type: TypeId::STRING,
readonly: false,
}),
});
assert!(obj != TypeId::ERROR);
}
#[test]
fn test_index_signature_intersection_combines() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let obj_a = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: TypeId::NUMBER,
readonly: false,
}),
number_index: None,
});
let obj_b = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: TypeId::NUMBER,
readonly: false,
}),
number_index: None,
});
let intersection = interner.intersection(vec![obj_a, obj_b]);
assert!(checker.is_subtype_of(intersection, obj_a));
assert!(checker.is_subtype_of(intersection, obj_b));
}
#[test]
fn test_index_signature_with_properties() {
let interner = TypeInterner::new();
let _checker = SubtypeChecker::new(&interner);
let union_type = interner.union(vec![TypeId::NUMBER, TypeId::STRING]);
let obj = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![PropertyInfo::new(
interner.intern_string("x"),
TypeId::NUMBER,
)],
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: union_type,
readonly: false,
}),
number_index: None,
});
assert!(obj != TypeId::ERROR);
}
#[test]
fn test_index_signature_property_must_match_index() {
let interner = TypeInterner::new();
let _checker = SubtypeChecker::new(&interner);
let obj_valid = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![PropertyInfo::new(
interner.intern_string("x"),
TypeId::STRING,
)],
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: TypeId::STRING,
readonly: false,
}),
number_index: None,
});
assert!(obj_valid != TypeId::ERROR);
}
#[test]
fn test_index_signature_readonly_to_mutable() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let obj_readonly = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: TypeId::NUMBER,
readonly: true,
}),
number_index: None,
});
let obj_mutable = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: TypeId::NUMBER,
readonly: false,
}),
number_index: None,
});
assert!(!checker.is_subtype_of(obj_readonly, obj_mutable));
}
#[test]
fn test_index_signature_mutable_to_readonly() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let obj_mutable = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: TypeId::NUMBER,
readonly: false,
}),
number_index: None,
});
let obj_readonly = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: TypeId::NUMBER,
readonly: true,
}),
number_index: None,
});
assert!(checker.is_subtype_of(obj_mutable, obj_readonly));
}
#[test]
fn test_index_signature_union_value_subtyping() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let union_value = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let obj = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: union_value,
readonly: false,
}),
number_index: None,
});
let obj_string = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: TypeId::STRING,
readonly: false,
}),
number_index: None,
});
assert!(checker.is_subtype_of(obj_string, obj));
}
#[test]
fn test_index_signature_intersection_value() {
let interner = TypeInterner::new();
let _checker = SubtypeChecker::new(&interner);
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 intersection_value = interner.intersection(vec![obj_a, obj_b]);
let obj = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: intersection_value,
readonly: false,
}),
number_index: None,
});
assert!(obj != TypeId::ERROR);
}
#[test]
fn test_index_signature_empty_object_to_indexed() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let empty_obj = interner.object(vec![]);
let indexed_obj = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: TypeId::NUMBER,
readonly: false,
}),
number_index: None,
});
let result = checker.is_subtype_of(empty_obj, indexed_obj);
let _ = result;
}
#[test]
fn test_index_signature_object_with_extra_props() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let obj_with_props = interner.object(vec![
PropertyInfo::new(interner.intern_string("a"), TypeId::NUMBER),
PropertyInfo::new(interner.intern_string("b"), TypeId::STRING),
]);
let union_value = interner.union(vec![TypeId::NUMBER, TypeId::STRING]);
let indexed_obj = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: union_value,
readonly: false,
}),
number_index: None,
});
assert!(checker.is_subtype_of(obj_with_props, indexed_obj));
}
#[test]
fn test_index_signature_numeric_string_key() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let obj_with_numeric_props = interner.object(vec![
PropertyInfo::new(interner.intern_string("0"), TypeId::STRING),
PropertyInfo::new(interner.intern_string("1"), TypeId::STRING),
]);
let number_indexed = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
string_index: None,
number_index: Some(IndexSignature {
key_type: TypeId::NUMBER,
value_type: TypeId::STRING,
readonly: false,
}),
});
assert!(checker.is_subtype_of(obj_with_numeric_props, number_indexed));
}
#[test]
fn test_index_signature_any_value() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let indexed_any = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: TypeId::ANY,
readonly: false,
}),
number_index: None,
});
let obj_with_props = interner.object(vec![PropertyInfo::new(
interner.intern_string("x"),
TypeId::BOOLEAN,
)]);
assert!(checker.is_subtype_of(obj_with_props, indexed_any));
}
#[test]
fn test_index_signature_unknown_value() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let indexed_unknown = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: TypeId::UNKNOWN,
readonly: false,
}),
number_index: None,
});
let indexed_string = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: TypeId::STRING,
readonly: false,
}),
number_index: None,
});
assert!(checker.is_subtype_of(indexed_string, indexed_unknown));
}
#[test]
fn test_index_signature_never_value() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let indexed_never = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: TypeId::NEVER,
readonly: false,
}),
number_index: None,
});
let empty_obj = interner.object(vec![]);
let result = checker.is_subtype_of(empty_obj, indexed_never);
let _ = result;
}
#[test]
fn test_index_signature_function_value() {
let interner = TypeInterner::new();
let _checker = SubtypeChecker::new(&interner);
let fn_type = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let indexed_fn = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: fn_type,
readonly: false,
}),
number_index: None,
});
assert!(indexed_fn != TypeId::ERROR);
}
#[test]
fn test_index_signature_array_value() {
let interner = TypeInterner::new();
let _checker = SubtypeChecker::new(&interner);
let array_type = interner.array(TypeId::NUMBER);
let indexed_array = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: array_type,
readonly: false,
}),
number_index: None,
});
assert!(indexed_array != TypeId::ERROR);
}
#[test]
fn test_index_signature_tuple_value() {
let interner = TypeInterner::new();
let _checker = SubtypeChecker::new(&interner);
let tuple_type = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
optional: false,
rest: false,
name: None,
},
TupleElement {
type_id: TypeId::NUMBER,
optional: false,
rest: false,
name: None,
},
]);
let indexed_tuple = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
string_index: None,
number_index: Some(IndexSignature {
key_type: TypeId::NUMBER,
value_type: tuple_type,
readonly: false,
}),
});
assert!(indexed_tuple != TypeId::ERROR);
}
#[test]
fn test_index_signature_nested_object_value() {
let interner = TypeInterner::new();
let _checker = SubtypeChecker::new(&interner);
let nested_obj = interner.object(vec![PropertyInfo::new(
interner.intern_string("x"),
TypeId::NUMBER,
)]);
let indexed_nested = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: nested_obj,
readonly: false,
}),
number_index: None,
});
assert!(indexed_nested != TypeId::ERROR);
}
#[test]
fn test_index_signature_intersection_objects() {
let interner = TypeInterner::new();
let _checker = SubtypeChecker::new(&interner);
let indexed_obj = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: TypeId::NUMBER,
readonly: false,
}),
number_index: None,
});
let prop_obj = interner.object(vec![PropertyInfo::new(
interner.intern_string("x"),
TypeId::NUMBER,
)]);
let intersection = interner.intersection(vec![indexed_obj, prop_obj]);
assert!(intersection != TypeId::ERROR);
}
#[test]
fn test_index_signature_literal_key_subset() {
let interner = TypeInterner::new();
let _checker = SubtypeChecker::new(&interner);
let _literal_keys = interner.union(vec![
interner.literal_string("a"),
interner.literal_string("b"),
]);
let obj_with_literal_props = interner.object(vec![
PropertyInfo::new(interner.intern_string("a"), TypeId::NUMBER),
PropertyInfo::new(interner.intern_string("b"), TypeId::NUMBER),
]);
assert!(obj_with_literal_props != TypeId::ERROR);
}
#[test]
fn test_variance_nested_function_contravariance() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let narrow_callback = 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::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let wide_param = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let wide_callback = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: wide_param,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let hof_narrow = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("f")),
type_id: narrow_callback,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let hof_wide = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("f")),
type_id: wide_callback,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(!checker.is_subtype_of(hof_wide, hof_narrow));
assert!(checker.is_subtype_of(hof_narrow, hof_wide));
}
#[test]
fn test_variance_callback_return_type() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let narrow_returning = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::STRING,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let wide_return = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let wide_returning = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: wide_return,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let hof_narrow_return = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("f")),
type_id: narrow_returning,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let hof_wide_return = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("f")),
type_id: wide_returning,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(!checker.is_subtype_of(hof_narrow_return, hof_wide_return));
assert!(checker.is_subtype_of(hof_wide_return, hof_narrow_return));
}
#[test]
fn test_variance_readonly_property_covariant() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let wide_type = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let narrow_readonly = interner.object(vec![PropertyInfo::readonly(
interner.intern_string("x"),
TypeId::STRING,
)]);
let wide_readonly = interner.object(vec![PropertyInfo::readonly(
interner.intern_string("x"),
wide_type,
)]);
assert!(checker.is_subtype_of(narrow_readonly, wide_readonly));
}
#[test]
fn test_variance_mutable_property_invariant() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let wide_type = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let narrow_mutable = interner.object(vec![PropertyInfo::new(
interner.intern_string("x"),
TypeId::STRING,
)]);
let wide_mutable = interner.object(vec![PropertyInfo::new(
interner.intern_string("x"),
wide_type,
)]);
assert!(checker.is_subtype_of(narrow_mutable, wide_mutable));
}
#[test]
fn test_variance_tuple_element_covariant() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let wide_first = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let wide_second = interner.union(vec![TypeId::NUMBER, TypeId::BOOLEAN]);
let narrow_tuple = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
optional: false,
name: None,
rest: false,
},
TupleElement {
type_id: TypeId::NUMBER,
optional: false,
name: None,
rest: false,
},
]);
let wide_tuple = interner.tuple(vec![
TupleElement {
type_id: wide_first,
optional: false,
name: None,
rest: false,
},
TupleElement {
type_id: wide_second,
optional: false,
name: None,
rest: false,
},
]);
assert!(checker.is_subtype_of(narrow_tuple, wide_tuple));
assert!(!checker.is_subtype_of(wide_tuple, narrow_tuple));
}
#[test]
fn test_variance_function_returning_function() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let wide_param = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let inner_narrow = 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::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let inner_wide = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: wide_param,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let factory_narrow = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: inner_narrow,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let factory_wide = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: inner_wide,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(checker.is_subtype_of(factory_wide, factory_narrow));
assert!(!checker.is_subtype_of(factory_narrow, factory_wide));
}
#[test]
fn test_variance_union_in_contravariant_position() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let union_ab = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let fn_union_param = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: union_ab,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let fn_single_param = 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::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(checker.is_subtype_of(fn_union_param, fn_single_param));
assert!(!checker.is_subtype_of(fn_single_param, fn_union_param));
}
#[test]
fn test_variance_intersection_in_covariant_position() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
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 intersection_ab = interner.intersection(vec![obj_a, obj_b]);
let fn_returns_intersection = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: intersection_ab,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let fn_returns_a = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: obj_a,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(checker.is_subtype_of(fn_returns_intersection, fn_returns_a));
}
#[test]
fn test_variance_array_element_unsound_covariance() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let wide_element = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let narrow_array = interner.array(TypeId::STRING);
let wide_array = interner.array(wide_element);
assert!(checker.is_subtype_of(narrow_array, wide_array));
}
#[test]
#[ignore = "Method bivariance/strict function types not fully implemented"]
fn test_variance_method_bivariant_params() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let wide_type = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let narrow_method_obj = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![],
construct_signatures: vec![],
properties: vec![PropertyInfo {
name: interner.intern_string("handle"),
type_id: 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::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
}),
write_type: TypeId::VOID,
optional: false,
readonly: false,
is_method: true,
visibility: Visibility::Public,
parent_id: None,
}],
string_index: None,
number_index: None,
});
let wide_method_obj = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![],
construct_signatures: vec![],
properties: vec![PropertyInfo {
name: interner.intern_string("handle"),
type_id: interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: wide_type,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
}),
write_type: TypeId::VOID,
optional: false,
readonly: false,
is_method: true,
visibility: Visibility::Public,
parent_id: None,
}],
string_index: None,
number_index: None,
});
assert!(checker.is_subtype_of(narrow_method_obj, wide_method_obj));
assert!(checker.is_subtype_of(wide_method_obj, narrow_method_obj));
}
#[test]
fn test_variance_function_property_contravariant() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let wide_type = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let narrow_fn_obj = interner.object(vec![PropertyInfo {
name: interner.intern_string("handle"),
type_id: 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::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
}),
write_type: TypeId::VOID,
optional: false,
readonly: false,
is_method: false,
visibility: Visibility::Public,
parent_id: None,
}]);
let wide_fn_obj = interner.object(vec![PropertyInfo {
name: interner.intern_string("handle"),
type_id: interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: wide_type,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
}),
write_type: TypeId::VOID,
optional: false,
readonly: false,
is_method: false,
visibility: Visibility::Public,
parent_id: None,
}]);
assert!(checker.is_subtype_of(wide_fn_obj, narrow_fn_obj));
}
#[test]
fn test_variance_promise_covariant() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let wide_type = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let then_narrow = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("cb")),
type_id: interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("value")),
type_id: TypeId::STRING,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
}),
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let then_wide = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("cb")),
type_id: interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("value")),
type_id: wide_type,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
}),
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let promise_narrow = interner.object(vec![PropertyInfo::method(
interner.intern_string("then"),
then_narrow,
)]);
let promise_wide = interner.object(vec![PropertyInfo::method(
interner.intern_string("then"),
then_wide,
)]);
assert!(checker.is_subtype_of(promise_narrow, promise_wide));
}
#[test]
fn test_variance_triple_nested_contravariance() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let wide_type = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let inner_narrow = 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::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let inner_wide = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: wide_type,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let middle_narrow = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("g")),
type_id: inner_narrow,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let middle_wide = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("g")),
type_id: inner_wide,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let outer_narrow = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("f")),
type_id: middle_narrow,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let outer_wide = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("f")),
type_id: middle_wide,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(!checker.is_subtype_of(outer_narrow, outer_wide));
assert!(checker.is_subtype_of(outer_wide, outer_narrow));
}
#[test]
#[ignore = "Method bivariance/strict function types not fully implemented"]
fn test_variance_constructor_param_bivariant() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let wide_type = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let instance = interner.object(vec![PropertyInfo::new(
interner.intern_string("value"),
TypeId::STRING,
)]);
let ctor_narrow = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![],
construct_signatures: vec![CallSignature {
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: instance,
type_predicate: None,
is_method: false,
}],
properties: vec![],
string_index: None,
number_index: None,
});
let ctor_wide = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![],
construct_signatures: vec![CallSignature {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: wide_type,
optional: false,
rest: false,
}],
this_type: None,
return_type: instance,
type_predicate: None,
is_method: false,
}],
properties: vec![],
string_index: None,
number_index: None,
});
assert!(checker.is_subtype_of(ctor_wide, ctor_narrow));
assert!(checker.is_subtype_of(ctor_narrow, ctor_wide));
}
#[test]
fn test_variance_rest_param_contravariant() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let wide_type = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let narrow_array = interner.array(TypeId::STRING);
let wide_array = interner.array(wide_type);
let fn_narrow_rest = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("args")),
type_id: narrow_array,
optional: false,
rest: true,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let fn_wide_rest = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("args")),
type_id: wide_array,
optional: false,
rest: true,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(checker.is_subtype_of(fn_wide_rest, fn_narrow_rest));
}
#[test]
fn test_variance_optional_param_covariant_optionality() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let fn_optional = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: TypeId::STRING,
optional: true,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let fn_required = 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::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(checker.is_subtype_of(fn_optional, fn_required));
}
#[test]
fn test_overload_single_signature_subtype() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let fn_type = 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 callable_type = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![CallSignature {
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_method: false,
}],
construct_signatures: vec![],
properties: vec![],
string_index: None,
number_index: None,
});
assert!(checker.is_subtype_of(fn_type, callable_type));
}
#[test]
fn test_overload_multiple_to_single() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let multi_overload = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![
CallSignature {
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::STRING,
type_predicate: None,
is_method: false,
},
CallSignature {
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::NUMBER,
type_predicate: None,
is_method: false,
},
],
construct_signatures: vec![],
properties: vec![],
string_index: None,
number_index: None,
});
let single_overload = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![CallSignature {
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::STRING,
type_predicate: None,
is_method: false,
}],
construct_signatures: vec![],
properties: vec![],
string_index: None,
number_index: None,
});
assert!(checker.is_subtype_of(multi_overload, single_overload));
}
#[test]
fn test_overload_order_independent_matching() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let overloads_ab = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![
CallSignature {
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::STRING,
type_predicate: None,
is_method: false,
},
CallSignature {
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::NUMBER,
type_predicate: None,
is_method: false,
},
],
construct_signatures: vec![],
properties: vec![],
string_index: None,
number_index: None,
});
let overloads_ba = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![
CallSignature {
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::NUMBER,
type_predicate: None,
is_method: false,
},
CallSignature {
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::STRING,
type_predicate: None,
is_method: false,
},
],
construct_signatures: vec![],
properties: vec![],
string_index: None,
number_index: None,
});
assert!(checker.is_subtype_of(overloads_ab, overloads_ba));
assert!(checker.is_subtype_of(overloads_ba, overloads_ab));
}
#[test]
fn test_overload_missing_signature_not_subtype() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let single_overload = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![CallSignature {
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::STRING,
type_predicate: None,
is_method: false,
}],
construct_signatures: vec![],
properties: vec![],
string_index: None,
number_index: None,
});
let two_overloads = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![
CallSignature {
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::STRING,
type_predicate: None,
is_method: false,
},
CallSignature {
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::NUMBER,
type_predicate: None,
is_method: false,
},
],
construct_signatures: vec![],
properties: vec![],
string_index: None,
number_index: None,
});
assert!(!checker.is_subtype_of(single_overload, two_overloads));
}
#[test]
fn test_overload_wider_param_satisfies_target() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let wide_param = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let wide_overload = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![CallSignature {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: wide_param,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_method: false,
}],
construct_signatures: vec![],
properties: vec![],
string_index: None,
number_index: None,
});
let narrow_overload = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![CallSignature {
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::VOID,
type_predicate: None,
is_method: false,
}],
construct_signatures: vec![],
properties: vec![],
string_index: None,
number_index: None,
});
assert!(checker.is_subtype_of(wide_overload, narrow_overload));
}
#[test]
fn test_overload_constructor_subtype() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let instance = interner.object(vec![PropertyInfo::new(
interner.intern_string("value"),
TypeId::STRING,
)]);
let multi_ctor = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![],
construct_signatures: vec![
CallSignature {
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: instance,
type_predicate: None,
is_method: false,
},
CallSignature {
type_params: vec![],
params: vec![],
this_type: None,
return_type: instance,
type_predicate: None,
is_method: false,
},
],
properties: vec![],
string_index: None,
number_index: None,
});
let single_ctor = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![],
construct_signatures: vec![CallSignature {
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: instance,
type_predicate: None,
is_method: false,
}],
properties: vec![],
string_index: None,
number_index: None,
});
assert!(checker.is_subtype_of(multi_ctor, single_ctor));
}
#[test]
fn test_overload_with_different_arity() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let multi_arity = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![
CallSignature {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::STRING,
type_predicate: None,
is_method: false,
},
CallSignature {
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::STRING,
type_predicate: None,
is_method: false,
},
CallSignature {
type_params: vec![],
params: vec![
ParamInfo {
name: Some(interner.intern_string("x")),
type_id: TypeId::STRING,
optional: false,
rest: false,
},
ParamInfo {
name: Some(interner.intern_string("y")),
type_id: TypeId::NUMBER,
optional: false,
rest: false,
},
],
this_type: None,
return_type: TypeId::STRING,
type_predicate: None,
is_method: false,
},
],
construct_signatures: vec![],
properties: vec![],
string_index: None,
number_index: None,
});
let no_args = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![CallSignature {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::STRING,
type_predicate: None,
is_method: false,
}],
construct_signatures: vec![],
properties: vec![],
string_index: None,
number_index: None,
});
assert!(checker.is_subtype_of(multi_arity, no_args));
}
#[test]
fn test_this_parameter_explicit_type() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let foo_type = interner.object(vec![PropertyInfo::new(
interner.intern_string("name"),
TypeId::STRING,
)]);
let fn_with_this = 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: Some(foo_type),
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let fn_without_this = 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::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(checker.is_subtype_of(fn_without_this, fn_with_this));
}
#[test]
fn test_this_parameter_covariant_in_method() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let base_type = interner.object(vec![PropertyInfo::new(
interner.intern_string("name"),
TypeId::STRING,
)]);
let derived_type = interner.object(vec![
PropertyInfo::new(interner.intern_string("name"), TypeId::STRING),
PropertyInfo::new(interner.intern_string("age"), TypeId::NUMBER),
]);
let derived_method = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: Some(derived_type),
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let base_method = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: Some(base_type),
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(checker.is_subtype_of(base_method, derived_method));
}
#[test]
fn test_this_parameter_void_this() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let fn_void_this = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: Some(TypeId::VOID),
return_type: TypeId::NUMBER,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let fn_any_this = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: Some(TypeId::ANY),
return_type: TypeId::NUMBER,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let fn_no_this = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::NUMBER,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(checker.is_subtype_of(fn_void_this, fn_no_this));
assert!(checker.is_subtype_of(fn_no_this, fn_void_this));
assert!(checker.is_subtype_of(fn_any_this, fn_no_this));
}
#[test]
fn test_this_parameter_in_callable_method() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let obj_type = interner.object(vec![PropertyInfo::new(
interner.intern_string("data"),
TypeId::STRING,
)]);
let method_fn = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: Some(obj_type),
return_type: TypeId::STRING,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let callable_with_method = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![],
construct_signatures: vec![],
properties: vec![PropertyInfo::method(
interner.intern_string("getData"),
method_fn,
)],
string_index: None,
number_index: None,
});
let plain_method = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::STRING,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let callable_plain = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![],
construct_signatures: vec![],
properties: vec![PropertyInfo::method(
interner.intern_string("getData"),
plain_method,
)],
string_index: None,
number_index: None,
});
assert!(checker.is_subtype_of(callable_with_method, callable_plain));
}
#[test]
fn test_this_parameter_fluent_api_pattern() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let builder_type = interner.object(vec![PropertyInfo::new(
interner.intern_string("value"),
TypeId::STRING,
)]);
let set_method = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("value")),
type_id: TypeId::STRING,
optional: false,
rest: false,
}],
this_type: Some(builder_type),
return_type: builder_type,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let other_builder = interner.object(vec![
PropertyInfo::new(interner.intern_string("value"), TypeId::STRING),
PropertyInfo::new(interner.intern_string("extra"), TypeId::NUMBER),
]);
let other_set_method = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("value")),
type_id: TypeId::STRING,
optional: false,
rest: false,
}],
this_type: Some(other_builder),
return_type: other_builder,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(!checker.is_subtype_of(set_method, other_set_method));
}
#[test]
fn test_this_parameter_unknown_this() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let fn_unknown_this = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: Some(TypeId::UNKNOWN),
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let fn_string_this = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: Some(TypeId::STRING),
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(checker.is_subtype_of(fn_unknown_this, fn_string_this));
}
#[test]
fn test_overload_with_call_and_construct() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let instance = interner.object(vec![PropertyInfo::new(
interner.intern_string("value"),
TypeId::STRING,
)]);
let dual_callable = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![CallSignature {
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::STRING,
type_predicate: None,
is_method: false,
}],
construct_signatures: vec![CallSignature {
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: instance,
type_predicate: None,
is_method: false,
}],
properties: vec![],
string_index: None,
number_index: None,
});
let call_only = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![CallSignature {
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::STRING,
type_predicate: None,
is_method: false,
}],
construct_signatures: vec![],
properties: vec![],
string_index: None,
number_index: None,
});
assert!(checker.is_subtype_of(dual_callable, call_only));
}
#[test]
fn test_overload_rest_vs_multiple_params() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let string_array = interner.array(TypeId::STRING);
let rest_fn = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![CallSignature {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("args")),
type_id: string_array,
optional: false,
rest: true,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_method: false,
}],
construct_signatures: vec![],
properties: vec![],
string_index: None,
number_index: None,
});
let two_params = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![CallSignature {
type_params: vec![],
params: vec![
ParamInfo {
name: Some(interner.intern_string("a")),
type_id: TypeId::STRING,
optional: false,
rest: false,
},
ParamInfo {
name: Some(interner.intern_string("b")),
type_id: TypeId::STRING,
optional: false,
rest: false,
},
],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_method: false,
}],
construct_signatures: vec![],
properties: vec![],
string_index: None,
number_index: None,
});
assert!(checker.is_subtype_of(rest_fn, two_params));
}
#[test]
fn test_this_in_overload_signature() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let obj_type = interner.object(vec![PropertyInfo::new(
interner.intern_string("x"),
TypeId::NUMBER,
)]);
let overload_with_this = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![CallSignature {
type_params: vec![],
params: vec![],
this_type: Some(obj_type),
return_type: TypeId::NUMBER,
type_predicate: None,
is_method: false,
}],
construct_signatures: vec![],
properties: vec![],
string_index: None,
number_index: None,
});
let overload_no_this = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![CallSignature {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::NUMBER,
type_predicate: None,
is_method: false,
}],
construct_signatures: vec![],
properties: vec![],
string_index: None,
number_index: None,
});
assert!(checker.is_subtype_of(overload_no_this, overload_with_this));
}
#[test]
fn test_unique_symbol_self_subtype() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let sym = interner.intern(TypeData::UniqueSymbol(SymbolRef(42)));
assert!(checker.is_subtype_of(sym, sym));
}
#[test]
fn test_unique_symbol_not_subtype_of_different() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let sym_a = interner.intern(TypeData::UniqueSymbol(SymbolRef(1)));
let sym_b = interner.intern(TypeData::UniqueSymbol(SymbolRef(2)));
assert!(!checker.is_subtype_of(sym_a, sym_b));
assert!(!checker.is_subtype_of(sym_b, sym_a));
}
#[test]
fn test_unique_symbol_subtype_of_symbol() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let unique_sym = interner.intern(TypeData::UniqueSymbol(SymbolRef(100)));
assert!(checker.is_subtype_of(unique_sym, TypeId::SYMBOL));
}
#[test]
fn test_symbol_not_subtype_of_unique_symbol() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let unique_sym = interner.intern(TypeData::UniqueSymbol(SymbolRef(100)));
assert!(!checker.is_subtype_of(TypeId::SYMBOL, unique_sym));
}
#[test]
fn test_unique_symbol_in_union() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let unique_sym = interner.intern(TypeData::UniqueSymbol(SymbolRef(1)));
let sym_or_string = interner.union(vec![unique_sym, TypeId::STRING]);
assert!(checker.is_subtype_of(unique_sym, sym_or_string));
assert!(checker.is_subtype_of(TypeId::STRING, sym_or_string));
let symbol_or_string = interner.union(vec![TypeId::SYMBOL, TypeId::STRING]);
assert!(checker.is_subtype_of(sym_or_string, symbol_or_string));
}
#[test]
fn test_well_known_symbol_iterator() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let sym_iterator = interner.intern(TypeData::UniqueSymbol(SymbolRef(1000)));
assert!(checker.is_subtype_of(sym_iterator, TypeId::SYMBOL));
let sym_async_iterator = interner.intern(TypeData::UniqueSymbol(SymbolRef(1001)));
assert!(!checker.is_subtype_of(sym_iterator, sym_async_iterator));
}
#[test]
fn test_well_known_symbol_async_iterator() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let sym_async_iterator = interner.intern(TypeData::UniqueSymbol(SymbolRef(1001)));
assert!(checker.is_subtype_of(sym_async_iterator, TypeId::SYMBOL));
}
#[test]
fn test_well_known_symbol_to_string_tag() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let sym_to_string_tag = interner.intern(TypeData::UniqueSymbol(SymbolRef(1002)));
assert!(checker.is_subtype_of(sym_to_string_tag, TypeId::SYMBOL));
}
#[test]
fn test_well_known_symbol_has_instance() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let sym_has_instance = interner.intern(TypeData::UniqueSymbol(SymbolRef(1003)));
assert!(checker.is_subtype_of(sym_has_instance, TypeId::SYMBOL));
}
#[test]
fn test_symbol_keyed_object_property() {
let interner = TypeInterner::new();
let _checker = SubtypeChecker::new(&interner);
let _sym_iterator = interner.intern(TypeData::UniqueSymbol(SymbolRef(1000)));
let iterator_fn = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::ANY,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let iterable_obj = interner.object(vec![PropertyInfo {
name: interner.intern_string("[Symbol.iterator]"),
type_id: iterator_fn,
write_type: iterator_fn,
optional: false,
readonly: true,
is_method: true,
visibility: Visibility::Public,
parent_id: None,
}]);
assert!(iterable_obj != TypeId::ERROR);
}
#[test]
fn test_symbol_union_with_multiple_unique() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let sym_a = interner.intern(TypeData::UniqueSymbol(SymbolRef(1)));
let sym_b = interner.intern(TypeData::UniqueSymbol(SymbolRef(2)));
let sym_c = interner.intern(TypeData::UniqueSymbol(SymbolRef(3)));
let sym_union = interner.union(vec![sym_a, sym_b, sym_c]);
assert!(checker.is_subtype_of(sym_a, sym_union));
assert!(checker.is_subtype_of(sym_b, sym_union));
assert!(checker.is_subtype_of(sym_c, sym_union));
assert!(checker.is_subtype_of(sym_union, TypeId::SYMBOL));
}
#[test]
fn test_symbol_not_subtype_of_string() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
assert!(!checker.is_subtype_of(TypeId::SYMBOL, TypeId::STRING));
assert!(!checker.is_subtype_of(TypeId::STRING, TypeId::SYMBOL));
}
#[test]
fn test_symbol_not_subtype_of_number() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
assert!(!checker.is_subtype_of(TypeId::SYMBOL, TypeId::NUMBER));
assert!(!checker.is_subtype_of(TypeId::NUMBER, TypeId::SYMBOL));
}
#[test]
fn test_unique_symbol_intersection() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let unique_sym = interner.intern(TypeData::UniqueSymbol(SymbolRef(42)));
let intersection = interner.intersection(vec![unique_sym, TypeId::SYMBOL]);
assert!(checker.is_subtype_of(intersection, TypeId::SYMBOL));
}
#[test]
fn test_symbol_as_property_key() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let property_key = interner.union(vec![TypeId::STRING, TypeId::NUMBER, TypeId::SYMBOL]);
assert!(checker.is_subtype_of(TypeId::SYMBOL, property_key));
let unique_sym = interner.intern(TypeData::UniqueSymbol(SymbolRef(1)));
assert!(checker.is_subtype_of(unique_sym, property_key));
}
#[test]
fn test_const_unique_symbol_type() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let const_sym = interner.intern(TypeData::UniqueSymbol(SymbolRef(999)));
assert!(checker.is_subtype_of(const_sym, TypeId::SYMBOL));
assert!(!checker.is_subtype_of(TypeId::SYMBOL, const_sym));
}
#[test]
fn test_let_symbol_type() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let let_sym = TypeId::SYMBOL;
assert!(checker.is_subtype_of(let_sym, TypeId::SYMBOL));
}
#[test]
fn test_symbol_for_shared() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let shared_sym = TypeId::SYMBOL;
assert!(checker.is_subtype_of(shared_sym, TypeId::SYMBOL));
}
#[test]
fn test_iterable_protocol_types() {
let interner = TypeInterner::new();
let _checker = SubtypeChecker::new(&interner);
let value_name = interner.intern_string("value");
let done_name = interner.intern_string("done");
let iter_result = interner.object(vec![
PropertyInfo::readonly(value_name, TypeId::NUMBER),
PropertyInfo::readonly(done_name, TypeId::BOOLEAN),
]);
let next_fn = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: iter_result,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let iterator = interner.object(vec![PropertyInfo {
name: interner.intern_string("next"),
type_id: next_fn,
write_type: next_fn,
optional: false,
readonly: true,
is_method: true,
visibility: Visibility::Public,
parent_id: None,
}]);
assert!(iterator != TypeId::ERROR);
}
#[test]
fn test_async_iterable_protocol_types() {
let interner = TypeInterner::new();
let _checker = SubtypeChecker::new(&interner);
let value_name = interner.intern_string("value");
let done_name = interner.intern_string("done");
let async_iter_result = interner.object(vec![
PropertyInfo::readonly(value_name, TypeId::NUMBER),
PropertyInfo::readonly(done_name, TypeId::BOOLEAN),
]);
let promise = interner.object(vec![PropertyInfo {
name: interner.intern_string("then"),
type_id: async_iter_result,
write_type: async_iter_result,
optional: false,
readonly: true,
is_method: true,
visibility: Visibility::Public,
parent_id: None,
}]);
let next_fn = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: promise,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let async_iterator = interner.object(vec![PropertyInfo {
name: interner.intern_string("next"),
type_id: next_fn,
write_type: next_fn,
optional: false,
readonly: true,
is_method: true,
visibility: Visibility::Public,
parent_id: None,
}]);
assert!(async_iterator != TypeId::ERROR);
}
#[test]
fn test_symbol_keyof_type() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let unique_sym = interner.intern(TypeData::UniqueSymbol(SymbolRef(1)));
let keyof_result = interner.union(vec![unique_sym, interner.literal_string("name")]);
assert!(checker.is_subtype_of(unique_sym, keyof_result));
}
#[test]
fn test_symbol_in_discriminated_union() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let sym_a = interner.intern(TypeData::UniqueSymbol(SymbolRef(1)));
let sym_b = interner.intern(TypeData::UniqueSymbol(SymbolRef(2)));
let variant_a = interner.object(vec![PropertyInfo::readonly(
interner.intern_string("kind"),
sym_a,
)]);
let variant_b = interner.object(vec![PropertyInfo::readonly(
interner.intern_string("kind"),
sym_b,
)]);
let discriminated_union = interner.union(vec![variant_a, variant_b]);
assert!(checker.is_subtype_of(variant_a, discriminated_union));
assert!(checker.is_subtype_of(variant_b, discriminated_union));
assert!(!checker.is_subtype_of(variant_a, variant_b));
}
#[test]
fn test_null_not_subtype_of_string_strict() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
checker.strict_null_checks = true;
assert!(!checker.is_subtype_of(TypeId::NULL, TypeId::STRING));
}
#[test]
fn test_undefined_not_subtype_of_string_strict() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
checker.strict_null_checks = true;
assert!(!checker.is_subtype_of(TypeId::UNDEFINED, TypeId::STRING));
}
#[test]
fn test_null_subtype_of_string_legacy() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
checker.strict_null_checks = false;
assert!(checker.is_subtype_of(TypeId::NULL, TypeId::STRING));
}
#[test]
fn test_undefined_subtype_of_string_legacy() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
checker.strict_null_checks = false;
assert!(checker.is_subtype_of(TypeId::UNDEFINED, TypeId::STRING));
}
#[test]
fn test_nullable_union_string() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
checker.strict_null_checks = true;
let nullable_string = interner.union(vec![TypeId::STRING, TypeId::NULL]);
assert!(checker.is_subtype_of(TypeId::NULL, nullable_string));
assert!(checker.is_subtype_of(TypeId::STRING, nullable_string));
assert!(!checker.is_subtype_of(nullable_string, TypeId::STRING));
}
#[test]
fn test_nullable_union_number() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
checker.strict_null_checks = true;
let nullable_number = interner.union(vec![TypeId::NUMBER, TypeId::NULL]);
assert!(checker.is_subtype_of(TypeId::NULL, nullable_number));
assert!(checker.is_subtype_of(TypeId::NUMBER, nullable_number));
assert!(!checker.is_subtype_of(nullable_number, TypeId::NUMBER));
}
#[test]
fn test_optional_union_undefined() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
checker.strict_null_checks = true;
let optional_string = interner.union(vec![TypeId::STRING, TypeId::UNDEFINED]);
assert!(checker.is_subtype_of(TypeId::UNDEFINED, optional_string));
assert!(checker.is_subtype_of(TypeId::STRING, optional_string));
assert!(!checker.is_subtype_of(optional_string, TypeId::STRING));
}
#[test]
fn test_nullable_and_optional_union() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
checker.strict_null_checks = true;
let nullable_optional = interner.union(vec![TypeId::STRING, TypeId::NULL, TypeId::UNDEFINED]);
assert!(checker.is_subtype_of(TypeId::STRING, nullable_optional));
assert!(checker.is_subtype_of(TypeId::NULL, nullable_optional));
assert!(checker.is_subtype_of(TypeId::UNDEFINED, nullable_optional));
assert!(!checker.is_subtype_of(nullable_optional, TypeId::STRING));
assert!(!checker.is_subtype_of(nullable_optional, TypeId::NULL));
assert!(!checker.is_subtype_of(nullable_optional, TypeId::UNDEFINED));
}
#[test]
fn test_null_distinct_from_undefined() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
checker.strict_null_checks = true;
assert!(!checker.is_subtype_of(TypeId::NULL, TypeId::UNDEFINED));
assert!(!checker.is_subtype_of(TypeId::UNDEFINED, TypeId::NULL));
}
#[test]
fn test_null_subtype_of_self() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
checker.strict_null_checks = true;
assert!(checker.is_subtype_of(TypeId::NULL, TypeId::NULL));
}
#[test]
fn test_undefined_subtype_of_self() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
checker.strict_null_checks = true;
assert!(checker.is_subtype_of(TypeId::UNDEFINED, TypeId::UNDEFINED));
}
#[test]
fn test_null_subtype_of_any() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
checker.strict_null_checks = true;
assert!(checker.is_subtype_of(TypeId::NULL, TypeId::ANY));
}
#[test]
fn test_undefined_subtype_of_any() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
checker.strict_null_checks = true;
assert!(checker.is_subtype_of(TypeId::UNDEFINED, TypeId::ANY));
}
#[test]
fn test_null_subtype_of_unknown() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
checker.strict_null_checks = true;
assert!(checker.is_subtype_of(TypeId::NULL, TypeId::UNKNOWN));
}
#[test]
fn test_undefined_subtype_of_unknown() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
checker.strict_null_checks = true;
assert!(checker.is_subtype_of(TypeId::UNDEFINED, TypeId::UNKNOWN));
}
#[test]
fn test_null_not_subtype_of_object() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
checker.strict_null_checks = true;
assert!(!checker.is_subtype_of(TypeId::NULL, TypeId::OBJECT));
}
#[test]
fn test_undefined_not_subtype_of_object() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
checker.strict_null_checks = true;
assert!(!checker.is_subtype_of(TypeId::UNDEFINED, TypeId::OBJECT));
}
#[test]
fn test_null_not_subtype_of_never() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
checker.strict_null_checks = true;
assert!(!checker.is_subtype_of(TypeId::NULL, TypeId::NEVER));
}
#[test]
fn test_never_subtype_of_null() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
checker.strict_null_checks = true;
assert!(checker.is_subtype_of(TypeId::NEVER, TypeId::NULL));
}
#[test]
fn test_nullable_object_type() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
checker.strict_null_checks = true;
let obj = interner.object(vec![PropertyInfo::new(
interner.intern_string("x"),
TypeId::STRING,
)]);
let nullable_obj = interner.union(vec![obj, TypeId::NULL]);
assert!(checker.is_subtype_of(obj, nullable_obj));
assert!(checker.is_subtype_of(TypeId::NULL, nullable_obj));
assert!(!checker.is_subtype_of(nullable_obj, obj));
}
#[test]
fn test_nullable_function_type() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
checker.strict_null_checks = true;
let fn_type = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let nullable_fn = interner.union(vec![fn_type, TypeId::NULL]);
assert!(checker.is_subtype_of(fn_type, nullable_fn));
assert!(checker.is_subtype_of(TypeId::NULL, nullable_fn));
}
#[test]
fn test_nullable_array_type() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
checker.strict_null_checks = true;
let string_array = interner.array(TypeId::STRING);
let nullable_array = interner.union(vec![string_array, TypeId::NULL]);
assert!(checker.is_subtype_of(string_array, nullable_array));
assert!(checker.is_subtype_of(TypeId::NULL, nullable_array));
}
#[test]
fn test_void_distinct_from_undefined() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
checker.strict_null_checks = true;
assert!(checker.is_subtype_of(TypeId::UNDEFINED, TypeId::VOID));
}
#[test]
fn test_nullable_literal_type() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
checker.strict_null_checks = true;
let hello = interner.literal_string("hello");
let nullable_hello = interner.union(vec![hello, TypeId::NULL]);
assert!(checker.is_subtype_of(hello, nullable_hello));
assert!(checker.is_subtype_of(TypeId::NULL, nullable_hello));
assert!(!checker.is_subtype_of(TypeId::STRING, nullable_hello));
}
#[test]
fn test_non_null_assertion_type() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
checker.strict_null_checks = true;
let non_null_result = TypeId::STRING;
let nullable_optional = interner.union(vec![TypeId::STRING, TypeId::NULL, TypeId::UNDEFINED]);
assert!(checker.is_subtype_of(non_null_result, nullable_optional));
}
#[test]
fn test_nullable_union_widening() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
checker.strict_null_checks = true;
let nullable = interner.union(vec![TypeId::STRING, TypeId::NULL]);
let nullable_optional = interner.union(vec![TypeId::STRING, TypeId::NULL, TypeId::UNDEFINED]);
assert!(checker.is_subtype_of(nullable, nullable_optional));
assert!(!checker.is_subtype_of(nullable_optional, nullable));
}
#[test]
fn test_null_in_intersection() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
checker.strict_null_checks = true;
let intersection = interner.intersection(vec![TypeId::STRING, TypeId::NULL]);
assert!(checker.is_subtype_of(intersection, TypeId::STRING));
}
#[test]
fn test_optional_property_accepts_undefined() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
checker.strict_null_checks = true;
let optional_value = interner.union(vec![TypeId::STRING, TypeId::UNDEFINED]);
assert!(checker.is_subtype_of(TypeId::UNDEFINED, optional_value));
assert!(checker.is_subtype_of(TypeId::STRING, optional_value));
assert!(!checker.is_subtype_of(TypeId::NULL, optional_value));
}
#[test]
fn test_nullish_coalescing_result_type() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
checker.strict_null_checks = true;
let result = TypeId::STRING;
let nullable = interner.union(vec![TypeId::STRING, TypeId::NULL]);
assert!(checker.is_subtype_of(result, nullable));
}
#[test]
fn test_null_union_with_literal_numbers() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
checker.strict_null_checks = true;
let lit_1 = interner.literal_number(1.0);
let lit_2 = interner.literal_number(2.0);
let lit_3 = interner.literal_number(3.0);
let nullable_nums = interner.union(vec![lit_1, lit_2, lit_3, TypeId::NULL]);
assert!(checker.is_subtype_of(lit_1, nullable_nums));
assert!(checker.is_subtype_of(lit_2, nullable_nums));
assert!(checker.is_subtype_of(lit_3, nullable_nums));
assert!(checker.is_subtype_of(TypeId::NULL, nullable_nums));
assert!(!checker.is_subtype_of(TypeId::NUMBER, nullable_nums));
}
#[test]
fn test_undefined_union_with_boolean() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
checker.strict_null_checks = true;
let optional_bool = interner.union(vec![TypeId::BOOLEAN, TypeId::UNDEFINED]);
assert!(checker.is_subtype_of(TypeId::BOOLEAN, optional_bool));
assert!(checker.is_subtype_of(TypeId::UNDEFINED, optional_bool));
let lit_true = interner.literal_boolean(true);
let lit_false = interner.literal_boolean(false);
assert!(checker.is_subtype_of(lit_true, optional_bool));
assert!(checker.is_subtype_of(lit_false, optional_bool));
}
#[test]
fn test_primitive_intersection_string_number_is_never() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let string_and_number = interner.intersection(vec![TypeId::STRING, TypeId::NUMBER]);
assert!(checker.is_subtype_of(string_and_number, TypeId::NEVER));
}
#[test]
fn test_primitive_intersection_boolean_string_is_never() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let bool_and_string = interner.intersection(vec![TypeId::BOOLEAN, TypeId::STRING]);
assert!(checker.is_subtype_of(bool_and_string, TypeId::NEVER));
}
#[test]
fn test_primitive_intersection_number_bigint_is_never() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let num_and_bigint = interner.intersection(vec![TypeId::NUMBER, TypeId::BIGINT]);
assert!(checker.is_subtype_of(num_and_bigint, TypeId::NEVER));
}
#[test]
fn test_literal_intersection_same_type() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let hello = interner.literal_string("hello");
let hello_and_string = interner.intersection(vec![hello, TypeId::STRING]);
assert!(checker.is_subtype_of(hello_and_string, hello));
assert!(checker.is_subtype_of(hello, hello_and_string));
}
#[test]
fn test_literal_intersection_different_literals_is_never() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let hello = interner.literal_string("hello");
let world = interner.literal_string("world");
let hello_and_world = interner.intersection(vec![hello, world]);
assert!(checker.is_subtype_of(hello_and_world, TypeId::NEVER));
}
#[test]
fn test_number_literal_intersection_different_values() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let one = interner.literal_number(1.0);
let two = interner.literal_number(2.0);
let one_and_two = interner.intersection(vec![one, two]);
assert!(checker.is_subtype_of(one_and_two, TypeId::NEVER));
}
#[test]
fn test_boolean_literal_intersection() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let lit_true = interner.literal_boolean(true);
let lit_false = interner.literal_boolean(false);
let true_and_false = interner.intersection(vec![lit_true, lit_false]);
assert!(checker.is_subtype_of(true_and_false, TypeId::NEVER));
}
#[test]
fn test_object_intersection_disjoint_properties() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a_name = interner.intern_string("a");
let b_name = interner.intern_string("b");
let obj_a = interner.object(vec![PropertyInfo::new(a_name, TypeId::STRING)]);
let obj_b = interner.object(vec![PropertyInfo::new(b_name, TypeId::NUMBER)]);
let intersection = interner.intersection(vec![obj_a, obj_b]);
assert!(checker.is_subtype_of(intersection, obj_a));
assert!(checker.is_subtype_of(intersection, obj_b));
}
#[test]
fn test_object_intersection_same_property_compatible() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let x_name = interner.intern_string("x");
let obj1 = interner.object(vec![PropertyInfo::new(x_name, TypeId::STRING)]);
let obj2 = interner.object(vec![PropertyInfo::new(x_name, TypeId::STRING)]);
let intersection = interner.intersection(vec![obj1, obj2]);
assert!(checker.is_subtype_of(intersection, obj1));
assert!(checker.is_subtype_of(obj1, intersection));
}
#[test]
fn test_object_intersection_property_narrowing() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let x_name = interner.intern_string("x");
let string_or_number = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let obj_wide = interner.object(vec![PropertyInfo::new(x_name, string_or_number)]);
let obj_narrow = interner.object(vec![PropertyInfo::new(x_name, TypeId::STRING)]);
let intersection = interner.intersection(vec![obj_wide, obj_narrow]);
assert!(checker.is_subtype_of(intersection, obj_narrow));
}
#[test]
fn test_intersection_with_any() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let x_name = interner.intern_string("x");
let obj = interner.object(vec![PropertyInfo::new(x_name, TypeId::STRING)]);
let obj_and_any = interner.intersection(vec![obj, TypeId::ANY]);
assert!(checker.is_subtype_of(TypeId::ANY, obj_and_any));
}
#[test]
fn test_intersection_with_unknown() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let x_name = interner.intern_string("x");
let obj = interner.object(vec![PropertyInfo::new(x_name, TypeId::STRING)]);
let obj_and_unknown = interner.intersection(vec![obj, TypeId::UNKNOWN]);
assert!(checker.is_subtype_of(obj_and_unknown, obj));
assert!(checker.is_subtype_of(obj, obj_and_unknown));
}
#[test]
fn test_function_intersection_creates_overload() {
let interner = TypeInterner::new();
let x_name = interner.intern_string("x");
let fn_str_to_num = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo::required(x_name, TypeId::STRING)],
this_type: None,
return_type: TypeId::NUMBER,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let fn_num_to_str = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo::required(x_name, TypeId::NUMBER)],
this_type: None,
return_type: TypeId::STRING,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let intersection = interner.intersection(vec![fn_str_to_num, fn_num_to_str]);
assert!(intersection != TypeId::ERROR);
assert!(intersection != TypeId::NEVER);
}
#[test]
fn test_intersection_brand_pattern() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let brand_name = interner.intern_string("__brand");
let user_id_lit = interner.literal_string("UserId");
let brand_obj = interner.object(vec![PropertyInfo::new(brand_name, user_id_lit)]);
let branded_string = interner.intersection(vec![TypeId::STRING, brand_obj]);
assert!(!checker.is_subtype_of(TypeId::STRING, branded_string));
assert!(checker.is_subtype_of(branded_string, TypeId::STRING));
}
#[test]
fn test_intersection_different_brands_is_never() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let brand_name = interner.intern_string("__brand");
let lit_a = interner.literal_string("A");
let lit_b = interner.literal_string("B");
let brand_a = interner.object(vec![PropertyInfo::new(brand_name, lit_a)]);
let brand_b = interner.object(vec![PropertyInfo::new(brand_name, lit_b)]);
let branded_a = interner.intersection(vec![TypeId::STRING, brand_a]);
let branded_b = interner.intersection(vec![TypeId::STRING, brand_b]);
let both = interner.intersection(vec![branded_a, branded_b]);
assert!(checker.is_subtype_of(both, TypeId::NEVER));
}
#[test]
fn test_intersection_readonly_property() {
let interner = TypeInterner::new();
let _checker = SubtypeChecker::new(&interner);
let x_name = interner.intern_string("x");
let readonly_obj = interner.object(vec![PropertyInfo::readonly(x_name, TypeId::STRING)]);
let mutable_obj = interner.object(vec![PropertyInfo::new(x_name, TypeId::STRING)]);
let intersection = interner.intersection(vec![readonly_obj, mutable_obj]);
assert!(intersection != TypeId::ERROR);
}
#[test]
fn test_intersection_optional_and_required() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let x_name = interner.intern_string("x");
let optional_obj = interner.object(vec![PropertyInfo::opt(x_name, TypeId::STRING)]);
let required_obj = interner.object(vec![PropertyInfo::new(x_name, TypeId::STRING)]);
let intersection = interner.intersection(vec![optional_obj, required_obj]);
assert!(checker.is_subtype_of(intersection, required_obj));
}
#[test]
fn test_intersection_index_signature_with_properties() {
let interner = TypeInterner::new();
let x_name = interner.intern_string("x");
let index_sig = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: TypeId::NUMBER,
readonly: false,
}),
number_index: None,
});
let prop_obj = interner.object(vec![PropertyInfo::new(x_name, TypeId::NUMBER)]);
let intersection = interner.intersection(vec![index_sig, prop_obj]);
assert!(intersection != TypeId::ERROR);
}
#[test]
fn test_intersection_two_index_signatures() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let one = interner.literal_number(1.0);
let two = interner.literal_number(2.0);
let one_or_two = interner.union(vec![one, two]);
let index_number = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: TypeId::NUMBER,
readonly: false,
}),
number_index: None,
});
let index_literal = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: one_or_two,
readonly: false,
}),
number_index: None,
});
let intersection = interner.intersection(vec![index_number, index_literal]);
assert!(checker.is_subtype_of(intersection, index_literal));
}
#[test]
fn test_array_intersection() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let string_array = interner.array(TypeId::STRING);
let number_array = interner.array(TypeId::NUMBER);
let intersection = interner.intersection(vec![string_array, number_array]);
assert!(!checker.is_subtype_of(intersection, TypeId::NEVER));
}
#[test]
fn test_tuple_intersection_compatible() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let tuple = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
},
]);
let intersection = interner.intersection(vec![tuple, tuple]);
assert!(checker.is_subtype_of(intersection, tuple));
assert!(checker.is_subtype_of(tuple, intersection));
}
#[test]
fn test_tuple_intersection_incompatible() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let tuple1 = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
},
]);
let tuple2 = interner.tuple(vec![
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
]);
let intersection = interner.intersection(vec![tuple1, tuple2]);
assert!(!checker.is_subtype_of(intersection, TypeId::NEVER));
}
#[test]
fn test_intersection_union_distribution() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a_name = interner.intern_string("a");
let b_name = interner.intern_string("b");
let c_name = interner.intern_string("c");
let obj_a = interner.object(vec![PropertyInfo::new(a_name, TypeId::STRING)]);
let obj_b = interner.object(vec![PropertyInfo::new(b_name, TypeId::NUMBER)]);
let obj_c = interner.object(vec![PropertyInfo::new(c_name, TypeId::BOOLEAN)]);
let a_or_b = interner.union(vec![obj_a, obj_b]);
let union_and_c = interner.intersection(vec![a_or_b, obj_c]);
let a_and_c = interner.intersection(vec![obj_a, obj_c]);
let b_and_c = interner.intersection(vec![obj_b, obj_c]);
let distributed = interner.union(vec![a_and_c, b_and_c]);
assert!(checker.is_subtype_of(union_and_c, distributed));
assert!(checker.is_subtype_of(distributed, union_and_c));
}
#[test]
fn test_intersection_null_with_object_is_never() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let x_name = interner.intern_string("x");
let obj = interner.object(vec![PropertyInfo::new(x_name, TypeId::STRING)]);
let null_and_obj = interner.intersection(vec![TypeId::NULL, obj]);
assert!(checker.is_subtype_of(null_and_obj, TypeId::NEVER));
}
#[test]
fn test_intersection_undefined_with_object_is_never() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let x_name = interner.intern_string("x");
let obj = interner.object(vec![PropertyInfo::new(x_name, TypeId::STRING)]);
let undefined_and_obj = interner.intersection(vec![TypeId::UNDEFINED, obj]);
assert!(checker.is_subtype_of(undefined_and_obj, TypeId::NEVER));
}
#[test]
fn test_intersection_method_signatures() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let foo_name = interner.intern_string("foo");
let bar_name = interner.intern_string("bar");
let fn_void = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let obj_foo = interner.object(vec![PropertyInfo::method(foo_name, fn_void)]);
let obj_bar = interner.object(vec![PropertyInfo::method(bar_name, fn_void)]);
let intersection = interner.intersection(vec![obj_foo, obj_bar]);
assert!(checker.is_subtype_of(intersection, obj_foo));
assert!(checker.is_subtype_of(intersection, obj_bar));
}
#[test]
fn test_intersection_same_method_different_returns() {
let interner = TypeInterner::new();
let foo_name = interner.intern_string("foo");
let fn_string = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::STRING,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let fn_number = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::NUMBER,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let obj_foo_string = interner.object(vec![PropertyInfo::method(foo_name, fn_string)]);
let obj_foo_number = interner.object(vec![PropertyInfo::method(foo_name, fn_number)]);
let intersection = interner.intersection(vec![obj_foo_string, obj_foo_number]);
assert!(intersection != TypeId::ERROR);
}
#[test]
fn test_intersection_three_objects() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a_name = interner.intern_string("a");
let b_name = interner.intern_string("b");
let c_name = interner.intern_string("c");
let obj_a = interner.object(vec![PropertyInfo::new(a_name, TypeId::STRING)]);
let obj_b = interner.object(vec![PropertyInfo::new(b_name, TypeId::NUMBER)]);
let obj_c = interner.object(vec![PropertyInfo::new(c_name, TypeId::BOOLEAN)]);
let intersection = interner.intersection(vec![obj_a, obj_b, obj_c]);
assert!(checker.is_subtype_of(intersection, obj_a));
assert!(checker.is_subtype_of(intersection, obj_b));
assert!(checker.is_subtype_of(intersection, obj_c));
}
#[test]
fn test_intersection_symbol_with_primitive_is_never() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let symbol_and_string = interner.intersection(vec![TypeId::SYMBOL, TypeId::STRING]);
assert!(checker.is_subtype_of(symbol_and_string, TypeId::NEVER));
}
#[test]
fn test_intersection_object_intrinsic_with_object() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let x_name = interner.intern_string("x");
let obj = interner.object(vec![PropertyInfo::new(x_name, TypeId::STRING)]);
let object_and_obj = interner.intersection(vec![TypeId::OBJECT, obj]);
assert!(checker.is_subtype_of(object_and_obj, obj));
}
#[test]
fn test_intersection_never_identity() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let x_name = interner.intern_string("x");
let obj = interner.object(vec![PropertyInfo::new(x_name, TypeId::STRING)]);
let never_and_obj = interner.intersection(vec![TypeId::NEVER, obj]);
let obj_and_never = interner.intersection(vec![obj, TypeId::NEVER]);
assert!(checker.is_subtype_of(never_and_obj, TypeId::NEVER));
assert!(checker.is_subtype_of(obj_and_never, TypeId::NEVER));
}
#[test]
fn test_keyof_single_property_is_literal() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let x_name = interner.intern_string("x");
let obj = interner.object(vec![PropertyInfo::new(x_name, TypeId::NUMBER)]);
let keyof_obj = interner.intern(TypeData::KeyOf(obj));
let lit_x = interner.literal_string("x");
assert!(checker.is_subtype_of(keyof_obj, lit_x));
}
#[test]
fn test_keyof_multiple_properties_is_union() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let obj = interner.object(vec![
PropertyInfo::new(interner.intern_string("a"), TypeId::NUMBER),
PropertyInfo::new(interner.intern_string("b"), TypeId::STRING),
PropertyInfo::new(interner.intern_string("c"), TypeId::BOOLEAN),
]);
let keyof_obj = interner.intern(TypeData::KeyOf(obj));
let lit_a = interner.literal_string("a");
let lit_b = interner.literal_string("b");
let lit_c = interner.literal_string("c");
let expected = interner.union(vec![lit_a, lit_b, lit_c]);
assert!(checker.is_subtype_of(lit_a, keyof_obj));
assert!(checker.is_subtype_of(lit_b, keyof_obj));
assert!(checker.is_subtype_of(lit_c, keyof_obj));
assert!(checker.is_subtype_of(keyof_obj, expected));
}
#[test]
fn test_keyof_empty_object_is_never() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let empty_obj = interner.object(vec![]);
let keyof_empty = interner.intern(TypeData::KeyOf(empty_obj));
assert!(checker.is_subtype_of(keyof_empty, TypeId::NEVER));
}
#[test]
fn test_keyof_with_optional_property() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let x_name = interner.intern_string("x");
let obj = interner.object(vec![PropertyInfo::opt(x_name, TypeId::NUMBER)]);
let keyof_obj = interner.intern(TypeData::KeyOf(obj));
let lit_x = interner.literal_string("x");
assert!(checker.is_subtype_of(lit_x, keyof_obj));
}
#[test]
fn test_keyof_with_readonly_property() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let x_name = interner.intern_string("x");
let obj = interner.object(vec![PropertyInfo::readonly(x_name, TypeId::NUMBER)]);
let keyof_obj = interner.intern(TypeData::KeyOf(obj));
let lit_x = interner.literal_string("x");
assert!(checker.is_subtype_of(lit_x, keyof_obj));
}
#[test]
fn test_keyof_with_method() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let foo_name = interner.intern_string("foo");
let fn_void = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let obj = interner.object(vec![PropertyInfo::method(foo_name, fn_void)]);
let keyof_obj = interner.intern(TypeData::KeyOf(obj));
let lit_foo = interner.literal_string("foo");
assert!(checker.is_subtype_of(lit_foo, keyof_obj));
}
#[test]
fn test_keyof_subtype_of_string() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let x_name = interner.intern_string("x");
let obj = interner.object(vec![PropertyInfo::new(x_name, TypeId::NUMBER)]);
let keyof_obj = interner.intern(TypeData::KeyOf(obj));
assert!(checker.is_subtype_of(keyof_obj, TypeId::STRING));
}
#[test]
fn test_keyof_not_equal_to_string() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let x_name = interner.intern_string("x");
let obj = interner.object(vec![PropertyInfo::new(x_name, TypeId::NUMBER)]);
let keyof_obj = interner.intern(TypeData::KeyOf(obj));
assert!(!checker.is_subtype_of(TypeId::STRING, keyof_obj));
}
#[test]
fn test_keyof_wider_object_has_more_keys() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let obj_a = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
TypeId::NUMBER,
)]);
let obj_ab = interner.object(vec![
PropertyInfo::new(interner.intern_string("a"), TypeId::NUMBER),
PropertyInfo::new(interner.intern_string("b"), TypeId::STRING),
]);
let keyof_a = interner.intern(TypeData::KeyOf(obj_a));
let keyof_ab = interner.intern(TypeData::KeyOf(obj_ab));
assert!(checker.is_subtype_of(keyof_a, keyof_ab));
assert!(!checker.is_subtype_of(keyof_ab, keyof_a));
}
#[test]
fn test_keyof_union_is_intersection_of_keys() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let obj_ab = interner.object(vec![
PropertyInfo::new(interner.intern_string("a"), TypeId::NUMBER),
PropertyInfo::new(interner.intern_string("b"), TypeId::STRING),
]);
let obj_bc = interner.object(vec![
PropertyInfo::new(interner.intern_string("b"), TypeId::STRING),
PropertyInfo::new(interner.intern_string("c"), TypeId::BOOLEAN),
]);
let union = interner.union(vec![obj_ab, obj_bc]);
let keyof_union = interner.intern(TypeData::KeyOf(union));
let lit_b = interner.literal_string("b");
assert!(checker.is_subtype_of(lit_b, keyof_union));
}
#[test]
fn test_keyof_intersection_is_union_of_keys() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
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 intersection = interner.intersection(vec![obj_a, obj_b]);
let keyof_intersection = interner.intern(TypeData::KeyOf(intersection));
let lit_a = interner.literal_string("a");
let lit_b = interner.literal_string("b");
assert!(checker.is_subtype_of(lit_a, keyof_intersection));
assert!(checker.is_subtype_of(lit_b, keyof_intersection));
}
#[test]
fn test_keyof_any_is_string_number_symbol() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let keyof_any = interner.intern(TypeData::KeyOf(TypeId::ANY));
let property_key = interner.union(vec![TypeId::STRING, TypeId::NUMBER, TypeId::SYMBOL]);
assert!(checker.is_subtype_of(keyof_any, property_key));
}
#[test]
fn test_keyof_unknown_is_never() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let keyof_unknown = interner.intern(TypeData::KeyOf(TypeId::UNKNOWN));
assert!(checker.is_subtype_of(keyof_unknown, TypeId::NEVER));
}
#[test]
fn test_keyof_never_is_string_number_symbol() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let keyof_never = interner.intern(TypeData::KeyOf(TypeId::NEVER));
let property_key = interner.union(vec![TypeId::STRING, TypeId::NUMBER, TypeId::SYMBOL]);
assert!(checker.is_subtype_of(keyof_never, property_key));
}
#[test]
fn test_keyof_string_has_string_methods() {
let interner = TypeInterner::new();
let keyof_string = interner.intern(TypeData::KeyOf(TypeId::STRING));
assert!(keyof_string != TypeId::ERROR);
}
#[test]
fn test_keyof_number_has_number_methods() {
let interner = TypeInterner::new();
let keyof_number = interner.intern(TypeData::KeyOf(TypeId::NUMBER));
assert!(keyof_number != TypeId::ERROR);
}
#[test]
fn test_keyof_array_type() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let string_array = interner.array(TypeId::STRING);
let keyof_array = interner.intern(TypeData::KeyOf(string_array));
assert!(checker.is_subtype_of(TypeId::NUMBER, keyof_array));
}
#[test]
fn test_keyof_tuple_type() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let tuple = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
},
]);
let keyof_tuple = interner.intern(TypeData::KeyOf(tuple));
let lit_0 = interner.literal_string("0");
let lit_1 = interner.literal_string("1");
assert!(checker.is_subtype_of(lit_0, keyof_tuple));
assert!(checker.is_subtype_of(lit_1, keyof_tuple));
}
#[test]
fn test_keyof_with_index_signature_includes_string() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let indexed_obj = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: TypeId::NUMBER,
readonly: false,
}),
number_index: None,
});
let keyof_indexed = interner.intern(TypeData::KeyOf(indexed_obj));
assert!(checker.is_subtype_of(TypeId::STRING, keyof_indexed));
}
#[test]
fn test_keyof_with_number_index_signature() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let indexed_obj = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
string_index: None,
number_index: Some(IndexSignature {
key_type: TypeId::NUMBER,
value_type: TypeId::STRING,
readonly: false,
}),
});
let keyof_indexed = interner.intern(TypeData::KeyOf(indexed_obj));
assert!(checker.is_subtype_of(TypeId::NUMBER, keyof_indexed));
}
#[test]
fn test_keyof_nested_object() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let y_name = interner.intern_string("y");
let inner_obj = interner.object(vec![PropertyInfo::new(y_name, TypeId::NUMBER)]);
let x_name = interner.intern_string("x");
let outer_obj = interner.object(vec![PropertyInfo::new(x_name, inner_obj)]);
let keyof_outer = interner.intern(TypeData::KeyOf(outer_obj));
let lit_x = interner.literal_string("x");
let lit_y = interner.literal_string("y");
assert!(checker.is_subtype_of(lit_x, keyof_outer));
assert!(!checker.is_subtype_of(lit_y, keyof_outer));
}
#[test]
fn test_keyof_generic_constraint() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let obj = interner.object(vec![
PropertyInfo::new(interner.intern_string("name"), TypeId::STRING),
PropertyInfo::new(interner.intern_string("age"), TypeId::NUMBER),
]);
let keyof_obj = interner.intern(TypeData::KeyOf(obj));
let lit_name = interner.literal_string("name");
let lit_age = interner.literal_string("age");
let lit_invalid = interner.literal_string("invalid");
assert!(checker.is_subtype_of(lit_name, keyof_obj));
assert!(checker.is_subtype_of(lit_age, keyof_obj));
assert!(!checker.is_subtype_of(lit_invalid, keyof_obj));
}
#[test]
fn test_keyof_mapped_type_source() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let obj = interner.object(vec![
PropertyInfo::new(interner.intern_string("x"), TypeId::NUMBER),
PropertyInfo::new(interner.intern_string("y"), TypeId::NUMBER),
]);
let keyof_obj = interner.intern(TypeData::KeyOf(obj));
assert!(keyof_obj != TypeId::ERROR);
assert!(keyof_obj != TypeId::NEVER);
assert!(checker.is_subtype_of(keyof_obj, TypeId::STRING));
}
#[test]
fn test_keyof_reflexive() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let obj = interner.object(vec![PropertyInfo::new(
interner.intern_string("x"),
TypeId::NUMBER,
)]);
let keyof_obj = interner.intern(TypeData::KeyOf(obj));
assert!(checker.is_subtype_of(keyof_obj, keyof_obj));
}
#[test]
fn test_keyof_null_is_never() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let keyof_null = interner.intern(TypeData::KeyOf(TypeId::NULL));
assert!(checker.is_subtype_of(keyof_null, TypeId::NEVER));
}
#[test]
fn test_keyof_undefined_is_never() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let keyof_undefined = interner.intern(TypeData::KeyOf(TypeId::UNDEFINED));
assert!(checker.is_subtype_of(keyof_undefined, TypeId::NEVER));
}
#[test]
fn test_keyof_void_is_never() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let keyof_void = interner.intern(TypeData::KeyOf(TypeId::VOID));
assert!(checker.is_subtype_of(keyof_void, TypeId::NEVER));
}
#[test]
fn test_keyof_object_intrinsic() {
let interner = TypeInterner::new();
let keyof_object = interner.intern(TypeData::KeyOf(TypeId::OBJECT));
assert!(keyof_object != TypeId::ERROR);
}
#[test]
fn test_keyof_symbol_keyed_object() {
let interner = TypeInterner::new();
let _checker = SubtypeChecker::new(&interner);
let sym_iterator = interner.intern_string("Symbol.iterator");
let fn_iterator = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::OBJECT,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let obj = interner.object(vec![PropertyInfo::method(sym_iterator, fn_iterator)]);
let keyof_obj = interner.intern(TypeData::KeyOf(obj));
assert!(keyof_obj != TypeId::NEVER);
}
#[test]
fn test_constructor_basic_new_signature() {
let interner = TypeInterner::new();
let _checker = SubtypeChecker::new(&interner);
let instance = interner.object(vec![PropertyInfo::new(
interner.intern_string("x"),
TypeId::NUMBER,
)]);
let constructor = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: instance,
type_predicate: None,
is_constructor: true,
is_method: false,
});
assert!(constructor != TypeId::ERROR);
assert!(constructor != TypeId::NEVER);
}
#[test]
fn test_constructor_with_parameters() {
let interner = TypeInterner::new();
let instance = interner.object(vec![
PropertyInfo::new(interner.intern_string("x"), TypeId::STRING),
PropertyInfo::new(interner.intern_string("y"), TypeId::NUMBER),
]);
let constructor = interner.function(FunctionShape {
type_params: vec![],
params: vec![
ParamInfo {
name: Some(interner.intern_string("x")),
type_id: TypeId::STRING,
optional: false,
rest: false,
},
ParamInfo {
name: Some(interner.intern_string("y")),
type_id: TypeId::NUMBER,
optional: false,
rest: false,
},
],
this_type: None,
return_type: instance,
type_predicate: None,
is_constructor: true,
is_method: false,
});
assert!(constructor != TypeId::ERROR);
}
#[test]
fn test_constructor_vs_regular_function() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let instance = interner.object(vec![PropertyInfo::new(
interner.intern_string("x"),
TypeId::NUMBER,
)]);
let constructor = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: instance,
type_predicate: None,
is_constructor: true,
is_method: false,
});
let regular_fn = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: instance,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(!checker.is_subtype_of(constructor, regular_fn));
assert!(!checker.is_subtype_of(regular_fn, constructor));
}
#[test]
fn test_constructor_callable_with_construct_signature() {
let interner = TypeInterner::new();
let _checker = SubtypeChecker::new(&interner);
let instance = interner.object(vec![PropertyInfo::new(
interner.intern_string("value"),
TypeId::STRING,
)]);
let callable_with_new = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![],
construct_signatures: vec![CallSignature {
type_params: vec![],
params: vec![],
this_type: None,
return_type: instance,
type_predicate: None,
is_method: false,
}],
properties: vec![],
string_index: None,
number_index: None,
});
assert!(callable_with_new != TypeId::ERROR);
}
#[test]
fn test_constructor_with_call_and_construct() {
let interner = TypeInterner::new();
let instance = interner.object(vec![PropertyInfo::new(
interner.intern_string("x"),
TypeId::NUMBER,
)]);
let callable_both = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![CallSignature {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::STRING,
type_predicate: None,
is_method: false,
}],
construct_signatures: vec![CallSignature {
type_params: vec![],
params: vec![],
this_type: None,
return_type: instance,
type_predicate: None,
is_method: false,
}],
properties: vec![],
string_index: None,
number_index: None,
});
assert!(callable_both != TypeId::ERROR);
}
#[test]
fn test_constructor_subtype_by_return_type() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let base = interner.object(vec![PropertyInfo::new(
interner.intern_string("x"),
TypeId::NUMBER,
)]);
let derived = interner.object(vec![
PropertyInfo::new(interner.intern_string("x"), TypeId::NUMBER),
PropertyInfo::new(interner.intern_string("y"), TypeId::STRING),
]);
let ctor_base = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: base,
type_predicate: None,
is_constructor: true,
is_method: false,
});
let ctor_derived = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: derived,
type_predicate: None,
is_constructor: true,
is_method: false,
});
assert!(checker.is_subtype_of(ctor_derived, ctor_base));
assert!(!checker.is_subtype_of(ctor_base, ctor_derived));
}
#[test]
fn test_constructor_contravariant_parameters() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let instance = interner.object(vec![PropertyInfo::new(
interner.intern_string("result"),
TypeId::BOOLEAN,
)]);
let string_or_number = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let ctor_wide_param = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: string_or_number,
optional: false,
rest: false,
}],
this_type: None,
return_type: instance,
type_predicate: None,
is_constructor: true,
is_method: false,
});
let ctor_narrow_param = 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: instance,
type_predicate: None,
is_constructor: true,
is_method: false,
});
assert!(checker.is_subtype_of(ctor_wide_param, ctor_narrow_param));
}
#[test]
fn test_constructor_optional_parameter() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let instance = interner.object(vec![]);
let ctor_optional = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: TypeId::STRING,
optional: true,
rest: false,
}],
this_type: None,
return_type: instance,
type_predicate: None,
is_constructor: true,
is_method: false,
});
let ctor_required = 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: instance,
type_predicate: None,
is_constructor: true,
is_method: false,
});
assert!(checker.is_subtype_of(ctor_optional, ctor_required));
}
#[test]
fn test_constructor_rest_parameter() {
let interner = TypeInterner::new();
let instance = interner.object(vec![]);
let string_array = interner.array(TypeId::STRING);
let ctor_rest = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("args")),
type_id: string_array,
optional: false,
rest: true,
}],
this_type: None,
return_type: instance,
type_predicate: None,
is_constructor: true,
is_method: false,
});
assert!(ctor_rest != TypeId::ERROR);
}
#[test]
fn test_constructor_overload_signatures() {
let interner = TypeInterner::new();
let instance_a = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
TypeId::NUMBER,
)]);
let instance_b = interner.object(vec![PropertyInfo::new(
interner.intern_string("b"),
TypeId::STRING,
)]);
let overloaded_ctor = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![],
construct_signatures: vec![
CallSignature {
type_params: vec![],
params: vec![],
this_type: None,
return_type: instance_a,
type_predicate: None,
is_method: false,
},
CallSignature {
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: instance_b,
type_predicate: None,
is_method: false,
},
],
properties: vec![],
string_index: None,
number_index: None,
});
assert!(overloaded_ctor != TypeId::ERROR);
}
#[test]
fn test_constructor_generic_type_param() {
let interner = TypeInterner::new();
let t_name = interner.intern_string("T");
let t_param = TypeParamInfo {
name: t_name,
constraint: None,
default: None,
is_const: false,
};
let t_type = interner.intern(TypeData::TypeParameter(t_param.clone()));
let generic_ctor = interner.function(FunctionShape {
type_params: vec![t_param],
params: vec![],
this_type: None,
return_type: t_type,
type_predicate: None,
is_constructor: true,
is_method: false,
});
assert!(generic_ctor != TypeId::ERROR);
}
#[test]
fn test_constructor_generic_with_constraint() {
let interner = TypeInterner::new();
let t_name = interner.intern_string("T");
let t_param = TypeParamInfo {
name: t_name,
constraint: Some(TypeId::OBJECT),
default: None,
is_const: false,
};
let t_type = interner.intern(TypeData::TypeParameter(t_param.clone()));
let constrained_ctor = interner.function(FunctionShape {
type_params: vec![t_param],
params: vec![],
this_type: None,
return_type: t_type,
type_predicate: None,
is_constructor: true,
is_method: false,
});
assert!(constrained_ctor != TypeId::ERROR);
}
#[test]
fn test_constructor_abstract_pattern() {
let interner = TypeInterner::new();
let _checker = SubtypeChecker::new(&interner);
let instance = interner.object(vec![PropertyInfo::new(
interner.intern_string("x"),
TypeId::NUMBER,
)]);
let abstract_ctor = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![],
construct_signatures: vec![CallSignature {
type_params: vec![],
params: vec![],
this_type: None,
return_type: instance,
type_predicate: None,
is_method: false,
}],
properties: vec![],
string_index: None,
number_index: None,
});
let concrete_ctor = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: instance,
type_predicate: None,
is_constructor: true,
is_method: false,
});
assert!(abstract_ctor != TypeId::ERROR);
assert!(concrete_ctor != TypeId::ERROR);
}
#[test]
fn test_constructor_with_static_properties() {
let interner = TypeInterner::new();
let instance = interner.object(vec![PropertyInfo::new(
interner.intern_string("value"),
TypeId::NUMBER,
)]);
let ctor_with_static = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![],
construct_signatures: vec![CallSignature {
type_params: vec![],
params: vec![],
this_type: None,
return_type: instance,
type_predicate: None,
is_method: false,
}],
properties: vec![PropertyInfo {
name: interner.intern_string("create"),
type_id: interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: instance,
type_predicate: None,
is_constructor: false,
is_method: false,
}),
write_type: TypeId::NEVER,
optional: false,
readonly: true,
is_method: true,
visibility: Visibility::Public,
parent_id: None,
}],
string_index: None,
number_index: None,
});
assert!(ctor_with_static != TypeId::ERROR);
}
#[test]
fn test_constructor_instance_type_extraction() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let instance = interner.object(vec![
PropertyInfo::new(interner.intern_string("x"), TypeId::NUMBER),
PropertyInfo::new(interner.intern_string("y"), TypeId::STRING),
]);
let ctor = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: instance,
type_predicate: None,
is_constructor: true,
is_method: false,
});
assert!(ctor != TypeId::ERROR);
assert!(checker.is_subtype_of(instance, instance));
}
#[test]
fn test_constructor_parameters_extraction() {
let interner = TypeInterner::new();
let instance = interner.object(vec![]);
let ctor = interner.function(FunctionShape {
type_params: vec![],
params: vec![
ParamInfo {
name: Some(interner.intern_string("name")),
type_id: TypeId::STRING,
optional: false,
rest: false,
},
ParamInfo {
name: Some(interner.intern_string("age")),
type_id: TypeId::NUMBER,
optional: false,
rest: false,
},
],
this_type: None,
return_type: instance,
type_predicate: None,
is_constructor: true,
is_method: false,
});
let params_tuple = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: Some(interner.intern_string("name")),
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::NUMBER,
name: Some(interner.intern_string("age")),
optional: false,
rest: false,
},
]);
assert!(ctor != TypeId::ERROR);
assert!(params_tuple != TypeId::ERROR);
}
#[test]
fn test_constructor_reflexive() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let instance = interner.object(vec![PropertyInfo::new(
interner.intern_string("x"),
TypeId::NUMBER,
)]);
let ctor = 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: instance,
type_predicate: None,
is_constructor: true,
is_method: false,
});
assert!(checker.is_subtype_of(ctor, ctor));
}
#[test]
fn test_constructor_never_return() {
let interner = TypeInterner::new();
let throwing_ctor = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::NEVER,
type_predicate: None,
is_constructor: true,
is_method: false,
});
assert!(throwing_ctor != TypeId::ERROR);
}
#[test]
fn test_constructor_any_return() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let ctor_any = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::ANY,
type_predicate: None,
is_constructor: true,
is_method: false,
});
let instance = interner.object(vec![PropertyInfo::new(
interner.intern_string("x"),
TypeId::NUMBER,
)]);
let ctor_specific = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: instance,
type_predicate: None,
is_constructor: true,
is_method: false,
});
assert!(checker.is_subtype_of(ctor_any, ctor_specific));
}
#[test]
fn test_constructor_multiple_construct_signatures_subtype() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let instance = interner.object(vec![PropertyInfo::new(
interner.intern_string("x"),
TypeId::NUMBER,
)]);
let single_sig = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![],
construct_signatures: vec![CallSignature {
type_params: vec![],
params: vec![],
this_type: None,
return_type: instance,
type_predicate: None,
is_method: false,
}],
properties: vec![],
string_index: None,
number_index: None,
});
let double_sig = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![],
construct_signatures: vec![
CallSignature {
type_params: vec![],
params: vec![],
this_type: None,
return_type: instance,
type_predicate: None,
is_method: false,
},
CallSignature {
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: instance,
type_predicate: None,
is_method: false,
},
],
properties: vec![],
string_index: None,
number_index: None,
});
assert!(checker.is_subtype_of(single_sig, double_sig));
}
#[test]
fn test_constructor_with_this_type() {
let interner = TypeInterner::new();
let window_type = interner.object(vec![PropertyInfo::readonly(
interner.intern_string("document"),
TypeId::OBJECT,
)]);
let instance = interner.object(vec![]);
let ctor_with_this = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: Some(window_type),
return_type: instance,
type_predicate: None,
is_constructor: true,
is_method: false,
});
assert!(ctor_with_this != TypeId::ERROR);
}
#[test]
fn test_constructor_empty_vs_nonempty() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let empty_instance = interner.object(vec![]);
let nonempty_instance = interner.object(vec![PropertyInfo::new(
interner.intern_string("x"),
TypeId::NUMBER,
)]);
let ctor_empty = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: empty_instance,
type_predicate: None,
is_constructor: true,
is_method: false,
});
let ctor_nonempty = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: nonempty_instance,
type_predicate: None,
is_constructor: true,
is_method: false,
});
assert!(checker.is_subtype_of(ctor_nonempty, ctor_empty));
assert!(!checker.is_subtype_of(ctor_empty, ctor_nonempty));
}
#[test]
fn test_this_type_basic() {
let interner = TypeInterner::new();
let this_type = interner.intern(TypeData::ThisType);
assert!(this_type != TypeId::ERROR);
assert!(this_type != TypeId::NEVER);
}
#[test]
fn test_this_type_in_method_return() {
let interner = TypeInterner::new();
let this_type = interner.intern(TypeData::ThisType);
let fluent_method = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: this_type,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let obj = interner.object(vec![PropertyInfo::method(
interner.intern_string("setName"),
fluent_method,
)]);
assert!(obj != TypeId::ERROR);
}
#[test]
fn test_this_type_fluent_builder() {
let interner = TypeInterner::new();
let this_type = interner.intern(TypeData::ThisType);
let result_type = interner.lazy(DefId(100));
let set_name = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("name")),
type_id: TypeId::STRING,
optional: false,
rest: false,
}],
this_type: None,
return_type: this_type,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let set_value = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("value")),
type_id: TypeId::NUMBER,
optional: false,
rest: false,
}],
this_type: None,
return_type: this_type,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let build = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: result_type,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let builder = interner.object(vec![
PropertyInfo::method(interner.intern_string("setName"), set_name),
PropertyInfo::method(interner.intern_string("setValue"), set_value),
PropertyInfo::method(interner.intern_string("build"), build),
]);
assert!(builder != TypeId::ERROR);
}
#[test]
fn test_this_type_with_explicit_this_parameter() {
let interner = TypeInterner::new();
let my_class = interner.lazy(DefId(1));
let method_with_this = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: Some(my_class),
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(method_with_this != TypeId::ERROR);
}
#[test]
fn test_this_type_with_this_constraint() {
let interner = TypeInterner::new();
let t_param = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: interner.intern_string("T"),
constraint: Some(interner.lazy(DefId(1))),
default: None,
is_const: false,
}));
let constrained_method = interner.function(FunctionShape {
type_params: vec![TypeParamInfo {
name: interner.intern_string("T"),
constraint: Some(interner.lazy(DefId(1))),
default: None,
is_const: false,
}],
params: vec![],
this_type: Some(t_param),
return_type: t_param,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(constrained_method != TypeId::ERROR);
}
#[test]
fn test_this_type_in_callback() {
let interner = TypeInterner::new();
let context_type = interner.lazy(DefId(1));
let callback = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: Some(context_type),
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let obj = interner.object(vec![PropertyInfo::new(
interner.intern_string("onClick"),
callback,
)]);
assert!(obj != TypeId::ERROR);
}
#[test]
fn test_this_type_subtype_check() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let this_type = interner.intern(TypeData::ThisType);
assert!(checker.is_subtype_of(this_type, TypeId::UNKNOWN));
}
#[test]
fn test_this_type_in_class_method() {
let interner = TypeInterner::new();
let this_type = interner.intern(TypeData::ThisType);
let chain_method = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: this_type,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let chainable = interner.object(vec![PropertyInfo::method(
interner.intern_string("chain"),
chain_method,
)]);
assert!(chainable != TypeId::ERROR);
}
#[test]
fn test_this_type_with_generic_method() {
let interner = TypeInterner::new();
let this_type = interner.intern(TypeData::ThisType);
let t_ref = interner.lazy(DefId(50));
let generic_fluent = interner.function(FunctionShape {
type_params: vec![TypeParamInfo {
name: interner.intern_string("T"),
constraint: None,
default: None,
is_const: false,
}],
params: vec![ParamInfo {
name: Some(interner.intern_string("value")),
type_id: t_ref,
optional: false,
rest: false,
}],
this_type: None,
return_type: this_type,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(generic_fluent != TypeId::ERROR);
}
#[test]
fn test_this_type_with_property_access() {
let interner = TypeInterner::new();
let this_type = interner.intern(TypeData::ThisType);
let obj = interner.object(vec![PropertyInfo::readonly(
interner.intern_string("self"),
this_type,
)]);
assert!(obj != TypeId::ERROR);
}
#[test]
fn test_this_type_array() {
let interner = TypeInterner::new();
let this_type = interner.intern(TypeData::ThisType);
let this_array = interner.array(this_type);
assert!(this_array != TypeId::ERROR);
}
#[test]
fn test_this_type_in_union() {
let interner = TypeInterner::new();
let this_type = interner.intern(TypeData::ThisType);
let nullable_this = interner.union(vec![this_type, TypeId::NULL]);
assert!(nullable_this != TypeId::ERROR);
}
#[test]
fn test_this_type_in_intersection() {
let interner = TypeInterner::new();
let this_type = interner.intern(TypeData::ThisType);
let has_id = interner.object(vec![PropertyInfo::new(
interner.intern_string("id"),
TypeId::STRING,
)]);
let this_with_id = interner.intersection(vec![this_type, has_id]);
assert!(this_with_id != TypeId::ERROR);
}
#[test]
fn test_this_type_clone_method() {
let interner = TypeInterner::new();
let this_type = interner.intern(TypeData::ThisType);
let clone_method = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: this_type,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let cloneable = interner.object(vec![PropertyInfo::method(
interner.intern_string("clone"),
clone_method,
)]);
assert!(cloneable != TypeId::ERROR);
}
#[test]
fn test_this_type_with_optional_chaining() {
let interner = TypeInterner::new();
let this_type = interner.intern(TypeData::ThisType);
let optional_this = interner.union(vec![this_type, TypeId::UNDEFINED]);
let optional_chain = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: optional_this,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(optional_chain != TypeId::ERROR);
}
#[test]
fn test_this_type_with_promise() {
let interner = TypeInterner::new();
let this_type = interner.intern(TypeData::ThisType);
let promise_this = interner.application(interner.lazy(DefId(100)), vec![this_type]);
let async_method = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: promise_this,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(async_method != TypeId::ERROR);
}
#[test]
fn test_this_type_in_tuple() {
let interner = TypeInterner::new();
let this_type = interner.intern(TypeData::ThisType);
let tuple_with_this = interner.tuple(vec![
TupleElement {
type_id: this_type,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
},
]);
assert!(tuple_with_this != TypeId::ERROR);
}
#[test]
fn test_this_type_map_method() {
let interner = TypeInterner::new();
let this_type = interner.intern(TypeData::ThisType);
let u_ref = interner.lazy(DefId(50));
let mapper_fn = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("value")),
type_id: this_type,
optional: false,
rest: false,
}],
this_type: None,
return_type: u_ref,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let map_method = interner.function(FunctionShape {
type_params: vec![TypeParamInfo {
name: interner.intern_string("U"),
constraint: None,
default: None,
is_const: false,
}],
params: vec![ParamInfo {
name: Some(interner.intern_string("fn")),
type_id: mapper_fn,
optional: false,
rest: false,
}],
this_type: None,
return_type: u_ref,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(map_method != TypeId::ERROR);
}
#[test]
fn test_this_type_with_readonly() {
let interner = TypeInterner::new();
let this_type = interner.intern(TypeData::ThisType);
let readonly_this = interner.application(interner.lazy(DefId(100)), vec![this_type]);
assert!(readonly_this != TypeId::ERROR);
}
#[test]
fn test_this_type_partial() {
let interner = TypeInterner::new();
let this_type = interner.intern(TypeData::ThisType);
let partial_this = interner.application(interner.lazy(DefId(101)), vec![this_type]);
assert!(partial_this != TypeId::ERROR);
}
#[test]
fn test_this_type_with_keyof() {
let interner = TypeInterner::new();
let this_type = interner.intern(TypeData::ThisType);
let keyof_this = interner.intern(TypeData::KeyOf(this_type));
assert!(keyof_this != TypeId::ERROR);
}
#[test]
fn test_this_type_indexed_access() {
let interner = TypeInterner::new();
let this_type = interner.intern(TypeData::ThisType);
let k_ref = interner.lazy(DefId(50));
let indexed = interner.intern(TypeData::IndexAccess(this_type, k_ref));
assert!(indexed != TypeId::ERROR);
}
#[test]
fn test_this_type_with_extends() {
let interner = TypeInterner::new();
let this_type = interner.intern(TypeData::ThisType);
let some_interface = interner.lazy(DefId(1));
let cond = ConditionalType {
check_type: this_type,
extends_type: some_interface,
true_type: TypeId::STRING,
false_type: TypeId::NUMBER,
is_distributive: false,
};
let conditional = interner.conditional(cond);
assert!(conditional != TypeId::ERROR);
}
#[test]
fn test_this_type_method_decorator_pattern() {
let interner = TypeInterner::new();
let this_type = interner.intern(TypeData::ThisType);
let decorated = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: Some(this_type),
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(decorated != TypeId::ERROR);
}
#[test]
fn test_this_type_static_vs_instance() {
let interner = TypeInterner::new();
let this_type = interner.intern(TypeData::ThisType);
let static_method = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::STRING,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let instance_method = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: this_type,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let class_type = interner.object(vec![
PropertyInfo::method(interner.intern_string("staticMethod"), static_method),
PropertyInfo::method(interner.intern_string("instanceMethod"), instance_method),
]);
assert!(class_type != TypeId::ERROR);
}
#[test]
fn test_this_type_with_getter_setter() {
let interner = TypeInterner::new();
let this_type = interner.intern(TypeData::ThisType);
let getter = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: this_type,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let obj = interner.object(vec![PropertyInfo {
name: interner.intern_string("current"),
type_id: getter,
write_type: this_type,
optional: false,
readonly: false,
is_method: false,
visibility: Visibility::Public,
parent_id: None,
}]);
assert!(obj != TypeId::ERROR);
}
#[test]
fn test_this_type_with_rest_params() {
let interner = TypeInterner::new();
let this_type = interner.intern(TypeData::ThisType);
let rest_method = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("args")),
type_id: interner.array(TypeId::ANY),
optional: false,
rest: true,
}],
this_type: None,
return_type: this_type,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(rest_method != TypeId::ERROR);
}
#[test]
fn test_this_type_comparison() {
let interner = TypeInterner::new();
let this1 = interner.intern(TypeData::ThisType);
let this2 = interner.intern(TypeData::ThisType);
assert_eq!(this1, this2);
}
#[test]
fn test_this_type_with_method_overload() {
let interner = TypeInterner::new();
let this_type = interner.intern(TypeData::ThisType);
let overload1 = 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: this_type,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let overload2 = 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: this_type,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let overloaded = interner.intersection(vec![overload1, overload2]);
assert!(overloaded != TypeId::ERROR);
}
#[test]
fn test_this_type_event_emitter_pattern() {
let interner = TypeInterner::new();
let this_type = interner.intern(TypeData::ThisType);
let on_method = interner.function(FunctionShape {
type_params: vec![],
params: vec![
ParamInfo {
name: Some(interner.intern_string("event")),
type_id: TypeId::STRING,
optional: false,
rest: false,
},
ParamInfo {
name: Some(interner.intern_string("handler")),
type_id: TypeId::FUNCTION,
optional: false,
rest: false,
},
],
this_type: None,
return_type: this_type,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let off_method = interner.function(FunctionShape {
type_params: vec![],
params: vec![
ParamInfo {
name: Some(interner.intern_string("event")),
type_id: TypeId::STRING,
optional: false,
rest: false,
},
ParamInfo {
name: Some(interner.intern_string("handler")),
type_id: TypeId::FUNCTION,
optional: false,
rest: false,
},
],
this_type: None,
return_type: this_type,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let emit_method = interner.function(FunctionShape {
type_params: vec![],
params: vec![
ParamInfo {
name: Some(interner.intern_string("event")),
type_id: TypeId::STRING,
optional: false,
rest: false,
},
ParamInfo {
name: Some(interner.intern_string("args")),
type_id: interner.array(TypeId::ANY),
optional: false,
rest: true,
},
],
this_type: None,
return_type: this_type,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let emitter = interner.object(vec![
PropertyInfo::method(interner.intern_string("on"), on_method),
PropertyInfo::method(interner.intern_string("off"), off_method),
PropertyInfo::method(interner.intern_string("emit"), emit_method),
]);
assert!(emitter != TypeId::ERROR);
}
#[test]
fn test_this_type_query_builder() {
let interner = TypeInterner::new();
let this_type = interner.intern(TypeData::ThisType);
let result_array = interner.array(interner.lazy(DefId(100)));
let promise_results = interner.application(interner.lazy(DefId(101)), vec![result_array]);
let where_method = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("condition")),
type_id: TypeId::STRING,
optional: false,
rest: false,
}],
this_type: None,
return_type: this_type,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let order_by_method = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("field")),
type_id: TypeId::STRING,
optional: false,
rest: false,
}],
this_type: None,
return_type: this_type,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let limit_method = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("n")),
type_id: TypeId::NUMBER,
optional: false,
rest: false,
}],
this_type: None,
return_type: this_type,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let execute_method = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: promise_results,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let query_builder = interner.object(vec![
PropertyInfo::method(interner.intern_string("where"), where_method),
PropertyInfo::method(interner.intern_string("orderBy"), order_by_method),
PropertyInfo::method(interner.intern_string("limit"), limit_method),
PropertyInfo::method(interner.intern_string("execute"), execute_method),
]);
assert!(query_builder != TypeId::ERROR);
}
#[test]
fn test_readonly_property_basic() {
let interner = TypeInterner::new();
let readonly_obj = interner.object(vec![PropertyInfo::readonly(
interner.intern_string("x"),
TypeId::STRING,
)]);
assert!(readonly_obj != TypeId::ERROR);
}
#[test]
fn test_readonly_vs_mutable_property() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let readonly_obj = interner.object(vec![PropertyInfo::readonly(
interner.intern_string("x"),
TypeId::STRING,
)]);
let mutable_obj = interner.object(vec![PropertyInfo::new(
interner.intern_string("x"),
TypeId::STRING,
)]);
assert!(checker.is_subtype_of(mutable_obj, readonly_obj));
assert!(checker.is_subtype_of(readonly_obj, mutable_obj));
}
#[test]
fn test_readonly_array_basic() {
let interner = TypeInterner::new();
let readonly_array = interner.readonly_array(TypeId::STRING);
assert!(readonly_array != TypeId::ERROR);
}
#[test]
fn test_readonly_array_vs_mutable() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let readonly_array = interner.readonly_array(TypeId::STRING);
let mutable_array = interner.array(TypeId::STRING);
assert!(checker.is_subtype_of(mutable_array, readonly_array));
assert!(!checker.is_subtype_of(readonly_array, mutable_array));
}
#[test]
fn test_readonly_tuple_basic() {
let interner = TypeInterner::new();
let readonly_tuple = interner.readonly_tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
},
]);
assert!(readonly_tuple != TypeId::ERROR);
}
#[test]
fn test_readonly_tuple_vs_mutable() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let readonly_tuple = interner.readonly_tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
},
]);
let mutable_tuple = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
},
]);
assert!(checker.is_subtype_of(mutable_tuple, readonly_tuple));
assert!(!checker.is_subtype_of(readonly_tuple, mutable_tuple));
}
#[test]
fn test_readonly_multiple_properties() {
let interner = TypeInterner::new();
let obj = interner.object(vec![
PropertyInfo::readonly(interner.intern_string("a"), TypeId::STRING),
PropertyInfo::readonly(interner.intern_string("b"), TypeId::NUMBER),
]);
assert!(obj != TypeId::ERROR);
}
#[test]
fn test_readonly_mixed_with_mutable() {
let interner = TypeInterner::new();
let mixed = interner.object(vec![
PropertyInfo::readonly(interner.intern_string("a"), TypeId::STRING),
PropertyInfo::new(interner.intern_string("b"), TypeId::NUMBER),
]);
assert!(mixed != TypeId::ERROR);
}
#[test]
fn test_readonly_index_signature() {
let interner = TypeInterner::new();
let readonly_index = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: TypeId::NUMBER,
readonly: true,
}),
number_index: None,
});
assert!(readonly_index != TypeId::ERROR);
}
#[test]
fn test_readonly_index_vs_mutable() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let readonly_index = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: TypeId::NUMBER,
readonly: true,
}),
number_index: None,
});
let mutable_index = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: TypeId::NUMBER,
readonly: false,
}),
number_index: None,
});
assert!(checker.is_subtype_of(mutable_index, readonly_index));
assert!(!checker.is_subtype_of(readonly_index, mutable_index));
}
#[test]
fn test_readonly_optional_property() {
let interner = TypeInterner::new();
let readonly_optional = interner.object(vec![PropertyInfo {
name: interner.intern_string("x"),
type_id: TypeId::STRING,
write_type: TypeId::STRING,
optional: true,
readonly: true,
is_method: false,
visibility: Visibility::Public,
parent_id: None,
}]);
assert!(readonly_optional != TypeId::ERROR);
}
#[test]
fn test_readonly_nested_object() {
let interner = TypeInterner::new();
let inner = interner.object(vec![PropertyInfo::readonly(
interner.intern_string("inner"),
TypeId::STRING,
)]);
let outer = interner.object(vec![PropertyInfo::readonly(
interner.intern_string("data"),
inner,
)]);
assert!(outer != TypeId::ERROR);
}
#[test]
fn test_readonly_with_union_property() {
let interner = TypeInterner::new();
let union = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let obj = interner.object(vec![PropertyInfo::readonly(
interner.intern_string("x"),
union,
)]);
assert!(obj != TypeId::ERROR);
}
#[test]
fn test_readonly_with_array_property() {
let interner = TypeInterner::new();
let string_array = interner.array(TypeId::STRING);
let obj = interner.object(vec![PropertyInfo::readonly(
interner.intern_string("items"),
string_array,
)]);
assert!(obj != TypeId::ERROR);
}
#[test]
fn test_readonly_deep_with_array() {
let interner = TypeInterner::new();
let readonly_array = interner.readonly_array(TypeId::STRING);
let obj = interner.object(vec![PropertyInfo::readonly(
interner.intern_string("items"),
readonly_array,
)]);
assert!(obj != TypeId::ERROR);
}
#[test]
fn test_readonly_with_function_property() {
let interner = TypeInterner::new();
let callback = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let obj = interner.object(vec![PropertyInfo::readonly(
interner.intern_string("callback"),
callback,
)]);
assert!(obj != TypeId::ERROR);
}
#[test]
fn test_readonly_method_is_always_readonly() {
let interner = TypeInterner::new();
let method = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::STRING,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let obj = interner.object(vec![PropertyInfo {
name: interner.intern_string("getValue"),
type_id: method,
write_type: method,
optional: false,
readonly: false, is_method: true,
visibility: Visibility::Public,
parent_id: None,
}]);
assert!(obj != TypeId::ERROR);
}
#[test]
fn test_readonly_with_literal_type() {
let interner = TypeInterner::new();
let lit_active = interner.literal_string("active");
let lit_inactive = interner.literal_string("inactive");
let status_union = interner.union(vec![lit_active, lit_inactive]);
let obj = interner.object(vec![PropertyInfo::readonly(
interner.intern_string("status"),
status_union,
)]);
assert!(obj != TypeId::ERROR);
}
#[test]
fn test_readonly_with_number_index() {
let interner = TypeInterner::new();
let readonly_number_index = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
string_index: None,
number_index: Some(IndexSignature {
key_type: TypeId::NUMBER,
value_type: TypeId::STRING,
readonly: true,
}),
});
assert!(readonly_number_index != TypeId::ERROR);
}
#[test]
fn test_readonly_intersection() {
let interner = TypeInterner::new();
let obj_a = interner.object(vec![PropertyInfo::readonly(
interner.intern_string("a"),
TypeId::STRING,
)]);
let obj_b = interner.object(vec![PropertyInfo::readonly(
interner.intern_string("b"),
TypeId::NUMBER,
)]);
let intersection = interner.intersection(vec![obj_a, obj_b]);
assert!(intersection != TypeId::ERROR);
}
#[test]
fn test_readonly_in_generic_context() {
let interner = TypeInterner::new();
let t_ref = interner.lazy(DefId(50));
let container = interner.object(vec![PropertyInfo::readonly(
interner.intern_string("value"),
t_ref,
)]);
assert!(container != TypeId::ERROR);
}
#[test]
fn test_readonly_preserves_subtype_covariance() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let lit_a = interner.literal_string("a");
let readonly_literal = interner.object(vec![PropertyInfo::readonly(
interner.intern_string("x"),
lit_a,
)]);
let readonly_string = interner.object(vec![PropertyInfo::readonly(
interner.intern_string("x"),
TypeId::STRING,
)]);
assert!(checker.is_subtype_of(readonly_literal, readonly_string));
}
#[test]
fn test_readonly_with_this_type() {
let interner = TypeInterner::new();
let this_type = interner.intern(TypeData::ThisType);
let obj = interner.object(vec![PropertyInfo::readonly(
interner.intern_string("self"),
this_type,
)]);
assert!(obj != TypeId::ERROR);
}
#[test]
fn test_readonly_with_tuple_property() {
let interner = TypeInterner::new();
let coords = interner.tuple(vec![
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
},
]);
let obj = interner.object(vec![PropertyInfo::readonly(
interner.intern_string("coords"),
coords,
)]);
assert!(obj != TypeId::ERROR);
}
#[test]
fn test_readonly_with_readonly_tuple_property() {
let interner = TypeInterner::new();
let readonly_coords = interner.readonly_tuple(vec![
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
},
]);
let obj = interner.object(vec![PropertyInfo::readonly(
interner.intern_string("coords"),
readonly_coords,
)]);
assert!(obj != TypeId::ERROR);
}
#[test]
fn test_readonly_mapped_type_pattern() {
let interner = TypeInterner::new();
let readonly_all = interner.object(vec![
PropertyInfo::readonly(interner.intern_string("a"), TypeId::STRING),
PropertyInfo::readonly(interner.intern_string("b"), TypeId::NUMBER),
]);
let mutable_all = interner.object(vec![
PropertyInfo::new(interner.intern_string("a"), TypeId::STRING),
PropertyInfo::new(interner.intern_string("b"), TypeId::NUMBER),
]);
let mut checker = SubtypeChecker::new(&interner);
assert!(checker.is_subtype_of(mutable_all, readonly_all));
}
#[test]
fn test_readonly_class_instance_properties() {
let interner = TypeInterner::new();
let instance = interner.object(vec![
PropertyInfo::readonly(interner.intern_string("id"), TypeId::STRING),
PropertyInfo::readonly(interner.intern_string("createdAt"), TypeId::NUMBER),
]);
assert!(instance != TypeId::ERROR);
}
#[test]
fn test_readonly_with_bigint() {
let interner = TypeInterner::new();
let obj = interner.object(vec![PropertyInfo::readonly(
interner.intern_string("value"),
TypeId::BIGINT,
)]);
assert!(obj != TypeId::ERROR);
}
#[test]
fn test_readonly_with_symbol() {
let interner = TypeInterner::new();
let obj = interner.object(vec![PropertyInfo::readonly(
interner.intern_string("sym"),
TypeId::SYMBOL,
)]);
assert!(obj != TypeId::ERROR);
}
#[test]
fn test_readonly_with_null_union() {
let interner = TypeInterner::new();
let nullable = interner.union(vec![TypeId::STRING, TypeId::NULL]);
let obj = interner.object(vec![PropertyInfo::readonly(
interner.intern_string("value"),
nullable,
)]);
assert!(obj != TypeId::ERROR);
}
#[test]
fn test_readonly_config_pattern() {
let interner = TypeInterner::new();
let config = interner.object(vec![
PropertyInfo::readonly(interner.intern_string("host"), TypeId::STRING),
PropertyInfo::readonly(interner.intern_string("port"), TypeId::NUMBER),
PropertyInfo::readonly(interner.intern_string("debug"), TypeId::BOOLEAN),
]);
assert!(config != TypeId::ERROR);
}
#[test]
fn test_overload_basic_two_signatures() {
let interner = TypeInterner::new();
let callable = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![
CallSignature {
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_method: false,
},
CallSignature {
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_method: false,
},
],
construct_signatures: vec![],
properties: vec![],
string_index: None,
number_index: None,
});
assert!(callable != TypeId::ERROR);
}
#[test]
fn test_overload_by_argument_count() {
let interner = TypeInterner::new();
let callable = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![
CallSignature {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_method: false,
},
CallSignature {
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::NUMBER,
type_predicate: None,
is_method: false,
},
CallSignature {
type_params: vec![],
params: vec![
ParamInfo {
name: Some(interner.intern_string("x")),
type_id: TypeId::NUMBER,
optional: false,
rest: false,
},
ParamInfo {
name: Some(interner.intern_string("y")),
type_id: TypeId::NUMBER,
optional: false,
rest: false,
},
],
this_type: None,
return_type: TypeId::NUMBER,
type_predicate: None,
is_method: false,
},
],
construct_signatures: vec![],
properties: vec![],
string_index: None,
number_index: None,
});
assert!(callable != TypeId::ERROR);
}
#[test]
fn test_overload_subtype_more_signatures_to_fewer() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let more_overloads = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![
CallSignature {
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_method: false,
},
CallSignature {
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_method: false,
},
],
construct_signatures: vec![],
properties: vec![],
string_index: None,
number_index: None,
});
let fewer_overloads = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![CallSignature {
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_method: false,
}],
construct_signatures: vec![],
properties: vec![],
string_index: None,
number_index: None,
});
assert!(checker.is_subtype_of(more_overloads, fewer_overloads));
}
#[test]
fn test_overload_subtype_fewer_not_subtype_of_more() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let more_overloads = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![
CallSignature {
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_method: false,
},
CallSignature {
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_method: false,
},
],
construct_signatures: vec![],
properties: vec![],
string_index: None,
number_index: None,
});
let fewer_overloads = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![CallSignature {
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_method: false,
}],
construct_signatures: vec![],
properties: vec![],
string_index: None,
number_index: None,
});
assert!(!checker.is_subtype_of(fewer_overloads, more_overloads));
}
#[test]
fn test_overload_generic_identity() {
let interner = TypeInterner::new();
let t_param = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: interner.intern_string("T"),
constraint: None,
default: None,
is_const: false,
}));
let callable = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![
CallSignature {
type_params: vec![TypeParamInfo {
name: interner.intern_string("T"),
constraint: None,
default: None,
is_const: false,
}],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: t_param,
optional: false,
rest: false,
}],
this_type: None,
return_type: t_param,
type_predicate: None,
is_method: false,
},
CallSignature {
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::STRING,
type_predicate: None,
is_method: false,
},
],
construct_signatures: vec![],
properties: vec![],
string_index: None,
number_index: None,
});
assert!(callable != TypeId::ERROR);
}
#[test]
fn test_overload_generic_with_constraint() {
let interner = TypeInterner::new();
let t_string = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: interner.intern_string("T"),
constraint: Some(TypeId::STRING),
default: None,
is_const: false,
}));
let t_number = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: interner.intern_string("T"),
constraint: Some(TypeId::NUMBER),
default: None,
is_const: false,
}));
let callable = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![
CallSignature {
type_params: vec![TypeParamInfo {
name: interner.intern_string("T"),
constraint: Some(TypeId::STRING),
default: None,
is_const: false,
}],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: t_string,
optional: false,
rest: false,
}],
this_type: None,
return_type: t_string,
type_predicate: None,
is_method: false,
},
CallSignature {
type_params: vec![TypeParamInfo {
name: interner.intern_string("T"),
constraint: Some(TypeId::NUMBER),
default: None,
is_const: false,
}],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: t_number,
optional: false,
rest: false,
}],
this_type: None,
return_type: t_number,
type_predicate: None,
is_method: false,
},
],
construct_signatures: vec![],
properties: vec![],
string_index: None,
number_index: None,
});
assert!(callable != TypeId::ERROR);
}
#[test]
fn test_overload_with_rest_parameter() {
let interner = TypeInterner::new();
let number_array = interner.array(TypeId::NUMBER);
let callable = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![
CallSignature {
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::NUMBER,
type_predicate: None,
is_method: false,
},
CallSignature {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("args")),
type_id: number_array,
optional: false,
rest: true,
}],
this_type: None,
return_type: TypeId::NUMBER,
type_predicate: None,
is_method: false,
},
],
construct_signatures: vec![],
properties: vec![],
string_index: None,
number_index: None,
});
assert!(callable != TypeId::ERROR);
}
#[test]
fn test_overload_with_optional_parameters() {
let interner = TypeInterner::new();
let callable = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![
CallSignature {
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::STRING,
type_predicate: None,
is_method: false,
},
CallSignature {
type_params: vec![],
params: vec![
ParamInfo {
name: Some(interner.intern_string("x")),
type_id: TypeId::STRING,
optional: false,
rest: false,
},
ParamInfo {
name: Some(interner.intern_string("y")),
type_id: TypeId::NUMBER,
optional: true,
rest: false,
},
],
this_type: None,
return_type: TypeId::STRING,
type_predicate: None,
is_method: false,
},
],
construct_signatures: vec![],
properties: vec![],
string_index: None,
number_index: None,
});
assert!(callable != TypeId::ERROR);
}
#[test]
fn test_overload_mixed_call_and_construct() {
let interner = TypeInterner::new();
let callable = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![CallSignature {
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::STRING,
type_predicate: None,
is_method: false,
}],
construct_signatures: vec![CallSignature {
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::OBJECT,
type_predicate: None,
is_method: false,
}],
properties: vec![],
string_index: None,
number_index: None,
});
assert!(callable != TypeId::ERROR);
}
#[test]
fn test_overload_return_type_union() {
let interner = TypeInterner::new();
let lit_a = interner.literal_string("a");
let lit_b = interner.literal_string("b");
let num_or_string = interner.union(vec![TypeId::NUMBER, TypeId::STRING]);
let callable = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![
CallSignature {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: lit_a,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::NUMBER,
type_predicate: None,
is_method: false,
},
CallSignature {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: lit_b,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::STRING,
type_predicate: None,
is_method: false,
},
CallSignature {
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: num_or_string,
type_predicate: None,
is_method: false,
},
],
construct_signatures: vec![],
properties: vec![],
string_index: None,
number_index: None,
});
assert!(callable != TypeId::ERROR);
}
#[test]
fn test_overload_subtype_signature_order_matters() {
let interner = TypeInterner::new();
let lit_a = interner.literal_string("a");
let specific_first = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![
CallSignature {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: lit_a,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::NUMBER,
type_predicate: None,
is_method: false,
},
CallSignature {
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::STRING,
type_predicate: None,
is_method: false,
},
],
construct_signatures: vec![],
properties: vec![],
string_index: None,
number_index: None,
});
let general_first = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![
CallSignature {
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::STRING,
type_predicate: None,
is_method: false,
},
CallSignature {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: lit_a,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::NUMBER,
type_predicate: None,
is_method: false,
},
],
construct_signatures: vec![],
properties: vec![],
string_index: None,
number_index: None,
});
assert!(specific_first != general_first);
}
#[test]
fn test_overload_generic_multiple_type_params() {
let interner = TypeInterner::new();
let t_param = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: interner.intern_string("T"),
constraint: None,
default: None,
is_const: false,
}));
let u_param = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: interner.intern_string("U"),
constraint: None,
default: None,
is_const: false,
}));
let tuple_t_u = interner.tuple(vec![
TupleElement {
type_id: t_param,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: u_param,
name: None,
optional: false,
rest: false,
},
]);
let callable = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![
CallSignature {
type_params: vec![
TypeParamInfo {
name: interner.intern_string("T"),
constraint: None,
default: None,
is_const: false,
},
TypeParamInfo {
name: interner.intern_string("U"),
constraint: None,
default: None,
is_const: false,
},
],
params: vec![
ParamInfo {
name: Some(interner.intern_string("x")),
type_id: t_param,
optional: false,
rest: false,
},
ParamInfo {
name: Some(interner.intern_string("y")),
type_id: u_param,
optional: false,
rest: false,
},
],
this_type: None,
return_type: tuple_t_u,
type_predicate: None,
is_method: false,
},
CallSignature {
type_params: vec![TypeParamInfo {
name: interner.intern_string("T"),
constraint: None,
default: None,
is_const: false,
}],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: t_param,
optional: false,
rest: false,
}],
this_type: None,
return_type: t_param,
type_predicate: None,
is_method: false,
},
],
construct_signatures: vec![],
properties: vec![],
string_index: None,
number_index: None,
});
assert!(callable != TypeId::ERROR);
}
#[test]
fn test_overload_reflexivity() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let callable = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![
CallSignature {
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_method: false,
},
CallSignature {
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_method: false,
},
],
construct_signatures: vec![],
properties: vec![],
string_index: None,
number_index: None,
});
assert!(checker.is_subtype_of(callable, callable));
}
#[test]
fn test_overload_covariant_return_types() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let lit_hello = interner.literal_string("hello");
let specific_return = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![CallSignature {
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: lit_hello,
type_predicate: None,
is_method: false,
}],
construct_signatures: vec![],
properties: vec![],
string_index: None,
number_index: None,
});
let general_return = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![CallSignature {
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_method: false,
}],
construct_signatures: vec![],
properties: vec![],
string_index: None,
number_index: None,
});
assert!(checker.is_subtype_of(specific_return, general_return));
assert!(!checker.is_subtype_of(general_return, specific_return));
}
#[test]
fn test_overload_contravariant_parameters() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let lit_hello = interner.literal_string("hello");
let general_param = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![CallSignature {
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::VOID,
type_predicate: None,
is_method: false,
}],
construct_signatures: vec![],
properties: vec![],
string_index: None,
number_index: None,
});
let specific_param = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![CallSignature {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: lit_hello,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_method: false,
}],
construct_signatures: vec![],
properties: vec![],
string_index: None,
number_index: None,
});
assert!(checker.is_subtype_of(general_param, specific_param));
assert!(!checker.is_subtype_of(specific_param, general_param));
}
#[test]
fn test_overload_construct_signature_subtyping() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let obj_with_x = interner.object(vec![PropertyInfo::new(
interner.intern_string("x"),
TypeId::NUMBER,
)]);
let obj_with_xy = interner.object(vec![
PropertyInfo::new(interner.intern_string("x"), TypeId::NUMBER),
PropertyInfo::new(interner.intern_string("y"), TypeId::NUMBER),
]);
let specific_constructor = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![],
construct_signatures: vec![CallSignature {
type_params: vec![],
params: vec![],
this_type: None,
return_type: obj_with_xy,
type_predicate: None,
is_method: false,
}],
properties: vec![],
string_index: None,
number_index: None,
});
let general_constructor = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![],
construct_signatures: vec![CallSignature {
type_params: vec![],
params: vec![],
this_type: None,
return_type: obj_with_x,
type_predicate: None,
is_method: false,
}],
properties: vec![],
string_index: None,
number_index: None,
});
assert!(checker.is_subtype_of(specific_constructor, general_constructor));
}
#[test]
fn test_overload_with_this_type() {
let interner = TypeInterner::new();
let window_type = interner.object(vec![PropertyInfo::new(
interner.intern_string("location"),
TypeId::STRING,
)]);
let document_type = interner.object(vec![PropertyInfo::new(
interner.intern_string("body"),
TypeId::OBJECT,
)]);
let callable = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![
CallSignature {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: TypeId::STRING,
optional: false,
rest: false,
}],
this_type: Some(window_type),
return_type: TypeId::VOID,
type_predicate: None,
is_method: false,
},
CallSignature {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: TypeId::NUMBER,
optional: false,
rest: false,
}],
this_type: Some(document_type),
return_type: TypeId::VOID,
type_predicate: None,
is_method: false,
},
],
construct_signatures: vec![],
properties: vec![],
string_index: None,
number_index: None,
});
assert!(callable != TypeId::ERROR);
}
#[test]
fn test_overload_empty_callable() {
let interner = TypeInterner::new();
let empty_callable = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![],
construct_signatures: vec![],
properties: vec![],
..Default::default()
});
assert!(empty_callable != TypeId::ERROR);
}
#[test]
fn test_overload_with_properties() {
let interner = TypeInterner::new();
let callable = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![CallSignature {
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_method: false,
}],
construct_signatures: vec![],
properties: vec![
PropertyInfo::new(interner.intern_string("name"), TypeId::STRING),
PropertyInfo::new(interner.intern_string("version"), TypeId::NUMBER),
],
string_index: None,
number_index: None,
});
assert!(callable != TypeId::ERROR);
}
#[test]
fn test_overload_generic_default_type() {
let interner = TypeInterner::new();
let t_param = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: interner.intern_string("T"),
constraint: None,
default: Some(TypeId::STRING),
is_const: false,
}));
let callable = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![CallSignature {
type_params: vec![TypeParamInfo {
name: interner.intern_string("T"),
constraint: None,
default: Some(TypeId::STRING),
is_const: false,
}],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: t_param,
optional: false,
rest: false,
}],
this_type: None,
return_type: t_param,
type_predicate: None,
is_method: false,
}],
construct_signatures: vec![],
properties: vec![],
string_index: None,
number_index: None,
});
assert!(callable != TypeId::ERROR);
}
#[test]
fn test_overload_array_methods_pattern() {
let interner = TypeInterner::new();
let t_param = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: interner.intern_string("T"),
constraint: None,
default: None,
is_const: false,
}));
let u_param = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: interner.intern_string("U"),
constraint: None,
default: None,
is_const: false,
}));
let map_callback = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: t_param,
optional: false,
rest: false,
}],
this_type: None,
return_type: u_param,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let filter_callback = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: t_param,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::BOOLEAN,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let reduce_callback = interner.function(FunctionShape {
type_params: vec![],
params: vec![
ParamInfo {
name: Some(interner.intern_string("acc")),
type_id: u_param,
optional: false,
rest: false,
},
ParamInfo {
name: Some(interner.intern_string("x")),
type_id: t_param,
optional: false,
rest: false,
},
],
this_type: None,
return_type: u_param,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let u_array = interner.array(u_param);
let t_array = interner.array(t_param);
let map_method = interner.function(FunctionShape {
type_params: vec![TypeParamInfo {
name: interner.intern_string("U"),
constraint: None,
default: None,
is_const: false,
}],
params: vec![ParamInfo {
name: Some(interner.intern_string("fn")),
type_id: map_callback,
optional: false,
rest: false,
}],
this_type: None,
return_type: u_array,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let filter_method = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("fn")),
type_id: filter_callback,
optional: false,
rest: false,
}],
this_type: None,
return_type: t_array,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let reduce_method = interner.function(FunctionShape {
type_params: vec![TypeParamInfo {
name: interner.intern_string("U"),
constraint: None,
default: None,
is_const: false,
}],
params: vec![
ParamInfo {
name: Some(interner.intern_string("fn")),
type_id: reduce_callback,
optional: false,
rest: false,
},
ParamInfo {
name: Some(interner.intern_string("init")),
type_id: u_param,
optional: false,
rest: false,
},
],
this_type: None,
return_type: u_param,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let array_like = interner.object(vec![
PropertyInfo::method(interner.intern_string("map"), map_method),
PropertyInfo::method(interner.intern_string("filter"), filter_method),
PropertyInfo::method(interner.intern_string("reduce"), reduce_method),
]);
assert!(array_like != TypeId::ERROR);
}
#[test]
fn test_overload_event_handler_pattern() {
let interner = TypeInterner::new();
let lit_click = interner.literal_string("click");
let lit_keydown = interner.literal_string("keydown");
let mouse_event = interner.object(vec![
PropertyInfo::readonly(interner.intern_string("type"), TypeId::STRING),
PropertyInfo::readonly(interner.intern_string("clientX"), TypeId::NUMBER),
PropertyInfo::readonly(interner.intern_string("clientY"), TypeId::NUMBER),
]);
let keyboard_event = interner.object(vec![
PropertyInfo::readonly(interner.intern_string("type"), TypeId::STRING),
PropertyInfo::readonly(interner.intern_string("key"), TypeId::STRING),
PropertyInfo::readonly(interner.intern_string("code"), TypeId::STRING),
]);
let base_event = interner.object(vec![PropertyInfo::readonly(
interner.intern_string("type"),
TypeId::STRING,
)]);
let mouse_listener = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("e")),
type_id: mouse_event,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let keyboard_listener = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("e")),
type_id: keyboard_event,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let base_listener = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("e")),
type_id: base_event,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let add_event_listener = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![
CallSignature {
type_params: vec![],
params: vec![
ParamInfo {
name: Some(interner.intern_string("type")),
type_id: lit_click,
optional: false,
rest: false,
},
ParamInfo {
name: Some(interner.intern_string("listener")),
type_id: mouse_listener,
optional: false,
rest: false,
},
],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_method: false,
},
CallSignature {
type_params: vec![],
params: vec![
ParamInfo {
name: Some(interner.intern_string("type")),
type_id: lit_keydown,
optional: false,
rest: false,
},
ParamInfo {
name: Some(interner.intern_string("listener")),
type_id: keyboard_listener,
optional: false,
rest: false,
},
],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_method: false,
},
CallSignature {
type_params: vec![],
params: vec![
ParamInfo {
name: Some(interner.intern_string("type")),
type_id: TypeId::STRING,
optional: false,
rest: false,
},
ParamInfo {
name: Some(interner.intern_string("listener")),
type_id: base_listener,
optional: false,
rest: false,
},
],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_method: false,
},
],
construct_signatures: vec![],
properties: vec![],
string_index: None,
number_index: None,
});
let event_target = interner.object(vec![PropertyInfo::method(
interner.intern_string("addEventListener"),
add_event_listener,
)]);
assert!(event_target != TypeId::ERROR);
}
#[test]
fn test_overload_promise_then_pattern() {
let interner = TypeInterner::new();
let t_param = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: interner.intern_string("T"),
constraint: None,
default: None,
is_const: false,
}));
let u_param = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: interner.intern_string("U"),
constraint: None,
default: None,
is_const: false,
}));
let v_param = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: interner.intern_string("V"),
constraint: None,
default: None,
is_const: false,
}));
let on_fulfilled_sync = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("value")),
type_id: t_param,
optional: false,
rest: false,
}],
this_type: None,
return_type: u_param,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let on_rejected = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("reason")),
type_id: TypeId::ANY,
optional: false,
rest: false,
}],
this_type: None,
return_type: v_param,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let u_or_v = interner.union(vec![u_param, v_param]);
let then_method = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![
CallSignature {
type_params: vec![TypeParamInfo {
name: interner.intern_string("U"),
constraint: None,
default: None,
is_const: false,
}],
params: vec![ParamInfo {
name: Some(interner.intern_string("onFulfilled")),
type_id: on_fulfilled_sync,
optional: false,
rest: false,
}],
this_type: None,
return_type: u_param,
type_predicate: None,
is_method: false,
},
CallSignature {
type_params: vec![
TypeParamInfo {
name: interner.intern_string("U"),
constraint: None,
default: None,
is_const: false,
},
TypeParamInfo {
name: interner.intern_string("V"),
constraint: None,
default: None,
is_const: false,
},
],
params: vec![
ParamInfo {
name: Some(interner.intern_string("onFulfilled")),
type_id: on_fulfilled_sync,
optional: false,
rest: false,
},
ParamInfo {
name: Some(interner.intern_string("onRejected")),
type_id: on_rejected,
optional: false,
rest: false,
},
],
this_type: None,
return_type: u_or_v,
type_predicate: None,
is_method: false,
},
],
construct_signatures: vec![],
properties: vec![],
string_index: None,
number_index: None,
});
assert!(then_method != TypeId::ERROR);
}
#[test]
fn test_overload_constructor_overloads() {
let interner = TypeInterner::new();
let date_instance = interner.object(vec![
PropertyInfo {
name: interner.intern_string("getTime"),
type_id: interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::NUMBER,
type_predicate: None,
is_constructor: false,
is_method: false,
}),
write_type: TypeId::NEVER,
optional: false,
readonly: true,
is_method: true,
visibility: Visibility::Public,
parent_id: None,
},
PropertyInfo {
name: interner.intern_string("toISOString"),
type_id: interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::STRING,
type_predicate: None,
is_constructor: false,
is_method: false,
}),
write_type: TypeId::NEVER,
optional: false,
readonly: true,
is_method: true,
visibility: Visibility::Public,
parent_id: None,
},
]);
let date_constructor = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![],
construct_signatures: vec![
CallSignature {
type_params: vec![],
params: vec![],
this_type: None,
return_type: date_instance,
type_predicate: None,
is_method: false,
},
CallSignature {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("value")),
type_id: TypeId::NUMBER,
optional: false,
rest: false,
}],
this_type: None,
return_type: date_instance,
type_predicate: None,
is_method: false,
},
CallSignature {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("value")),
type_id: TypeId::STRING,
optional: false,
rest: false,
}],
this_type: None,
return_type: date_instance,
type_predicate: None,
is_method: false,
},
CallSignature {
type_params: vec![],
params: vec![
ParamInfo {
name: Some(interner.intern_string("year")),
type_id: TypeId::NUMBER,
optional: false,
rest: false,
},
ParamInfo {
name: Some(interner.intern_string("month")),
type_id: TypeId::NUMBER,
optional: false,
rest: false,
},
ParamInfo {
name: Some(interner.intern_string("date")),
type_id: TypeId::NUMBER,
optional: true,
rest: false,
},
],
this_type: None,
return_type: date_instance,
type_predicate: None,
is_method: false,
},
],
properties: vec![],
string_index: None,
number_index: None,
});
assert!(date_constructor != TypeId::ERROR);
}
#[test]
fn test_explain_failure_intrinsic_mismatch() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let reason = checker.explain_failure(TypeId::STRING, TypeId::NUMBER);
assert!(reason.is_some());
match reason.unwrap() {
SubtypeFailureReason::IntrinsicTypeMismatch {
source_type,
target_type,
} => {
assert_eq!(source_type, TypeId::STRING);
assert_eq!(target_type, TypeId::NUMBER);
}
other => panic!("Expected IntrinsicTypeMismatch, got {other:?}"),
}
}
#[test]
fn test_explain_failure_literal_mismatch() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let hello = interner.literal_string("hello");
let world = interner.literal_string("world");
let reason = checker.explain_failure(hello, world);
assert!(reason.is_some());
match reason.unwrap() {
SubtypeFailureReason::LiteralTypeMismatch {
source_type,
target_type,
} => {
assert_eq!(source_type, hello);
assert_eq!(target_type, world);
}
other => panic!("Expected LiteralTypeMismatch, got {other:?}"),
}
}
#[test]
fn test_explain_failure_literal_to_incompatible_intrinsic() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let hello = interner.literal_string("hello");
let reason = checker.explain_failure(hello, TypeId::NUMBER);
assert!(reason.is_some());
match reason.unwrap() {
SubtypeFailureReason::LiteralTypeMismatch {
source_type,
target_type,
} => {
assert_eq!(source_type, hello);
assert_eq!(target_type, TypeId::NUMBER);
}
other => panic!("Expected LiteralTypeMismatch, got {other:?}"),
}
}
#[test]
fn test_explain_failure_error_type() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let reason = checker.explain_failure(TypeId::ERROR, TypeId::NUMBER);
assert!(
reason.is_some(),
"ERROR type should produce a failure reason"
);
match reason.unwrap() {
SubtypeFailureReason::ErrorType {
source_type,
target_type,
} => {
assert_eq!(source_type, TypeId::ERROR);
assert_eq!(target_type, TypeId::NUMBER);
}
other => panic!("Expected ErrorType, got {other:?}"),
}
}
#[test]
fn test_literal_number_to_string_fails() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let forty_two = interner.literal_number(42.0);
assert!(!checker.is_subtype_of(forty_two, TypeId::STRING));
let reason = checker.explain_failure(forty_two, TypeId::STRING);
assert!(reason.is_some());
match reason.unwrap() {
SubtypeFailureReason::LiteralTypeMismatch { .. } => {}
other => panic!("Expected LiteralTypeMismatch, got {other:?}"),
}
}
#[test]
fn test_intrinsic_to_literal_fails() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let hello = interner.literal_string("hello");
assert!(!checker.is_subtype_of(TypeId::STRING, hello));
let reason = checker.explain_failure(TypeId::STRING, hello);
assert!(reason.is_some());
match reason.unwrap() {
SubtypeFailureReason::TypeMismatch { .. } => {}
other => panic!("Expected TypeMismatch, got {other:?}"),
}
}
#[test]
fn test_tuple_to_array_homogeneous_two_strings() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let string_array = interner.array(TypeId::STRING);
let source = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
]);
assert!(
checker.is_subtype_of(source, string_array),
"[string, string] should be assignable to string[]"
);
}
#[test]
fn test_tuple_to_array_homogeneous_three_numbers() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let number_array = interner.array(TypeId::NUMBER);
let source = interner.tuple(vec![
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
},
]);
assert!(
checker.is_subtype_of(source, number_array),
"[number, number, number] should be assignable to number[]"
);
}
#[test]
fn test_tuple_to_array_homogeneous_booleans() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let boolean_array = interner.array(TypeId::BOOLEAN);
let source = interner.tuple(vec![
TupleElement {
type_id: TypeId::BOOLEAN,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::BOOLEAN,
name: None,
optional: false,
rest: false,
},
]);
assert!(
checker.is_subtype_of(source, boolean_array),
"[boolean, boolean] should be assignable to boolean[]"
);
}
#[test]
fn test_tuple_to_array_homogeneous_literal_to_base() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let hello = interner.literal_string("hello");
let world = interner.literal_string("world");
let string_array = interner.array(TypeId::STRING);
let source = interner.tuple(vec![
TupleElement {
type_id: hello,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: world,
name: None,
optional: false,
rest: false,
},
]);
assert!(
checker.is_subtype_of(source, string_array),
"[\"hello\", \"world\"] should be assignable to string[]"
);
}
#[test]
fn test_tuple_to_array_homogeneous_number_literals() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let one = interner.literal_number(1.0);
let two = interner.literal_number(2.0);
let three = interner.literal_number(3.0);
let number_array = interner.array(TypeId::NUMBER);
let source = interner.tuple(vec![
TupleElement {
type_id: one,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: two,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: three,
name: None,
optional: false,
rest: false,
},
]);
assert!(
checker.is_subtype_of(source, number_array),
"[1, 2, 3] should be assignable to number[]"
);
}
#[test]
fn test_tuple_to_union_array_string_number() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let union_elem = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let union_array = interner.array(union_elem);
let source = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
},
]);
assert!(
checker.is_subtype_of(source, union_array),
"[string, number] should be assignable to (string | number)[]"
);
}
#[test]
fn test_tuple_to_union_array_number_boolean() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let union_elem = interner.union(vec![TypeId::NUMBER, TypeId::BOOLEAN]);
let union_array = interner.array(union_elem);
let source = interner.tuple(vec![
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::BOOLEAN,
name: None,
optional: false,
rest: false,
},
]);
assert!(
checker.is_subtype_of(source, union_array),
"[number, boolean] should be assignable to (number | boolean)[]"
);
}
#[test]
fn test_tuple_to_union_array_three_types() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let union_elem = interner.union(vec![TypeId::STRING, TypeId::NUMBER, TypeId::BOOLEAN]);
let union_array = interner.array(union_elem);
let source = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::BOOLEAN,
name: None,
optional: false,
rest: false,
},
]);
assert!(
checker.is_subtype_of(source, union_array),
"[string, number, boolean] should be assignable to (string | number | boolean)[]"
);
}
#[test]
fn test_tuple_to_union_array_literals_to_base() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a_literal = interner.literal_string("a");
let one_literal = interner.literal_number(1.0);
let union_elem = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let union_array = interner.array(union_elem);
let source = interner.tuple(vec![
TupleElement {
type_id: a_literal,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: one_literal,
name: None,
optional: false,
rest: false,
},
]);
assert!(
checker.is_subtype_of(source, union_array),
"[\"a\", 1] should be assignable to (string | number)[]"
);
}
#[test]
fn test_tuple_to_union_array_subset_elements() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let union_elem = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let union_array = interner.array(union_elem);
let source = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
]);
assert!(
checker.is_subtype_of(source, union_array),
"[string, string] should be assignable to (string | number)[]"
);
}
#[test]
fn test_tuple_to_union_array_fails_missing_element_type() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let union_elem = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let union_array = interner.array(union_elem);
let source = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::BOOLEAN,
name: None,
optional: false,
rest: false,
},
]);
assert!(
!checker.is_subtype_of(source, union_array),
"[string, boolean] should NOT be assignable to (string | number)[] - boolean is not in union"
);
}
#[test]
fn test_tuple_rest_to_array_matching() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let union_elem = interner.union(vec![TypeId::NUMBER, TypeId::STRING]);
let union_array = interner.array(union_elem);
let string_array = interner.array(TypeId::STRING);
let source = interner.tuple(vec![
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: string_array,
name: None,
optional: false,
rest: true,
},
]);
assert!(
checker.is_subtype_of(source, union_array),
"[number, ...string[]] should be assignable to (number | string)[]"
);
}
#[test]
fn test_tuple_rest_to_array_homogeneous() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let string_array = interner.array(TypeId::STRING);
let source = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: string_array,
name: None,
optional: false,
rest: true,
},
]);
assert!(
checker.is_subtype_of(source, string_array),
"[string, ...string[]] should be assignable to string[]"
);
}
#[test]
fn test_tuple_rest_to_array_prefix_not_matching() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let string_array = interner.array(TypeId::STRING);
let source = interner.tuple(vec![
TupleElement {
type_id: TypeId::BOOLEAN,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: string_array,
name: None,
optional: false,
rest: true,
},
]);
assert!(
!checker.is_subtype_of(source, string_array),
"[boolean, ...string[]] should NOT be assignable to string[]"
);
}
#[test]
fn test_tuple_rest_to_array_rest_not_matching() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let string_array = interner.array(TypeId::STRING);
let number_array = interner.array(TypeId::NUMBER);
let source = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: number_array,
name: None,
optional: false,
rest: true,
},
]);
assert!(
!checker.is_subtype_of(source, string_array),
"[string, ...number[]] should NOT be assignable to string[]"
);
}
#[test]
fn test_tuple_rest_multiple_prefix_to_union_array() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let union_elem = interner.union(vec![TypeId::STRING, TypeId::NUMBER, TypeId::BOOLEAN]);
let union_array = interner.array(union_elem);
let boolean_array = interner.array(TypeId::BOOLEAN);
let source = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: boolean_array,
name: None,
optional: false,
rest: true,
},
]);
assert!(
checker.is_subtype_of(source, union_array),
"[string, number, ...boolean[]] should be assignable to (string | number | boolean)[]"
);
}
#[test]
fn test_tuple_only_rest_to_array() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let number_array = interner.array(TypeId::NUMBER);
let source = interner.tuple(vec![TupleElement {
type_id: number_array,
name: None,
optional: false,
rest: true,
}]);
assert!(
checker.is_subtype_of(source, number_array),
"[...number[]] should be assignable to number[]"
);
}
#[test]
fn test_empty_tuple_to_string_array() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let string_array = interner.array(TypeId::STRING);
let empty_tuple = interner.tuple(Vec::new());
assert!(
checker.is_subtype_of(empty_tuple, string_array),
"[] should be assignable to string[]"
);
}
#[test]
fn test_empty_tuple_to_number_array() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let number_array = interner.array(TypeId::NUMBER);
let empty_tuple = interner.tuple(Vec::new());
assert!(
checker.is_subtype_of(empty_tuple, number_array),
"[] should be assignable to number[]"
);
}
#[test]
fn test_empty_tuple_to_union_array() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let union_elem = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let union_array = interner.array(union_elem);
let empty_tuple = interner.tuple(Vec::new());
assert!(
checker.is_subtype_of(empty_tuple, union_array),
"[] should be assignable to (string | number)[]"
);
}
#[test]
fn test_empty_tuple_to_any_array() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let any_array = interner.array(TypeId::ANY);
let empty_tuple = interner.tuple(Vec::new());
assert!(
checker.is_subtype_of(empty_tuple, any_array),
"[] should be assignable to any[]"
);
}
#[test]
fn test_empty_tuple_to_never_array() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let never_array = interner.array(TypeId::NEVER);
let empty_tuple = interner.tuple(Vec::new());
assert!(
checker.is_subtype_of(empty_tuple, never_array),
"[] should be assignable to never[]"
);
}
#[test]
fn test_single_element_tuple_to_array() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let string_array = interner.array(TypeId::STRING);
let source = interner.tuple(vec![TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
}]);
assert!(
checker.is_subtype_of(source, string_array),
"[string] should be assignable to string[]"
);
}
#[test]
fn test_single_element_tuple_type_mismatch() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let string_array = interner.array(TypeId::STRING);
let source = interner.tuple(vec![TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
}]);
assert!(
!checker.is_subtype_of(source, string_array),
"[number] should NOT be assignable to string[]"
);
}
#[test]
fn test_single_element_tuple_to_union_array() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let union_elem = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let union_array = interner.array(union_elem);
let source = interner.tuple(vec![TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
}]);
assert!(
checker.is_subtype_of(source, union_array),
"[string] should be assignable to (string | number)[]"
);
}
#[test]
fn test_tuple_optional_to_array() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let union_elem = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let union_array = interner.array(union_elem);
let source = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: true,
rest: false,
},
]);
assert!(
checker.is_subtype_of(source, union_array),
"[string, number?] should be assignable to (string | number)[]"
);
}
#[test]
fn test_tuple_all_optional_to_array() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let union_elem = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let union_array = interner.array(union_elem);
let source = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: true,
rest: false,
},
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: true,
rest: false,
},
]);
assert!(
checker.is_subtype_of(source, union_array),
"[string?, number?] should be assignable to (string | number)[]"
);
}
#[test]
fn test_tuple_optional_homogeneous_to_array() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let string_array = interner.array(TypeId::STRING);
let source = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: true,
rest: false,
},
]);
assert!(
checker.is_subtype_of(source, string_array),
"[string, string?] should be assignable to string[]"
);
}
#[test]
fn test_tuple_optional_element_type_mismatch() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let string_array = interner.array(TypeId::STRING);
let source = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::BOOLEAN,
name: None,
optional: true,
rest: false,
},
]);
assert!(
!checker.is_subtype_of(source, string_array),
"[string, boolean?] should NOT be assignable to string[] - boolean is not string"
);
}
#[test]
fn test_tuple_optional_with_rest_to_array() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let union_elem = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let union_array = interner.array(union_elem);
let number_array = interner.array(TypeId::NUMBER);
let source = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: true,
rest: false,
},
TupleElement {
type_id: number_array,
name: None,
optional: false,
rest: true,
},
]);
assert!(
checker.is_subtype_of(source, union_array),
"[string?, ...number[]] should be assignable to (string | number)[]"
);
}
#[test]
fn test_named_tuple_to_array() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let name_atom = interner.intern_string("name");
let age_atom = interner.intern_string("age");
let union_elem = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let union_array = interner.array(union_elem);
let source = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: Some(name_atom),
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::NUMBER,
name: Some(age_atom),
optional: false,
rest: false,
},
]);
assert!(
checker.is_subtype_of(source, union_array),
"[name: string, age: number] should be assignable to (string | number)[]"
);
}
#[test]
fn test_tuple_with_any_to_string_array() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let string_array = interner.array(TypeId::STRING);
let source = interner.tuple(vec![
TupleElement {
type_id: TypeId::ANY,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::ANY,
name: None,
optional: false,
rest: false,
},
]);
assert!(
checker.is_subtype_of(source, string_array),
"[any, any] should be assignable to string[]"
);
}
#[test]
fn test_tuple_to_any_array() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let any_array = interner.array(TypeId::ANY);
let source = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
},
]);
assert!(
checker.is_subtype_of(source, any_array),
"[string, number] should be assignable to any[]"
);
}
#[test]
fn test_tuple_with_never_to_string_array() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let string_array = interner.array(TypeId::STRING);
let source = interner.tuple(vec![
TupleElement {
type_id: TypeId::NEVER,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::NEVER,
name: None,
optional: false,
rest: false,
},
]);
assert!(
checker.is_subtype_of(source, string_array),
"[never, never] should be assignable to string[]"
);
}
#[test]
fn test_tuple_to_unknown_array() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let unknown_array = interner.array(TypeId::UNKNOWN);
let source = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
},
]);
assert!(
checker.is_subtype_of(source, unknown_array),
"[string, number] should be assignable to unknown[]"
);
}
#[test]
fn test_tuple_with_unknown_to_string_array() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let string_array = interner.array(TypeId::STRING);
let source = interner.tuple(vec![
TupleElement {
type_id: TypeId::UNKNOWN,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::UNKNOWN,
name: None,
optional: false,
rest: false,
},
]);
assert!(
!checker.is_subtype_of(source, string_array),
"[unknown, unknown] should NOT be assignable to string[]"
);
}
#[test]
fn test_tuple_to_readonly_array() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let readonly_string_array = interner.readonly_array(TypeId::STRING);
let source = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
]);
assert!(
checker.is_subtype_of(source, readonly_string_array),
"[string, string] should be assignable to readonly string[]"
);
}
#[test]
fn test_nested_tuple_to_array() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let inner_tuple = interner.tuple(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_array = interner.array(inner_tuple);
let source = interner.tuple(vec![
TupleElement {
type_id: inner_tuple,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: inner_tuple,
name: None,
optional: false,
rest: false,
},
]);
assert!(
checker.is_subtype_of(source, tuple_array),
"[[string, number], [string, number]] should be assignable to [string, number][]"
);
}
#[test]
fn test_array_to_tuple_fails_fixed() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let string_array = interner.array(TypeId::STRING);
let target = interner.tuple(vec![TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
}]);
assert!(
!checker.is_subtype_of(string_array, target),
"string[] should NOT be assignable to [string]"
);
}
#[test]
fn test_array_to_tuple_fails_multi_element() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let string_array = interner.array(TypeId::STRING);
let target = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
]);
assert!(
!checker.is_subtype_of(string_array, target),
"string[] should NOT be assignable to [string, string]"
);
}
#[test]
fn test_this_type_class_hierarchy_fluent_return() {
let interner = TypeInterner::new();
let this_type = interner.intern(TypeData::ThisType);
let base_method = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: this_type,
type_predicate: None,
is_constructor: false,
is_method: true,
});
let base_class = interner.object(vec![PropertyInfo::method(
interner.intern_string("method"),
base_method,
)]);
let extra_method = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::NUMBER,
type_predicate: None,
is_constructor: false,
is_method: true,
});
let derived_class = interner.object(vec![
PropertyInfo::method(interner.intern_string("method"), base_method),
PropertyInfo::method(interner.intern_string("extra"), extra_method),
]);
let mut checker = SubtypeChecker::new(&interner);
assert!(
checker.is_subtype_of(derived_class, base_class),
"Derived should be subtype of Base"
);
}
#[test]
fn test_this_type_in_method_parameter_covariant() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let this_type = interner.intern(TypeData::ThisType);
let box_compare = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("other")),
type_id: this_type,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: true,
});
let box_class = interner.object(vec![PropertyInfo::method(
interner.intern_string("compare"),
box_compare,
)]);
let stringbox_class = interner.object(vec![PropertyInfo::method(
interner.intern_string("compare"),
box_compare,
)]);
assert!(
checker.is_subtype_of(stringbox_class, box_class),
"StringBox should be subtype of Box (this type enables bivariance)"
);
}
#[test]
fn test_this_type_explicit_this_parameter_inheritance() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let base_class_ref = interner.lazy(DefId(100));
let base_method = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: Some(base_class_ref),
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: true,
});
let _base_class = interner.object(vec![PropertyInfo::method(
interner.intern_string("method"),
base_method,
)]);
let derived_class_ref = interner.lazy(DefId(101));
let derived_method = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: Some(derived_class_ref),
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: true,
});
let _derived_class = interner.object(vec![PropertyInfo::method(
interner.intern_string("method"),
derived_method,
)]);
assert!(
checker.is_subtype_of(derived_method, base_method),
"Derived method should be subtype of Base method (method bivariance)"
);
}
#[test]
fn test_this_type_return_covariant_in_hierarchy() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let this_type = interner.intern(TypeData::ThisType);
let base_fluent = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: this_type,
type_predicate: None,
is_constructor: false,
is_method: true,
});
let base_class = interner.object(vec![PropertyInfo::method(
interner.intern_string("fluent"),
base_fluent,
)]);
let derived_class = interner.object(vec![
PropertyInfo::method(interner.intern_string("fluent"), base_fluent),
PropertyInfo::new(interner.intern_string("extra"), TypeId::NUMBER),
]);
assert!(
checker.is_subtype_of(derived_class, base_class),
"Derived should be subtype of Base (same this-returning method)"
);
}
#[test]
fn test_this_type_polymorphic_method_chain() {
let interner = TypeInterner::new();
let this_type = interner.intern(TypeData::ThisType);
let result_type = interner.lazy(DefId(1));
let set_name = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("name")),
type_id: TypeId::STRING,
optional: false,
rest: false,
}],
this_type: None,
return_type: this_type,
type_predicate: None,
is_constructor: false,
is_method: true,
});
let set_value = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("value")),
type_id: TypeId::NUMBER,
optional: false,
rest: false,
}],
this_type: None,
return_type: this_type,
type_predicate: None,
is_constructor: false,
is_method: true,
});
let build = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: result_type,
type_predicate: None,
is_constructor: false,
is_method: true,
});
let builder = interner.object(vec![
PropertyInfo::method(interner.intern_string("setName"), set_name),
PropertyInfo::method(interner.intern_string("setValue"), set_value),
PropertyInfo::method(interner.intern_string("build"), build),
]);
assert_ne!(builder, TypeId::ERROR);
}
#[test]
fn test_this_type_with_generics_in_class() {
let interner = TypeInterner::new();
let this_type = interner.intern(TypeData::ThisType);
let _t_param = TypeParamInfo {
name: interner.intern_string("T"),
constraint: None,
default: None,
is_const: false,
};
let _u_param = TypeParamInfo {
name: interner.intern_string("U"),
constraint: None,
default: None,
is_const: false,
};
let filter_method = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("predicate")),
type_id: interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("value")),
type_id: TypeId::UNKNOWN,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::BOOLEAN,
type_predicate: None,
is_constructor: false,
is_method: false,
}),
optional: false,
rest: false,
}],
this_type: None,
return_type: this_type,
type_predicate: None,
is_constructor: false,
is_method: true,
});
let container = interner.object(vec![PropertyInfo::method(
interner.intern_string("filter"),
filter_method,
)]);
assert_ne!(container, TypeId::ERROR);
}
#[test]
fn test_this_type_class_hierarchy_multiple_methods() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let this_type = interner.intern(TypeData::ThisType);
let method1 = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: this_type,
type_predicate: None,
is_constructor: false,
is_method: true,
});
let method2 = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: this_type,
type_predicate: None,
is_constructor: false,
is_method: true,
});
let method3 = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::NUMBER,
type_predicate: None,
is_constructor: false,
is_method: true,
});
let base_class = interner.object(vec![
PropertyInfo::method(interner.intern_string("method1"), method1),
PropertyInfo::method(interner.intern_string("method2"), method2),
]);
let derived_class = interner.object(vec![
PropertyInfo::method(interner.intern_string("method1"), method1),
PropertyInfo::method(interner.intern_string("method2"), method2),
PropertyInfo::method(interner.intern_string("method3"), method3),
]);
assert!(
checker.is_subtype_of(derived_class, base_class),
"Derived should be subtype of Base (all this-returning methods compatible)"
);
}
#[test]
fn test_this_type_with_constrained_generic() {
let interner = TypeInterner::new();
let base_ref = interner.lazy(DefId(100));
let t_param = TypeParamInfo {
name: interner.intern_string("T"),
constraint: Some(base_ref),
default: None,
is_const: false,
};
let t_type_param = interner.intern(TypeData::TypeParameter(t_param.clone()));
let constrained_method = interner.function(FunctionShape {
type_params: vec![t_param],
params: vec![],
this_type: Some(t_type_param),
return_type: t_type_param,
type_predicate: None,
is_constructor: false,
is_method: true,
});
let base_class = interner.object(vec![PropertyInfo::method(
interner.intern_string("method"),
constrained_method,
)]);
assert_ne!(base_class, TypeId::ERROR);
}
#[test]
fn test_rest_param_flag_is_preserved() {
let interner = TypeInterner::new();
let any_array = interner.array(TypeId::ANY);
let target = interner.function(FunctionShape {
type_params: vec![],
params: vec![
ParamInfo {
name: Some(interner.intern_string("name")),
type_id: TypeId::STRING,
optional: false,
rest: false,
},
ParamInfo {
name: Some(interner.intern_string("mixed")),
type_id: TypeId::ANY,
optional: false,
rest: false,
},
ParamInfo {
name: Some(interner.intern_string("args")),
type_id: any_array,
optional: false,
rest: true,
},
],
this_type: None,
return_type: TypeId::ANY,
type_predicate: None,
is_constructor: false,
is_method: false,
});
if let Some(TypeData::Function(shape_id)) = interner.lookup(target) {
let shape = interner.function_shape(shape_id);
assert_eq!(shape.params.len(), 3, "Should have 3 params");
assert!(!shape.params[0].rest, "First param should not be rest");
assert!(!shape.params[1].rest, "Second param should not be rest");
assert!(shape.params[2].rest, "Third param SHOULD be rest");
} else {
panic!("Target is not a function type");
}
}
#[test]
fn test_rest_param_any_with_extra_fixed_params() {
let interner = TypeInterner::new();
let source = interner.function(FunctionShape {
params: vec![
ParamInfo::unnamed(TypeId::STRING),
ParamInfo::unnamed(TypeId::ANY),
ParamInfo::unnamed(TypeId::ANY),
],
this_type: None,
return_type: TypeId::ANY,
type_params: Vec::new(),
type_predicate: None,
is_constructor: false,
is_method: false,
});
let rest_any = interner.array(TypeId::ANY);
let target = interner.function(FunctionShape {
params: vec![
ParamInfo::unnamed(TypeId::STRING),
ParamInfo::unnamed(TypeId::ANY),
ParamInfo {
name: None,
type_id: rest_any,
optional: false,
rest: true,
},
],
this_type: None,
return_type: TypeId::ANY,
type_params: Vec::new(),
type_predicate: None,
is_constructor: false,
is_method: false,
});
let mut checker = SubtypeChecker::new(&interner);
assert!(checker.is_subtype_of(source, target));
checker.allow_bivariant_rest = true;
assert!(checker.is_subtype_of(source, target));
}