use super::*;
use crate::TypeInterner;
use crate::def::DefId;
use crate::{SubtypeChecker, TypeSubstitution, instantiate_type};
#[test]
fn test_conditional_true_branch() {
let interner = TypeInterner::new();
let cond = ConditionalType {
check_type: TypeId::STRING,
extends_type: TypeId::STRING,
true_type: TypeId::NUMBER,
false_type: TypeId::BOOLEAN,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, TypeId::NUMBER);
}
#[test]
fn test_conditional_false_branch() {
let interner = TypeInterner::new();
let cond = ConditionalType {
check_type: TypeId::NUMBER,
extends_type: TypeId::STRING,
true_type: TypeId::NUMBER,
false_type: TypeId::BOOLEAN,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, TypeId::BOOLEAN);
}
#[test]
fn test_conditional_literal_extends_base() {
let interner = TypeInterner::new();
let hello = interner.literal_string("hello");
let lit_true = interner.literal_boolean(true);
let lit_false = interner.literal_boolean(false);
let cond = ConditionalType {
check_type: hello,
extends_type: TypeId::STRING,
true_type: lit_true,
false_type: lit_false,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, lit_true);
}
#[test]
fn test_conditional_distributive() {
let interner = TypeInterner::new();
let string_or_number = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let lit_true = interner.literal_boolean(true);
let lit_false = interner.literal_boolean(false);
let cond = ConditionalType {
check_type: string_or_number,
extends_type: TypeId::STRING,
true_type: lit_true,
false_type: lit_false,
is_distributive: true,
};
let result = evaluate_conditional(&interner, &cond);
let expected = interner.union(vec![lit_true, lit_false]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_non_distributive_union() {
let interner = TypeInterner::new();
let string_or_number = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let lit_true = interner.literal_boolean(true);
let lit_false = interner.literal_boolean(false);
let cond = ConditionalType {
check_type: string_or_number,
extends_type: TypeId::STRING,
true_type: lit_true,
false_type: lit_false,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, lit_false);
}
#[test]
fn test_rest_unknown_bivariant_conditional_evaluate_strict() {
let interner = TypeInterner::new();
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,
});
let lit_true = interner.literal_boolean(true);
let lit_false = interner.literal_boolean(false);
let cond = ConditionalType {
check_type: source,
extends_type: target,
true_type: lit_true,
false_type: lit_false,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, lit_false);
}
#[test]
fn test_conditional_instantiated_param_distributes() {
let interner = TypeInterner::new();
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 string_or_number = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let lit_true = interner.literal_boolean(true);
let lit_false = interner.literal_boolean(false);
let cond = ConditionalType {
check_type: t_param,
extends_type: TypeId::STRING,
true_type: lit_true,
false_type: lit_false,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
subst.insert(t_name, string_or_number);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![lit_true, lit_false]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_instantiated_param_distributes_branch_substitution() {
let interner = TypeInterner::new();
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 cond = ConditionalType {
check_type: t_param,
extends_type: TypeId::STRING,
true_type: t_param,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
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, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::STRING);
}
#[test]
fn test_conditional_distributive_nested_extends() {
let interner = TypeInterner::new();
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 lit_a = interner.literal_string("a");
let lit_b = interner.literal_string("b");
let lit_one = interner.literal_number(1.0);
let lit_two = interner.literal_number(2.0);
let lit_three = interner.literal_number(3.0);
let inner_cond = interner.conditional(ConditionalType {
check_type: t_param,
extends_type: lit_a,
true_type: lit_one,
false_type: lit_two,
is_distributive: false,
});
let outer_cond = ConditionalType {
check_type: t_param,
extends_type: TypeId::STRING,
true_type: inner_cond,
false_type: lit_three,
is_distributive: true,
};
let cond_type = interner.conditional(outer_cond);
let mut subst = TypeSubstitution::new();
subst.insert(t_name, interner.union(vec![lit_a, lit_b]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![lit_one, lit_two]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_distributive_infer_extends_nested() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: Some(TypeId::STRING),
default: None,
is_const: false,
}));
let lit_a = interner.literal_string("a");
let lit_yes = interner.literal_string("yes");
let lit_no = interner.literal_string("no");
let lit_fallback = interner.literal_string("fallback");
let inner_cond = interner.conditional(ConditionalType {
check_type: infer_r,
extends_type: lit_a,
true_type: lit_yes,
false_type: lit_no,
is_distributive: false,
});
let outer_cond = ConditionalType {
check_type: t_param,
extends_type: infer_r,
true_type: inner_cond,
false_type: lit_fallback,
is_distributive: true,
};
let cond_type = interner.conditional(outer_cond);
let mut subst = TypeSubstitution::new();
subst.insert(
t_name,
interner.union(vec![lit_a, interner.literal_string("b"), TypeId::NUMBER]),
);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![lit_yes, lit_no, lit_fallback]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_true_branch_substitution() {
let interner = TypeInterner::new();
let infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: Some(TypeId::STRING),
default: None,
is_const: false,
}));
let lit_a = interner.literal_string("a");
let cond = ConditionalType {
check_type: lit_a,
extends_type: infer_r,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, lit_a);
}
#[test]
fn test_conditional_infer_false_branch_substitution() {
let interner = TypeInterner::new();
let infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: Some(TypeId::STRING),
default: None,
is_const: false,
}));
let cond = ConditionalType {
check_type: TypeId::NUMBER,
extends_type: infer_r,
true_type: TypeId::STRING,
false_type: infer_r,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, TypeId::NUMBER);
}
#[test]
fn test_conditional_infer_array_element_extraction() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_array = interner.array(infer_r);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_array,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
subst.insert(
t_name,
interner.union(vec![
interner.array(TypeId::STRING),
interner.array(TypeId::NUMBER),
]),
);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_array_element_non_array_union_branch() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_array = interner.array(infer_r);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_array,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
subst.insert(
t_name,
interner.union(vec![interner.array(TypeId::STRING), TypeId::NUMBER]),
);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::STRING);
}
#[test]
fn test_conditional_infer_array_element_non_distributive_union_input() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_array = interner.array(infer_r);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_array,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
subst.insert(
t_name,
interner.union(vec![
interner.array(TypeId::STRING),
interner.array(TypeId::NUMBER),
]),
);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_array_element_non_distributive_union_branch() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_array = interner.array(infer_r);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_array,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
subst.insert(
t_name,
interner.union(vec![interner.array(TypeId::STRING), TypeId::NUMBER]),
);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_conditional_infer_array_element_from_tuple_rest() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_array = interner.array(infer_r);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_array,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let number_array = interner.array(TypeId::NUMBER);
let tuple = 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 mut subst = TypeSubstitution::new();
subst.insert(t_name, tuple);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_array_element_from_tuple_rest_tuple() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_array = interner.array(infer_r);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_array,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let rest_tuple = 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,
},
]);
let tuple = 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,
},
]);
let mut subst = TypeSubstitution::new();
subst.insert(t_name, tuple);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER, TypeId::BOOLEAN]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_array_element_from_optional_tuple_element() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_array = interner.array(infer_r);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_array,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let optional_tuple = interner.tuple(vec![TupleElement {
type_id: TypeId::STRING,
name: None,
optional: true,
rest: false,
}]);
subst.insert(t_name, optional_tuple);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![TypeId::STRING, TypeId::UNDEFINED]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_array_element_with_constraint() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: Some(TypeId::STRING),
default: None,
is_const: false,
}));
let extends_array = interner.array(infer_r);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_array,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
subst.insert(
t_name,
interner.union(vec![
interner.array(TypeId::NUMBER),
interner.array(TypeId::STRING),
]),
);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::STRING);
}
#[test]
fn test_conditional_infer_array_element_non_distributive() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let check_array = interner.array(t_param);
let extends_array = interner.array(infer_r);
let cond = ConditionalType {
check_type: check_array,
extends_type: extends_array,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
subst.insert(t_name, interner.union(vec![TypeId::STRING, TypeId::NUMBER]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_array_element_non_distributive_tuple_wrapper() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let check_tuple = interner.tuple(vec![TupleElement {
type_id: t_param,
name: None,
optional: false,
rest: false,
}]);
let extends_tuple = interner.tuple(vec![TupleElement {
type_id: interner.array(infer_r),
name: None,
optional: false,
rest: false,
}]);
let cond = ConditionalType {
check_type: check_tuple,
extends_type: extends_tuple,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
subst.insert(
t_name,
interner.union(vec![
interner.array(TypeId::STRING),
interner.array(TypeId::NUMBER),
]),
);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_object_property_distributive() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_obj = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
infer_r,
)]);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_obj,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let obj_a = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
TypeId::STRING,
)]);
let obj_b = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
TypeId::NUMBER,
)]);
let obj_c = interner.object(vec![PropertyInfo::new(
interner.intern_string("b"),
TypeId::BOOLEAN,
)]);
subst.insert(t_name, interner.union(vec![obj_a, obj_b, obj_c]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_object_property_with_constraint() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: Some(TypeId::STRING),
default: None,
is_const: false,
}));
let extends_obj = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
infer_r,
)]);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_obj,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let obj_a = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
TypeId::STRING,
)]);
let obj_b = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
TypeId::NUMBER,
)]);
subst.insert(t_name, interner.union(vec![obj_a, obj_b]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![TypeId::STRING, TypeId::UNDEFINED]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_object_property_readonly() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_obj = interner.object(vec![PropertyInfo::readonly(
interner.intern_string("a"),
infer_r,
)]);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_obj,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let obj_string = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
TypeId::STRING,
)]);
let obj_number = interner.object(vec![PropertyInfo::readonly(
interner.intern_string("a"),
TypeId::NUMBER,
)]);
subst.insert(t_name, interner.union(vec![obj_string, obj_number]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_object_property_readonly_non_distributive_union_input() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_obj = interner.object(vec![PropertyInfo::readonly(
interner.intern_string("a"),
infer_r,
)]);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_obj,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let obj_string = interner.object(vec![PropertyInfo::readonly(
interner.intern_string("a"),
TypeId::STRING,
)]);
let obj_number = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
TypeId::NUMBER,
)]);
subst.insert(t_name, interner.union(vec![obj_string, obj_number]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_object_property_readonly_non_distributive_union_branch() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_obj = interner.object(vec![PropertyInfo::readonly(
interner.intern_string("a"),
infer_r,
)]);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_obj,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let obj_string = interner.object(vec![PropertyInfo::readonly(
interner.intern_string("a"),
TypeId::STRING,
)]);
subst.insert(t_name, interner.union(vec![obj_string, TypeId::NUMBER]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_conditional_infer_object_property_readonly_wrapper_non_distributive_union_input() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_inner = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
infer_r,
)]);
let extends_obj = interner.intern(TypeData::ReadonlyType(extends_inner));
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_obj,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let obj_string_inner = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
TypeId::STRING,
)]);
let obj_string = interner.intern(TypeData::ReadonlyType(obj_string_inner));
let obj_number = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
TypeId::NUMBER,
)]);
subst.insert(t_name, interner.union(vec![obj_string, obj_number]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_object_property_readonly_wrapper_non_distributive_union_branch() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_inner = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
infer_r,
)]);
let extends_obj = interner.intern(TypeData::ReadonlyType(extends_inner));
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_obj,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let obj_string_inner = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
TypeId::STRING,
)]);
let obj_string = interner.intern(TypeData::ReadonlyType(obj_string_inner));
subst.insert(t_name, interner.union(vec![obj_string, TypeId::NUMBER]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_conditional_infer_object_property_function_return_distributive() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_fn = interner.function(FunctionShape {
params: Vec::new(),
this_type: None,
return_type: infer_r,
type_params: Vec::new(),
type_predicate: None,
is_constructor: false,
is_method: false,
});
let extends_obj = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
extends_fn,
)]);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_obj,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let string_fn = 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_fn = interner.function(FunctionShape {
params: Vec::new(),
this_type: None,
return_type: TypeId::NUMBER,
type_params: Vec::new(),
type_predicate: None,
is_constructor: false,
is_method: false,
});
let obj_string = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
string_fn,
)]);
let obj_number = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
number_fn,
)]);
subst.insert(t_name, interner.union(vec![obj_string, obj_number]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_template_literal_distributive() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_template = interner.template_literal(vec![TemplateSpan::Type(infer_r)]);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_template,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let lit_foo = interner.literal_string("foo");
let lit_bar = interner.literal_string("bar");
subst.insert(t_name, interner.union(vec![lit_foo, lit_bar]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![lit_foo, lit_bar]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_template_literal_with_prefix_distributive() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_template = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("foo")),
TemplateSpan::Type(infer_r),
]);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_template,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let lit_foo = interner.literal_string("foo1");
let lit_bar = interner.literal_string("bar");
subst.insert(t_name, interner.union(vec![lit_foo, lit_bar]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.literal_string("1");
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_template_literal_with_suffix_distributive() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_template = interner.template_literal(vec![
TemplateSpan::Type(infer_r),
TemplateSpan::Text(interner.intern_string("bar")),
]);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_template,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let lit_match = interner.literal_string("foobar");
let lit_other = interner.literal_string("baz");
subst.insert(t_name, interner.union(vec![lit_match, lit_other]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.literal_string("foo");
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_template_literal_non_distributive_union_input() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_template = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("foo")),
TemplateSpan::Type(infer_r),
]);
let cond = ConditionalType {
check_type: interner.tuple(vec![TupleElement {
type_id: t_param,
name: None,
optional: false,
rest: false,
}]),
extends_type: interner.tuple(vec![TupleElement {
type_id: extends_template,
name: None,
optional: false,
rest: false,
}]),
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let lit_foo1 = interner.literal_string("foo1");
let lit_foo2 = interner.literal_string("foo2");
subst.insert(t_name, interner.union(vec![lit_foo1, lit_foo2]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![
interner.literal_string("1"),
interner.literal_string("2"),
]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_template_literal_non_distributive_union_branch() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_template = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("foo")),
TemplateSpan::Type(infer_r),
]);
let cond = ConditionalType {
check_type: interner.tuple(vec![TupleElement {
type_id: t_param,
name: None,
optional: false,
rest: false,
}]),
extends_type: interner.tuple(vec![TupleElement {
type_id: extends_template,
name: None,
optional: false,
rest: false,
}]),
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let lit_foo1 = interner.literal_string("foo1");
let lit_bar = interner.literal_string("bar");
subst.insert(t_name, interner.union(vec![lit_foo1, lit_bar]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_conditional_infer_template_literal_non_distributive_template_union_input() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_template = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("foo")),
TemplateSpan::Type(infer_r),
]);
let cond = ConditionalType {
check_type: interner.tuple(vec![TupleElement {
type_id: t_param,
name: None,
optional: false,
rest: false,
}]),
extends_type: interner.tuple(vec![TupleElement {
type_id: extends_template,
name: None,
optional: false,
rest: false,
}]),
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let foo_template = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("foo")),
TemplateSpan::Type(TypeId::STRING),
]);
let bar_template = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("bar")),
TemplateSpan::Type(TypeId::STRING),
]);
subst.insert(t_name, interner.union(vec![foo_template, bar_template]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_conditional_infer_template_literal_with_constrained_infer_non_distributive_union_input() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: Some(TypeId::STRING),
default: None,
is_const: false,
}));
let extends_template = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("foo")),
TemplateSpan::Type(infer_r),
]);
let cond = ConditionalType {
check_type: interner.tuple(vec![TupleElement {
type_id: t_param,
name: None,
optional: false,
rest: false,
}]),
extends_type: interner.tuple(vec![TupleElement {
type_id: extends_template,
name: None,
optional: false,
rest: false,
}]),
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let lit_foo1 = interner.literal_string("foo1");
let lit_foo2 = interner.literal_string("foo2");
subst.insert(t_name, interner.union(vec![lit_foo1, lit_foo2]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![
interner.literal_string("1"),
interner.literal_string("2"),
]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_template_literal_with_constrained_infer_non_distributive_union_branch() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: Some(TypeId::STRING),
default: None,
is_const: false,
}));
let extends_template = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("foo")),
TemplateSpan::Type(infer_r),
]);
let cond = ConditionalType {
check_type: interner.tuple(vec![TupleElement {
type_id: t_param,
name: None,
optional: false,
rest: false,
}]),
extends_type: interner.tuple(vec![TupleElement {
type_id: extends_template,
name: None,
optional: false,
rest: false,
}]),
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let lit_foo1 = interner.literal_string("foo1");
let lit_bar = interner.literal_string("bar");
subst.insert(t_name, interner.union(vec![lit_foo1, lit_bar]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_conditional_infer_template_literal_with_middle_infer_non_distributive_union_input() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_template = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("foo")),
TemplateSpan::Type(infer_r),
TemplateSpan::Text(interner.intern_string("bar")),
]);
let cond = ConditionalType {
check_type: interner.tuple(vec![TupleElement {
type_id: t_param,
name: None,
optional: false,
rest: false,
}]),
extends_type: interner.tuple(vec![TupleElement {
type_id: extends_template,
name: None,
optional: false,
rest: false,
}]),
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let lit_left = interner.literal_string("foobazbar");
let lit_right = interner.literal_string("foobuzbar");
subst.insert(t_name, interner.union(vec![lit_left, lit_right]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![
interner.literal_string("baz"),
interner.literal_string("buz"),
]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_template_literal_with_middle_infer_non_distributive_union_branch() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_template = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("foo")),
TemplateSpan::Type(infer_r),
TemplateSpan::Text(interner.intern_string("bar")),
]);
let cond = ConditionalType {
check_type: interner.tuple(vec![TupleElement {
type_id: t_param,
name: None,
optional: false,
rest: false,
}]),
extends_type: interner.tuple(vec![TupleElement {
type_id: extends_template,
name: None,
optional: false,
rest: false,
}]),
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let lit_match = interner.literal_string("foobazbar");
let lit_other = interner.literal_string("bar");
subst.insert(t_name, interner.union(vec![lit_match, lit_other]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_conditional_infer_template_literal_with_middle_constrained_non_distributive_union_input() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: Some(TypeId::STRING),
default: None,
is_const: false,
}));
let extends_template = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("foo")),
TemplateSpan::Type(infer_r),
TemplateSpan::Text(interner.intern_string("bar")),
]);
let cond = ConditionalType {
check_type: interner.tuple(vec![TupleElement {
type_id: t_param,
name: None,
optional: false,
rest: false,
}]),
extends_type: interner.tuple(vec![TupleElement {
type_id: extends_template,
name: None,
optional: false,
rest: false,
}]),
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let lit_left = interner.literal_string("foobazbar");
let lit_right = interner.literal_string("foobuzbar");
subst.insert(t_name, interner.union(vec![lit_left, lit_right]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![
interner.literal_string("baz"),
interner.literal_string("buz"),
]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_template_literal_with_middle_constrained_non_distributive_union_branch() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: Some(TypeId::STRING),
default: None,
is_const: false,
}));
let extends_template = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("foo")),
TemplateSpan::Type(infer_r),
TemplateSpan::Text(interner.intern_string("bar")),
]);
let cond = ConditionalType {
check_type: interner.tuple(vec![TupleElement {
type_id: t_param,
name: None,
optional: false,
rest: false,
}]),
extends_type: interner.tuple(vec![TupleElement {
type_id: extends_template,
name: None,
optional: false,
rest: false,
}]),
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let lit_match = interner.literal_string("foobazbar");
let lit_other = interner.literal_string("bar");
subst.insert(t_name, interner.union(vec![lit_match, lit_other]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_conditional_infer_template_literal_with_middle_non_distributive_non_matching_union_branch()
{
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_template = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("foo")),
TemplateSpan::Type(infer_r),
TemplateSpan::Text(interner.intern_string("bar")),
]);
let cond = ConditionalType {
check_type: interner.tuple(vec![TupleElement {
type_id: t_param,
name: None,
optional: false,
rest: false,
}]),
extends_type: interner.tuple(vec![TupleElement {
type_id: extends_template,
name: None,
optional: false,
rest: false,
}]),
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let lit_match = interner.literal_string("foobazbar");
let lit_other = interner.literal_string("bar");
subst.insert(t_name, interner.union(vec![lit_match, lit_other]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_conditional_infer_template_literal_with_middle_non_distributive_non_string_union_branch() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_template = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("foo")),
TemplateSpan::Type(infer_r),
TemplateSpan::Text(interner.intern_string("bar")),
]);
let cond = ConditionalType {
check_type: interner.tuple(vec![TupleElement {
type_id: t_param,
name: None,
optional: false,
rest: false,
}]),
extends_type: interner.tuple(vec![TupleElement {
type_id: extends_template,
name: None,
optional: false,
rest: false,
}]),
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let lit_match = interner.literal_string("foobazbar");
subst.insert(t_name, interner.union(vec![lit_match, TypeId::NUMBER]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_conditional_infer_template_literal_with_middle_non_distributive_non_string_template_union_branch()
{
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_template = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("foo")),
TemplateSpan::Type(infer_r),
TemplateSpan::Text(interner.intern_string("bar")),
]);
let cond = ConditionalType {
check_type: interner.tuple(vec![TupleElement {
type_id: t_param,
name: None,
optional: false,
rest: false,
}]),
extends_type: interner.tuple(vec![TupleElement {
type_id: extends_template,
name: None,
optional: false,
rest: false,
}]),
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let middle_template = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("foo")),
TemplateSpan::Type(TypeId::STRING),
TemplateSpan::Text(interner.intern_string("bar")),
]);
subst.insert(
t_name,
interner.union(vec![middle_template, TypeId::NUMBER]),
);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_conditional_infer_template_literal_two_infers_non_distributive_union_input() {
let interner = TypeInterner::new();
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 infer_a_name = interner.intern_string("A");
let infer_a = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_a_name,
constraint: None,
default: None,
is_const: false,
}));
let infer_b_name = interner.intern_string("B");
let infer_b = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_b_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_template = interner.template_literal(vec![
TemplateSpan::Type(infer_a),
TemplateSpan::Text(interner.intern_string("-")),
TemplateSpan::Type(infer_b),
]);
let cond = ConditionalType {
check_type: interner.tuple(vec![TupleElement {
type_id: t_param,
name: None,
optional: false,
rest: false,
}]),
extends_type: interner.tuple(vec![TupleElement {
type_id: extends_template,
name: None,
optional: false,
rest: false,
}]),
true_type: interner.union(vec![infer_a, infer_b]),
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let lit_left = interner.literal_string("foo-bar");
let lit_right = interner.literal_string("baz-qux");
subst.insert(t_name, interner.union(vec![lit_left, lit_right]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![
interner.literal_string("foo"),
interner.literal_string("baz"),
interner.literal_string("bar"),
interner.literal_string("qux"),
]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_template_literal_two_infers_non_distributive_non_matching_union_branch() {
let interner = TypeInterner::new();
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 infer_a_name = interner.intern_string("A");
let infer_a = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_a_name,
constraint: None,
default: None,
is_const: false,
}));
let infer_b_name = interner.intern_string("B");
let infer_b = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_b_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_template = interner.template_literal(vec![
TemplateSpan::Type(infer_a),
TemplateSpan::Text(interner.intern_string("-")),
TemplateSpan::Type(infer_b),
]);
let cond = ConditionalType {
check_type: interner.tuple(vec![TupleElement {
type_id: t_param,
name: None,
optional: false,
rest: false,
}]),
extends_type: interner.tuple(vec![TupleElement {
type_id: extends_template,
name: None,
optional: false,
rest: false,
}]),
true_type: interner.union(vec![infer_a, infer_b]),
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let lit_match = interner.literal_string("foo-bar");
let lit_other = interner.literal_string("baz");
subst.insert(t_name, interner.union(vec![lit_match, lit_other]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_conditional_infer_template_literal_two_infers_non_distributive_union_branch() {
let interner = TypeInterner::new();
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 infer_a_name = interner.intern_string("A");
let infer_a = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_a_name,
constraint: None,
default: None,
is_const: false,
}));
let infer_b_name = interner.intern_string("B");
let infer_b = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_b_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_template = interner.template_literal(vec![
TemplateSpan::Type(infer_a),
TemplateSpan::Text(interner.intern_string("-")),
TemplateSpan::Type(infer_b),
]);
let cond = ConditionalType {
check_type: interner.tuple(vec![TupleElement {
type_id: t_param,
name: None,
optional: false,
rest: false,
}]),
extends_type: interner.tuple(vec![TupleElement {
type_id: extends_template,
name: None,
optional: false,
rest: false,
}]),
true_type: interner.union(vec![infer_a, infer_b]),
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let lit_match = interner.literal_string("foo-bar");
subst.insert(t_name, interner.union(vec![lit_match, TypeId::NUMBER]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_conditional_infer_template_literal_with_suffix_non_distributive_union_input() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_template = interner.template_literal(vec![
TemplateSpan::Type(infer_r),
TemplateSpan::Text(interner.intern_string("bar")),
]);
let cond = ConditionalType {
check_type: interner.tuple(vec![TupleElement {
type_id: t_param,
name: None,
optional: false,
rest: false,
}]),
extends_type: interner.tuple(vec![TupleElement {
type_id: extends_template,
name: None,
optional: false,
rest: false,
}]),
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let lit_left = interner.literal_string("foobar");
let lit_right = interner.literal_string("bazbar");
subst.insert(t_name, interner.union(vec![lit_left, lit_right]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![
interner.literal_string("foo"),
interner.literal_string("baz"),
]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_template_literal_with_suffix_non_distributive_union_branch() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_template = interner.template_literal(vec![
TemplateSpan::Type(infer_r),
TemplateSpan::Text(interner.intern_string("bar")),
]);
let cond = ConditionalType {
check_type: interner.tuple(vec![TupleElement {
type_id: t_param,
name: None,
optional: false,
rest: false,
}]),
extends_type: interner.tuple(vec![TupleElement {
type_id: extends_template,
name: None,
optional: false,
rest: false,
}]),
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let lit_match = interner.literal_string("foobar");
let lit_other = interner.literal_string("baz");
subst.insert(t_name, interner.union(vec![lit_match, lit_other]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_conditional_infer_template_literal_with_suffix_non_distributive_non_matching_union_branch()
{
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_template = interner.template_literal(vec![
TemplateSpan::Type(infer_r),
TemplateSpan::Text(interner.intern_string("bar")),
]);
let cond = ConditionalType {
check_type: interner.tuple(vec![TupleElement {
type_id: t_param,
name: None,
optional: false,
rest: false,
}]),
extends_type: interner.tuple(vec![TupleElement {
type_id: extends_template,
name: None,
optional: false,
rest: false,
}]),
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let lit_match = interner.literal_string("foobar");
let lit_other = interner.literal_string("baz");
subst.insert(t_name, interner.union(vec![lit_match, lit_other]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_conditional_infer_template_literal_with_suffix_non_distributive_non_string_union_branch() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_template = interner.template_literal(vec![
TemplateSpan::Type(infer_r),
TemplateSpan::Text(interner.intern_string("bar")),
]);
let cond = ConditionalType {
check_type: interner.tuple(vec![TupleElement {
type_id: t_param,
name: None,
optional: false,
rest: false,
}]),
extends_type: interner.tuple(vec![TupleElement {
type_id: extends_template,
name: None,
optional: false,
rest: false,
}]),
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let lit_match = interner.literal_string("foobar");
subst.insert(t_name, interner.union(vec![lit_match, TypeId::NUMBER]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_conditional_infer_template_literal_with_suffix_non_distributive_non_string_template_union_branch()
{
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_template = interner.template_literal(vec![
TemplateSpan::Type(infer_r),
TemplateSpan::Text(interner.intern_string("bar")),
]);
let cond = ConditionalType {
check_type: interner.tuple(vec![TupleElement {
type_id: t_param,
name: None,
optional: false,
rest: false,
}]),
extends_type: interner.tuple(vec![TupleElement {
type_id: extends_template,
name: None,
optional: false,
rest: false,
}]),
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let suffix_template = interner.template_literal(vec![
TemplateSpan::Type(TypeId::STRING),
TemplateSpan::Text(interner.intern_string("bar")),
]);
subst.insert(
t_name,
interner.union(vec![suffix_template, TypeId::NUMBER]),
);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_conditional_infer_template_literal_with_suffix_constrained_non_distributive_union_input() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: Some(TypeId::STRING),
default: None,
is_const: false,
}));
let extends_template = interner.template_literal(vec![
TemplateSpan::Type(infer_r),
TemplateSpan::Text(interner.intern_string("bar")),
]);
let cond = ConditionalType {
check_type: interner.tuple(vec![TupleElement {
type_id: t_param,
name: None,
optional: false,
rest: false,
}]),
extends_type: interner.tuple(vec![TupleElement {
type_id: extends_template,
name: None,
optional: false,
rest: false,
}]),
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let lit_left = interner.literal_string("foobar");
let lit_right = interner.literal_string("bazbar");
subst.insert(t_name, interner.union(vec![lit_left, lit_right]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![
interner.literal_string("foo"),
interner.literal_string("baz"),
]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_template_literal_with_suffix_constrained_non_distributive_union_branch() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: Some(TypeId::STRING),
default: None,
is_const: false,
}));
let extends_template = interner.template_literal(vec![
TemplateSpan::Type(infer_r),
TemplateSpan::Text(interner.intern_string("bar")),
]);
let cond = ConditionalType {
check_type: interner.tuple(vec![TupleElement {
type_id: t_param,
name: None,
optional: false,
rest: false,
}]),
extends_type: interner.tuple(vec![TupleElement {
type_id: extends_template,
name: None,
optional: false,
rest: false,
}]),
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let lit_match = interner.literal_string("foobar");
let lit_other = interner.literal_string("baz");
subst.insert(t_name, interner.union(vec![lit_match, lit_other]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_conditional_infer_template_literal_with_prefix_non_distributive_union_input() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_template = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("foo")),
TemplateSpan::Type(infer_r),
]);
let cond = ConditionalType {
check_type: interner.tuple(vec![TupleElement {
type_id: t_param,
name: None,
optional: false,
rest: false,
}]),
extends_type: interner.tuple(vec![TupleElement {
type_id: extends_template,
name: None,
optional: false,
rest: false,
}]),
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let lit_left = interner.literal_string("foo1");
let lit_right = interner.literal_string("foo2");
subst.insert(t_name, interner.union(vec![lit_left, lit_right]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![
interner.literal_string("1"),
interner.literal_string("2"),
]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_template_literal_with_prefix_non_distributive_union_branch() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_template = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("foo")),
TemplateSpan::Type(infer_r),
]);
let cond = ConditionalType {
check_type: interner.tuple(vec![TupleElement {
type_id: t_param,
name: None,
optional: false,
rest: false,
}]),
extends_type: interner.tuple(vec![TupleElement {
type_id: extends_template,
name: None,
optional: false,
rest: false,
}]),
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let lit_match = interner.literal_string("foo1");
let lit_other = interner.literal_string("bar");
subst.insert(t_name, interner.union(vec![lit_match, lit_other]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_conditional_infer_template_literal_with_prefix_non_distributive_non_matching_union_branch()
{
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_template = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("foo")),
TemplateSpan::Type(infer_r),
]);
let cond = ConditionalType {
check_type: interner.tuple(vec![TupleElement {
type_id: t_param,
name: None,
optional: false,
rest: false,
}]),
extends_type: interner.tuple(vec![TupleElement {
type_id: extends_template,
name: None,
optional: false,
rest: false,
}]),
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let lit_match = interner.literal_string("foo1");
let lit_other = interner.literal_string("bar");
subst.insert(t_name, interner.union(vec![lit_match, lit_other]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_conditional_infer_template_literal_with_prefix_non_distributive_non_string_union_branch() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_template = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("foo")),
TemplateSpan::Type(infer_r),
]);
let cond = ConditionalType {
check_type: interner.tuple(vec![TupleElement {
type_id: t_param,
name: None,
optional: false,
rest: false,
}]),
extends_type: interner.tuple(vec![TupleElement {
type_id: extends_template,
name: None,
optional: false,
rest: false,
}]),
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let lit_match = interner.literal_string("foo1");
subst.insert(t_name, interner.union(vec![lit_match, TypeId::NUMBER]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_conditional_infer_template_literal_with_prefix_constrained_non_distributive_union_input() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: Some(TypeId::STRING),
default: None,
is_const: false,
}));
let extends_template = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("foo")),
TemplateSpan::Type(infer_r),
]);
let cond = ConditionalType {
check_type: interner.tuple(vec![TupleElement {
type_id: t_param,
name: None,
optional: false,
rest: false,
}]),
extends_type: interner.tuple(vec![TupleElement {
type_id: extends_template,
name: None,
optional: false,
rest: false,
}]),
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let lit_left = interner.literal_string("foo1");
let lit_right = interner.literal_string("foo2");
subst.insert(t_name, interner.union(vec![lit_left, lit_right]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![
interner.literal_string("1"),
interner.literal_string("2"),
]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_template_literal_with_prefix_constrained_non_distributive_union_branch() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: Some(TypeId::STRING),
default: None,
is_const: false,
}));
let extends_template = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("foo")),
TemplateSpan::Type(infer_r),
]);
let cond = ConditionalType {
check_type: interner.tuple(vec![TupleElement {
type_id: t_param,
name: None,
optional: false,
rest: false,
}]),
extends_type: interner.tuple(vec![TupleElement {
type_id: extends_template,
name: None,
optional: false,
rest: false,
}]),
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let lit_match = interner.literal_string("foo1");
let lit_other = interner.literal_string("bar");
subst.insert(t_name, interner.union(vec![lit_match, lit_other]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_conditional_infer_template_literal_two_infers_with_constraint_non_distributive_union_input()
{
let interner = TypeInterner::new();
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 infer_a_name = interner.intern_string("A");
let infer_a = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_a_name,
constraint: Some(TypeId::STRING),
default: None,
is_const: false,
}));
let infer_b_name = interner.intern_string("B");
let infer_b = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_b_name,
constraint: Some(TypeId::STRING),
default: None,
is_const: false,
}));
let extends_template = interner.template_literal(vec![
TemplateSpan::Type(infer_a),
TemplateSpan::Text(interner.intern_string("-")),
TemplateSpan::Type(infer_b),
]);
let cond = ConditionalType {
check_type: interner.tuple(vec![TupleElement {
type_id: t_param,
name: None,
optional: false,
rest: false,
}]),
extends_type: interner.tuple(vec![TupleElement {
type_id: extends_template,
name: None,
optional: false,
rest: false,
}]),
true_type: interner.union(vec![infer_a, infer_b]),
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let lit_left = interner.literal_string("foo-bar");
let lit_right = interner.literal_string("baz-qux");
subst.insert(t_name, interner.union(vec![lit_left, lit_right]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![
interner.literal_string("foo"),
interner.literal_string("baz"),
interner.literal_string("bar"),
interner.literal_string("qux"),
]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_template_literal_two_infers_with_constraint_non_distributive_non_matching_union_branch()
{
let interner = TypeInterner::new();
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 infer_a_name = interner.intern_string("A");
let infer_a = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_a_name,
constraint: Some(TypeId::STRING),
default: None,
is_const: false,
}));
let infer_b_name = interner.intern_string("B");
let infer_b = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_b_name,
constraint: Some(TypeId::STRING),
default: None,
is_const: false,
}));
let extends_template = interner.template_literal(vec![
TemplateSpan::Type(infer_a),
TemplateSpan::Text(interner.intern_string("-")),
TemplateSpan::Type(infer_b),
]);
let cond = ConditionalType {
check_type: interner.tuple(vec![TupleElement {
type_id: t_param,
name: None,
optional: false,
rest: false,
}]),
extends_type: interner.tuple(vec![TupleElement {
type_id: extends_template,
name: None,
optional: false,
rest: false,
}]),
true_type: interner.union(vec![infer_a, infer_b]),
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let lit_match = interner.literal_string("foo-bar");
let lit_other = interner.literal_string("baz");
subst.insert(t_name, interner.union(vec![lit_match, lit_other]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_conditional_infer_template_literal_two_infers_with_constraint_non_distributive_union_branch()
{
let interner = TypeInterner::new();
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 infer_a_name = interner.intern_string("A");
let infer_a = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_a_name,
constraint: Some(TypeId::STRING),
default: None,
is_const: false,
}));
let infer_b_name = interner.intern_string("B");
let infer_b = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_b_name,
constraint: Some(TypeId::STRING),
default: None,
is_const: false,
}));
let extends_template = interner.template_literal(vec![
TemplateSpan::Type(infer_a),
TemplateSpan::Text(interner.intern_string("-")),
TemplateSpan::Type(infer_b),
]);
let cond = ConditionalType {
check_type: interner.tuple(vec![TupleElement {
type_id: t_param,
name: None,
optional: false,
rest: false,
}]),
extends_type: interner.tuple(vec![TupleElement {
type_id: extends_template,
name: None,
optional: false,
rest: false,
}]),
true_type: interner.union(vec![infer_a, infer_b]),
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let lit_match = interner.literal_string("foo-bar");
subst.insert(t_name, interner.union(vec![lit_match, TypeId::NUMBER]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_conditional_infer_template_literal_union_input_distributive() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_template = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("foo")),
TemplateSpan::Type(infer_r),
]);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_template,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let foo_template = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("foo")),
TemplateSpan::Type(TypeId::STRING),
]);
let bar_template = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("bar")),
TemplateSpan::Type(TypeId::STRING),
]);
subst.insert(t_name, interner.union(vec![foo_template, bar_template]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::STRING);
}
#[test]
fn test_conditional_infer_template_literal_from_string_input() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_template = interner.template_literal(vec![TemplateSpan::Type(infer_r)]);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_template,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
subst.insert(t_name, TypeId::STRING);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::STRING);
}
#[test]
fn test_conditional_infer_template_literal_from_template_string_input() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_template = interner.template_literal(vec![TemplateSpan::Type(infer_r)]);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_template,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let template_string = interner.template_literal(vec![TemplateSpan::Type(TypeId::STRING)]);
subst.insert(t_name, template_string);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::STRING);
}
#[test]
fn test_conditional_infer_template_literal_with_middle_infer_distributive() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_template = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("foo")),
TemplateSpan::Type(infer_r),
TemplateSpan::Text(interner.intern_string("bar")),
]);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_template,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let lit_match = interner.literal_string("foobazbar");
let lit_other = interner.literal_string("bar");
subst.insert(t_name, interner.union(vec![lit_match, lit_other]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.literal_string("baz");
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_template_literal_two_infers_distributive() {
let interner = TypeInterner::new();
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 infer_a_name = interner.intern_string("A");
let infer_a = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_a_name,
constraint: None,
default: None,
is_const: false,
}));
let infer_b_name = interner.intern_string("B");
let infer_b = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_b_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_template = interner.template_literal(vec![
TemplateSpan::Type(infer_a),
TemplateSpan::Text(interner.intern_string("-")),
TemplateSpan::Type(infer_b),
]);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_template,
true_type: interner.union(vec![infer_a, infer_b]),
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let lit_left = interner.literal_string("foo-bar");
let lit_right = interner.literal_string("baz-qux");
subst.insert(t_name, interner.union(vec![lit_left, lit_right]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![
interner.literal_string("foo"),
interner.literal_string("baz"),
interner.literal_string("bar"),
interner.literal_string("qux"),
]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_template_literal_with_constrained_infer_distributive() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: Some(TypeId::STRING),
default: None,
is_const: false,
}));
let extends_template = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("foo")),
TemplateSpan::Type(infer_r),
]);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_template,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let lit_foo1 = interner.literal_string("foo1");
let lit_foo2 = interner.literal_string("foo2");
subst.insert(t_name, interner.union(vec![lit_foo1, lit_foo2]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![
interner.literal_string("1"),
interner.literal_string("2"),
]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_nested_object_property_distributive() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_inner = interner.object(vec![PropertyInfo::new(
interner.intern_string("b"),
infer_r,
)]);
let extends_obj = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
extends_inner,
)]);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_obj,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let obj_a_string = interner.object(vec![PropertyInfo::new(
interner.intern_string("b"),
TypeId::STRING,
)]);
let obj_a_number = interner.object(vec![PropertyInfo::new(
interner.intern_string("b"),
TypeId::NUMBER,
)]);
let obj_string = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
obj_a_string,
)]);
let obj_number = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
obj_a_number,
)]);
subst.insert(t_name, interner.union(vec![obj_string, obj_number]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_nested_object_property_non_distributive_union_input() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_inner = interner.object(vec![PropertyInfo::new(
interner.intern_string("b"),
infer_r,
)]);
let extends_obj = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
extends_inner,
)]);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_obj,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let obj_a_string = interner.object(vec![PropertyInfo::new(
interner.intern_string("b"),
TypeId::STRING,
)]);
let obj_a_number = interner.object(vec![PropertyInfo::new(
interner.intern_string("b"),
TypeId::NUMBER,
)]);
let obj_string = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
obj_a_string,
)]);
let obj_number = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
obj_a_number,
)]);
subst.insert(t_name, interner.union(vec![obj_string, obj_number]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_nested_object_property_non_distributive_union_branch() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_inner = interner.object(vec![PropertyInfo::new(
interner.intern_string("b"),
infer_r,
)]);
let extends_obj = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
extends_inner,
)]);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_obj,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let obj_a_string = interner.object(vec![PropertyInfo::new(
interner.intern_string("b"),
TypeId::STRING,
)]);
let obj_string = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
obj_a_string,
)]);
subst.insert(t_name, interner.union(vec![obj_string, TypeId::NUMBER]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_conditional_infer_nested_object_property_with_constraint() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: Some(TypeId::STRING),
default: None,
is_const: false,
}));
let extends_inner = interner.object(vec![PropertyInfo::new(
interner.intern_string("b"),
infer_r,
)]);
let extends_obj = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
extends_inner,
)]);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_obj,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let obj_a_string = interner.object(vec![PropertyInfo::new(
interner.intern_string("b"),
TypeId::STRING,
)]);
let obj_a_number = interner.object(vec![PropertyInfo::new(
interner.intern_string("b"),
TypeId::NUMBER,
)]);
let obj_string = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
obj_a_string,
)]);
let obj_number = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
obj_a_number,
)]);
subst.insert(t_name, interner.union(vec![obj_string, obj_number]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![TypeId::STRING, TypeId::UNDEFINED]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_nested_object_property_readonly() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_inner = interner.object(vec![PropertyInfo::new(
interner.intern_string("b"),
infer_r,
)]);
let extends_obj = interner.object(vec![PropertyInfo::readonly(
interner.intern_string("a"),
extends_inner,
)]);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_obj,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let obj_a_string = interner.object(vec![PropertyInfo::new(
interner.intern_string("b"),
TypeId::STRING,
)]);
let obj_a_number = interner.object(vec![PropertyInfo::new(
interner.intern_string("b"),
TypeId::NUMBER,
)]);
let obj_string = interner.object(vec![PropertyInfo::readonly(
interner.intern_string("a"),
obj_a_string,
)]);
let obj_number = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
obj_a_number,
)]);
subst.insert(t_name, interner.union(vec![obj_string, obj_number]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_nested_object_property_readonly_wrapper() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_inner_obj = interner.object(vec![PropertyInfo::new(
interner.intern_string("b"),
infer_r,
)]);
let extends_inner = interner.intern(TypeData::ReadonlyType(extends_inner_obj));
let extends_obj = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
extends_inner,
)]);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_obj,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let obj_a_string_inner = interner.object(vec![PropertyInfo::new(
interner.intern_string("b"),
TypeId::STRING,
)]);
let obj_a_string = interner.intern(TypeData::ReadonlyType(obj_a_string_inner));
let obj_a_number = interner.object(vec![PropertyInfo::new(
interner.intern_string("b"),
TypeId::NUMBER,
)]);
let obj_string = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
obj_a_string,
)]);
let obj_number = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
obj_a_number,
)]);
subst.insert(t_name, interner.union(vec![obj_string, obj_number]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_nested_object_property_readonly_wrapper_non_distributive_union_input() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_inner_obj = interner.object(vec![PropertyInfo::new(
interner.intern_string("b"),
infer_r,
)]);
let extends_inner = interner.intern(TypeData::ReadonlyType(extends_inner_obj));
let extends_obj = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
extends_inner,
)]);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_obj,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let obj_a_string_inner = interner.object(vec![PropertyInfo::new(
interner.intern_string("b"),
TypeId::STRING,
)]);
let obj_a_string = interner.intern(TypeData::ReadonlyType(obj_a_string_inner));
let obj_a_number = interner.object(vec![PropertyInfo::new(
interner.intern_string("b"),
TypeId::NUMBER,
)]);
let obj_string = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
obj_a_string,
)]);
let obj_number = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
obj_a_number,
)]);
subst.insert(t_name, interner.union(vec![obj_string, obj_number]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_nested_object_property_readonly_wrapper_non_distributive_union_branch() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_inner_obj = interner.object(vec![PropertyInfo::new(
interner.intern_string("b"),
infer_r,
)]);
let extends_inner = interner.intern(TypeData::ReadonlyType(extends_inner_obj));
let extends_obj = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
extends_inner,
)]);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_obj,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let obj_a_string_inner = interner.object(vec![PropertyInfo::new(
interner.intern_string("b"),
TypeId::STRING,
)]);
let obj_a_string = interner.intern(TypeData::ReadonlyType(obj_a_string_inner));
let obj_string = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
obj_a_string,
)]);
subst.insert(t_name, interner.union(vec![obj_string, TypeId::NUMBER]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_conditional_infer_nested_object_property_non_matching_branch() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_inner = interner.object(vec![PropertyInfo::new(
interner.intern_string("b"),
infer_r,
)]);
let extends_obj = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
extends_inner,
)]);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_obj,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let obj_a_string = interner.object(vec![PropertyInfo::new(
interner.intern_string("b"),
TypeId::STRING,
)]);
let obj_a_number = interner.object(vec![PropertyInfo::new(
interner.intern_string("c"),
TypeId::NUMBER,
)]);
let obj_match = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
obj_a_string,
)]);
let obj_non_match = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
obj_a_number,
)]);
subst.insert(t_name, interner.union(vec![obj_match, obj_non_match]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::STRING);
}
#[test]
fn test_conditional_infer_nested_object_property_union_value() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_inner = interner.object(vec![PropertyInfo::new(
interner.intern_string("b"),
infer_r,
)]);
let extends_obj = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
extends_inner,
)]);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_obj,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let b_union = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let obj_a = interner.object(vec![PropertyInfo::new(
interner.intern_string("b"),
b_union,
)]);
let obj = interner.object(vec![PropertyInfo::new(interner.intern_string("a"), obj_a)]);
subst.insert(t_name, obj);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, b_union);
}
#[test]
fn test_conditional_infer_object_property_non_object_union_branch() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_obj = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
infer_r,
)]);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_obj,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let obj_match = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
TypeId::STRING,
)]);
subst.insert(t_name, interner.union(vec![obj_match, TypeId::NUMBER]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::STRING);
}
#[test]
fn test_conditional_infer_object_property_non_distributive_non_object_union_branch() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_obj = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
infer_r,
)]);
let cond = ConditionalType {
check_type: interner.tuple(vec![TupleElement {
type_id: t_param,
name: None,
optional: false,
rest: false,
}]),
extends_type: interner.tuple(vec![TupleElement {
type_id: extends_obj,
name: None,
optional: false,
rest: false,
}]),
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let obj_match = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
TypeId::STRING,
)]);
subst.insert(t_name, interner.union(vec![obj_match, TypeId::NUMBER]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_conditional_infer_object_index_signature_distributive() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_obj = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: Vec::new(),
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: infer_r,
readonly: false,
}),
number_index: None,
});
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_obj,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let obj_string = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
TypeId::STRING,
)]);
let obj_number = interner.object(vec![PropertyInfo::new(
interner.intern_string("b"),
TypeId::NUMBER,
)]);
subst.insert(t_name, interner.union(vec![obj_string, obj_number]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_number_index_signature_distributive() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_obj = 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: infer_r,
readonly: false,
}),
});
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_obj,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let obj_string = interner.object(vec![PropertyInfo::new(
interner.intern_string("0"),
TypeId::STRING,
)]);
let obj_number = interner.object(vec![PropertyInfo::new(
interner.intern_string("1"),
TypeId::NUMBER,
)]);
subst.insert(t_name, interner.union(vec![obj_string, obj_number]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_number_index_signature_non_distributive_union_input() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_obj = 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: infer_r,
readonly: false,
}),
});
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_obj,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let obj_string = interner.object(vec![PropertyInfo::new(
interner.intern_string("0"),
TypeId::STRING,
)]);
let obj_number = interner.object(vec![PropertyInfo::new(
interner.intern_string("1"),
TypeId::NUMBER,
)]);
subst.insert(t_name, interner.union(vec![obj_string, obj_number]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_number_index_signature_non_distributive_union_branch() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_obj = 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: infer_r,
readonly: false,
}),
});
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_obj,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let obj_string = interner.object(vec![PropertyInfo::new(
interner.intern_string("0"),
TypeId::STRING,
)]);
subst.insert(t_name, interner.union(vec![obj_string, TypeId::NUMBER]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_conditional_infer_object_index_signature_non_object_union_branch() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_obj = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: Vec::new(),
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: infer_r,
readonly: false,
}),
number_index: None,
});
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_obj,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let obj_string = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
TypeId::STRING,
)]);
subst.insert(t_name, interner.union(vec![obj_string, TypeId::NUMBER]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::STRING);
}
#[test]
fn test_conditional_infer_object_index_signature_non_distributive_union_input() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_obj = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: Vec::new(),
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: infer_r,
readonly: false,
}),
number_index: None,
});
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_obj,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let obj_string = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
TypeId::STRING,
)]);
let obj_number = interner.object(vec![PropertyInfo::new(
interner.intern_string("b"),
TypeId::NUMBER,
)]);
subst.insert(t_name, interner.union(vec![obj_string, obj_number]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_object_index_signature_non_distributive_union_branch() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_obj = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: Vec::new(),
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: infer_r,
readonly: false,
}),
number_index: None,
});
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_obj,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let obj_string = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
TypeId::STRING,
)]);
subst.insert(t_name, interner.union(vec![obj_string, TypeId::NUMBER]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_conditional_infer_optional_property_missing_object() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_obj = interner.object(vec![PropertyInfo::opt(
interner.intern_string("a"),
infer_r,
)]);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_obj,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let empty_obj = interner.object(Vec::new());
subst.insert(t_name, empty_obj);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::UNDEFINED);
}
#[test]
fn test_conditional_infer_optional_property_present_distributive() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_obj = interner.object(vec![PropertyInfo::opt(
interner.intern_string("a"),
infer_r,
)]);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_obj,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let obj_string = interner.object(vec![PropertyInfo::opt(
interner.intern_string("a"),
TypeId::STRING,
)]);
let obj_number = interner.object(vec![PropertyInfo::opt(
interner.intern_string("a"),
TypeId::NUMBER,
)]);
subst.insert(t_name, interner.union(vec![obj_string, obj_number]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER, TypeId::UNDEFINED]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_optional_property_with_constraint() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: Some(TypeId::STRING),
default: None,
is_const: false,
}));
let extends_obj = interner.object(vec![PropertyInfo::opt(
interner.intern_string("a"),
infer_r,
)]);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_obj,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let obj_string = interner.object(vec![PropertyInfo::opt(
interner.intern_string("a"),
TypeId::STRING,
)]);
let obj_number = interner.object(vec![PropertyInfo::opt(
interner.intern_string("a"),
TypeId::NUMBER,
)]);
subst.insert(t_name, interner.union(vec![obj_string, obj_number]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::STRING);
}
#[test]
fn test_conditional_infer_optional_property_non_distributive_union_input() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_obj = interner.object(vec![PropertyInfo::opt(
interner.intern_string("a"),
infer_r,
)]);
let cond = ConditionalType {
check_type: interner.tuple(vec![TupleElement {
type_id: t_param,
name: None,
optional: false,
rest: false,
}]),
extends_type: interner.tuple(vec![TupleElement {
type_id: extends_obj,
name: None,
optional: false,
rest: false,
}]),
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let obj_string = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
TypeId::STRING,
)]);
let empty_obj = interner.object(Vec::new());
subst.insert(t_name, interner.union(vec![obj_string, empty_obj]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![TypeId::STRING, TypeId::UNDEFINED]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_optional_property_non_distributive_union_branch() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_obj = interner.object(vec![PropertyInfo::opt(
interner.intern_string("a"),
infer_r,
)]);
let cond = ConditionalType {
check_type: interner.tuple(vec![TupleElement {
type_id: t_param,
name: None,
optional: false,
rest: false,
}]),
extends_type: interner.tuple(vec![TupleElement {
type_id: extends_obj,
name: None,
optional: false,
rest: false,
}]),
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let obj_string = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
TypeId::STRING,
)]);
subst.insert(t_name, interner.union(vec![obj_string, TypeId::NUMBER]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_conditional_infer_object_property_intersection_check() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_obj = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
infer_r,
)]);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_obj,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let obj_a = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
TypeId::STRING,
)]);
let obj_b = interner.object(vec![PropertyInfo::new(
interner.intern_string("b"),
TypeId::NUMBER,
)]);
let intersection = interner.intersection(vec![obj_a, obj_b]);
subst.insert(t_name, intersection);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::STRING);
}
#[test]
fn test_conditional_infer_function_param_distributive() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_fn = interner.function(FunctionShape {
params: vec![ParamInfo::unnamed(infer_r)],
this_type: None,
return_type: TypeId::VOID,
type_params: Vec::new(),
type_predicate: None,
is_constructor: false,
is_method: false,
});
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_fn,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let string_fn = 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,
});
let number_fn = 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,
});
subst.insert(t_name, interner.union(vec![string_fn, number_fn]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_function_optional_param_distributive() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_fn = interner.function(FunctionShape {
params: vec![ParamInfo {
name: None,
type_id: infer_r,
optional: true,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_params: Vec::new(),
type_predicate: None,
is_constructor: false,
is_method: false,
});
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_fn,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let string_fn = interner.function(FunctionShape {
params: vec![ParamInfo {
name: None,
type_id: TypeId::STRING,
optional: true,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_params: Vec::new(),
type_predicate: None,
is_constructor: false,
is_method: false,
});
let number_fn = interner.function(FunctionShape {
params: vec![ParamInfo {
name: None,
type_id: TypeId::NUMBER,
optional: true,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_params: Vec::new(),
type_predicate: None,
is_constructor: false,
is_method: false,
});
subst.insert(t_name, interner.union(vec![string_fn, number_fn]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER, TypeId::UNDEFINED]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_function_optional_param_non_distributive_union_input() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_fn = interner.function(FunctionShape {
params: vec![ParamInfo {
name: None,
type_id: infer_r,
optional: true,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_params: Vec::new(),
type_predicate: None,
is_constructor: false,
is_method: false,
});
let cond = ConditionalType {
check_type: interner.tuple(vec![TupleElement {
type_id: t_param,
name: None,
optional: false,
rest: false,
}]),
extends_type: interner.tuple(vec![TupleElement {
type_id: extends_fn,
name: None,
optional: false,
rest: false,
}]),
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let string_fn = interner.function(FunctionShape {
params: vec![ParamInfo {
name: None,
type_id: TypeId::STRING,
optional: true,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_params: Vec::new(),
type_predicate: None,
is_constructor: false,
is_method: false,
});
let number_fn = interner.function(FunctionShape {
params: vec![ParamInfo {
name: None,
type_id: TypeId::NUMBER,
optional: true,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_params: Vec::new(),
type_predicate: None,
is_constructor: false,
is_method: false,
});
subst.insert(t_name, interner.union(vec![string_fn, number_fn]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER, TypeId::UNDEFINED]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_function_param_non_function_union_branch() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_fn = interner.function(FunctionShape {
params: vec![ParamInfo::unnamed(infer_r)],
this_type: None,
return_type: TypeId::VOID,
type_params: Vec::new(),
type_predicate: None,
is_constructor: false,
is_method: false,
});
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_fn,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let string_fn = 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,
});
subst.insert(t_name, interner.union(vec![string_fn, TypeId::NUMBER]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::STRING);
}
#[test]
fn test_conditional_infer_function_param_non_distributive_union_input() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_fn = interner.function(FunctionShape {
params: vec![ParamInfo::unnamed(infer_r)],
this_type: None,
return_type: TypeId::VOID,
type_params: Vec::new(),
type_predicate: None,
is_constructor: false,
is_method: false,
});
let cond = ConditionalType {
check_type: interner.tuple(vec![TupleElement {
type_id: t_param,
name: None,
optional: false,
rest: false,
}]),
extends_type: interner.tuple(vec![TupleElement {
type_id: extends_fn,
name: None,
optional: false,
rest: false,
}]),
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let string_fn = 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,
});
let number_fn = 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,
});
subst.insert(t_name, interner.union(vec![string_fn, number_fn]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_function_param_non_distributive_union_branch() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_fn = interner.function(FunctionShape {
params: vec![ParamInfo::unnamed(infer_r)],
this_type: None,
return_type: TypeId::VOID,
type_params: Vec::new(),
type_predicate: None,
is_constructor: false,
is_method: false,
});
let cond = ConditionalType {
check_type: interner.tuple(vec![TupleElement {
type_id: t_param,
name: None,
optional: false,
rest: false,
}]),
extends_type: interner.tuple(vec![TupleElement {
type_id: extends_fn,
name: None,
optional: false,
rest: false,
}]),
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let string_fn = 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,
});
subst.insert(t_name, interner.union(vec![string_fn, TypeId::NUMBER]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_conditional_infer_function_rest_param_distributive() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_fn = interner.function(FunctionShape {
params: vec![ParamInfo {
name: None,
type_id: infer_r,
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 cond = ConditionalType {
check_type: t_param,
extends_type: extends_fn,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let string_fn = interner.function(FunctionShape {
params: vec![ParamInfo {
name: None,
type_id: interner.array(TypeId::STRING),
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 number_fn = interner.function(FunctionShape {
params: vec![ParamInfo {
name: None,
type_id: interner.array(TypeId::NUMBER),
optional: false,
rest: true,
}],
this_type: None,
return_type: TypeId::VOID,
type_params: Vec::new(),
type_predicate: None,
is_constructor: false,
is_method: false,
});
subst.insert(t_name, interner.union(vec![string_fn, number_fn]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![
interner.array(TypeId::STRING),
interner.array(TypeId::NUMBER),
]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_function_rest_param_non_distributive_union_input() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_fn = interner.function(FunctionShape {
params: vec![ParamInfo {
name: None,
type_id: infer_r,
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 cond = ConditionalType {
check_type: interner.tuple(vec![TupleElement {
type_id: t_param,
name: None,
optional: false,
rest: false,
}]),
extends_type: interner.tuple(vec![TupleElement {
type_id: extends_fn,
name: None,
optional: false,
rest: false,
}]),
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let string_fn = interner.function(FunctionShape {
params: vec![ParamInfo {
name: None,
type_id: interner.array(TypeId::STRING),
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 number_fn = interner.function(FunctionShape {
params: vec![ParamInfo {
name: None,
type_id: interner.array(TypeId::NUMBER),
optional: false,
rest: true,
}],
this_type: None,
return_type: TypeId::VOID,
type_params: Vec::new(),
type_predicate: None,
is_constructor: false,
is_method: false,
});
subst.insert(t_name, interner.union(vec![string_fn, number_fn]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![
interner.array(TypeId::STRING),
interner.array(TypeId::NUMBER),
]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_function_rest_param_non_distributive_union_branch() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_fn = interner.function(FunctionShape {
params: vec![ParamInfo {
name: None,
type_id: infer_r,
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 cond = ConditionalType {
check_type: interner.tuple(vec![TupleElement {
type_id: t_param,
name: None,
optional: false,
rest: false,
}]),
extends_type: interner.tuple(vec![TupleElement {
type_id: extends_fn,
name: None,
optional: false,
rest: false,
}]),
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let string_fn = interner.function(FunctionShape {
params: vec![ParamInfo {
name: None,
type_id: interner.array(TypeId::STRING),
optional: false,
rest: true,
}],
this_type: None,
return_type: TypeId::VOID,
type_params: Vec::new(),
type_predicate: None,
is_constructor: false,
is_method: false,
});
subst.insert(t_name, interner.union(vec![string_fn, TypeId::NUMBER]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_conditional_infer_function_this_param_distributive() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_fn = interner.function(FunctionShape {
params: Vec::new(),
this_type: Some(infer_r),
return_type: TypeId::VOID,
type_params: Vec::new(),
type_predicate: None,
is_constructor: false,
is_method: false,
});
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_fn,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let string_fn = interner.function(FunctionShape {
params: Vec::new(),
this_type: Some(TypeId::STRING),
return_type: TypeId::VOID,
type_params: Vec::new(),
type_predicate: None,
is_constructor: false,
is_method: false,
});
let number_fn = interner.function(FunctionShape {
params: Vec::new(),
this_type: Some(TypeId::NUMBER),
return_type: TypeId::VOID,
type_params: Vec::new(),
type_predicate: None,
is_constructor: false,
is_method: false,
});
subst.insert(t_name, interner.union(vec![string_fn, number_fn]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_function_this_param_non_distributive_union_input() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_fn = interner.function(FunctionShape {
params: Vec::new(),
this_type: Some(infer_r),
return_type: TypeId::VOID,
type_params: Vec::new(),
type_predicate: None,
is_constructor: false,
is_method: false,
});
let cond = ConditionalType {
check_type: interner.tuple(vec![TupleElement {
type_id: t_param,
name: None,
optional: false,
rest: false,
}]),
extends_type: interner.tuple(vec![TupleElement {
type_id: extends_fn,
name: None,
optional: false,
rest: false,
}]),
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let string_fn = interner.function(FunctionShape {
params: Vec::new(),
this_type: Some(TypeId::STRING),
return_type: TypeId::VOID,
type_params: Vec::new(),
type_predicate: None,
is_constructor: false,
is_method: false,
});
let number_fn = interner.function(FunctionShape {
params: Vec::new(),
this_type: Some(TypeId::NUMBER),
return_type: TypeId::VOID,
type_params: Vec::new(),
type_predicate: None,
is_constructor: false,
is_method: false,
});
subst.insert(t_name, interner.union(vec![string_fn, number_fn]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_function_this_param_non_distributive_union_branch() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_fn = interner.function(FunctionShape {
params: Vec::new(),
this_type: Some(infer_r),
return_type: TypeId::VOID,
type_params: Vec::new(),
type_predicate: None,
is_constructor: false,
is_method: false,
});
let cond = ConditionalType {
check_type: interner.tuple(vec![TupleElement {
type_id: t_param,
name: None,
optional: false,
rest: false,
}]),
extends_type: interner.tuple(vec![TupleElement {
type_id: extends_fn,
name: None,
optional: false,
rest: false,
}]),
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let string_fn = interner.function(FunctionShape {
params: Vec::new(),
this_type: Some(TypeId::STRING),
return_type: TypeId::VOID,
type_params: Vec::new(),
type_predicate: None,
is_constructor: false,
is_method: false,
});
subst.insert(t_name, interner.union(vec![string_fn, TypeId::NUMBER]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_conditional_infer_function_return_distributive() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_fn = interner.function(FunctionShape {
params: Vec::new(),
this_type: None,
return_type: infer_r,
type_params: Vec::new(),
type_predicate: None,
is_constructor: false,
is_method: false,
});
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_fn,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let string_fn = 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_fn = interner.function(FunctionShape {
params: Vec::new(),
this_type: None,
return_type: TypeId::NUMBER,
type_params: Vec::new(),
type_predicate: None,
is_constructor: false,
is_method: false,
});
subst.insert(t_name, interner.union(vec![string_fn, number_fn]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_function_return_non_distributive_union_input() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_fn = interner.function(FunctionShape {
params: Vec::new(),
this_type: None,
return_type: infer_r,
type_params: Vec::new(),
type_predicate: None,
is_constructor: false,
is_method: false,
});
let cond = ConditionalType {
check_type: interner.tuple(vec![TupleElement {
type_id: t_param,
name: None,
optional: false,
rest: false,
}]),
extends_type: interner.tuple(vec![TupleElement {
type_id: extends_fn,
name: None,
optional: false,
rest: false,
}]),
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let string_fn = 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_fn = interner.function(FunctionShape {
params: Vec::new(),
this_type: None,
return_type: TypeId::NUMBER,
type_params: Vec::new(),
type_predicate: None,
is_constructor: false,
is_method: false,
});
subst.insert(t_name, interner.union(vec![string_fn, number_fn]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_function_param_and_return_distributive() {
let interner = TypeInterner::new();
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 p_name = interner.intern_string("P");
let infer_p = interner.intern(TypeData::Infer(TypeParamInfo {
name: p_name,
constraint: None,
default: None,
is_const: false,
}));
let r_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: r_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_fn = interner.function(FunctionShape {
params: vec![ParamInfo::unnamed(infer_p)],
this_type: None,
return_type: infer_r,
type_params: Vec::new(),
type_predicate: None,
is_constructor: false,
is_method: false,
});
let true_tuple = interner.tuple(vec![
TupleElement {
type_id: infer_p,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: infer_r,
name: None,
optional: false,
rest: false,
},
]);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_fn,
true_type: true_tuple,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let string_number_fn = interner.function(FunctionShape {
params: vec![ParamInfo::unnamed(TypeId::STRING)],
this_type: None,
return_type: TypeId::NUMBER,
type_params: Vec::new(),
type_predicate: None,
is_constructor: false,
is_method: false,
});
let boolean_string_fn = interner.function(FunctionShape {
params: vec![ParamInfo::unnamed(TypeId::BOOLEAN)],
this_type: None,
return_type: TypeId::STRING,
type_params: Vec::new(),
type_predicate: None,
is_constructor: false,
is_method: false,
});
subst.insert(
t_name,
interner.union(vec![string_number_fn, boolean_string_fn]),
);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
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,
},
]);
let tuple_boolean_string = 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,
},
]);
let expected = interner.union(vec![tuple_string_number, tuple_boolean_string]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_function_return_non_distributive_union_branch() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_fn = interner.function(FunctionShape {
params: Vec::new(),
this_type: None,
return_type: infer_r,
type_params: Vec::new(),
type_predicate: None,
is_constructor: false,
is_method: false,
});
let cond = ConditionalType {
check_type: interner.tuple(vec![TupleElement {
type_id: t_param,
name: None,
optional: false,
rest: false,
}]),
extends_type: interner.tuple(vec![TupleElement {
type_id: extends_fn,
name: None,
optional: false,
rest: false,
}]),
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let string_fn = 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,
});
subst.insert(t_name, interner.union(vec![string_fn, TypeId::NUMBER]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_conditional_infer_function_param_and_return_non_distributive_union_input() {
let interner = TypeInterner::new();
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 p_name = interner.intern_string("P");
let infer_p = interner.intern(TypeData::Infer(TypeParamInfo {
name: p_name,
constraint: None,
default: None,
is_const: false,
}));
let r_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: r_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_fn = interner.function(FunctionShape {
params: vec![ParamInfo::unnamed(infer_p)],
this_type: None,
return_type: infer_r,
type_params: Vec::new(),
type_predicate: None,
is_constructor: false,
is_method: false,
});
let true_tuple = interner.tuple(vec![
TupleElement {
type_id: infer_p,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: infer_r,
name: None,
optional: false,
rest: false,
},
]);
let cond = ConditionalType {
check_type: interner.tuple(vec![TupleElement {
type_id: t_param,
name: None,
optional: false,
rest: false,
}]),
extends_type: interner.tuple(vec![TupleElement {
type_id: extends_fn,
name: None,
optional: false,
rest: false,
}]),
true_type: true_tuple,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let string_number_fn = interner.function(FunctionShape {
params: vec![ParamInfo::unnamed(TypeId::STRING)],
this_type: None,
return_type: TypeId::NUMBER,
type_params: Vec::new(),
type_predicate: None,
is_constructor: false,
is_method: false,
});
let boolean_string_fn = interner.function(FunctionShape {
params: vec![ParamInfo::unnamed(TypeId::BOOLEAN)],
this_type: None,
return_type: TypeId::STRING,
type_params: Vec::new(),
type_predicate: None,
is_constructor: false,
is_method: false,
});
subst.insert(
t_name,
interner.union(vec![string_number_fn, boolean_string_fn]),
);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let param_union = interner.union(vec![TypeId::STRING, TypeId::BOOLEAN]);
let return_union = interner.union(vec![TypeId::NUMBER, TypeId::STRING]);
let expected = interner.tuple(vec![
TupleElement {
type_id: param_union,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: return_union,
name: None,
optional: false,
rest: false,
},
]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_object_call_signature_distributive() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_callable = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![CallSignature {
params: vec![ParamInfo::unnamed(infer_r)],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
type_params: Vec::new(),
is_method: false,
}],
construct_signatures: Vec::new(),
properties: Vec::new(),
string_index: None,
number_index: None,
});
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_callable,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let string_callable = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![CallSignature {
params: vec![ParamInfo::unnamed(TypeId::STRING)],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
type_params: Vec::new(),
is_method: false,
}],
construct_signatures: Vec::new(),
properties: Vec::new(),
string_index: None,
number_index: None,
});
let number_callable = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![CallSignature {
params: vec![ParamInfo::unnamed(TypeId::NUMBER)],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
type_params: Vec::new(),
is_method: false,
}],
construct_signatures: Vec::new(),
properties: Vec::new(),
string_index: None,
number_index: None,
});
subst.insert(
t_name,
interner.union(vec![string_callable, number_callable]),
);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_call_signature_param_from_function_distributive() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_callable = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![CallSignature {
params: vec![ParamInfo::unnamed(infer_r)],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
type_params: Vec::new(),
is_method: false,
}],
construct_signatures: Vec::new(),
properties: Vec::new(),
string_index: None,
number_index: None,
});
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_callable,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let string_fn = 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,
});
let number_fn = 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,
});
subst.insert(t_name, interner.union(vec![string_fn, number_fn]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_call_signature_return_from_function_distributive() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_callable = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![CallSignature {
params: Vec::new(),
this_type: None,
return_type: infer_r,
type_predicate: None,
type_params: Vec::new(),
is_method: false,
}],
construct_signatures: Vec::new(),
properties: Vec::new(),
string_index: None,
number_index: None,
});
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_callable,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let string_fn = 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_fn = interner.function(FunctionShape {
params: Vec::new(),
this_type: None,
return_type: TypeId::NUMBER,
type_params: Vec::new(),
type_predicate: None,
is_constructor: false,
is_method: false,
});
subst.insert(t_name, interner.union(vec![string_fn, number_fn]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_object_call_signature_non_distributive_union_input() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_callable = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![CallSignature {
params: vec![ParamInfo::unnamed(infer_r)],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
type_params: Vec::new(),
is_method: false,
}],
construct_signatures: Vec::new(),
properties: Vec::new(),
string_index: None,
number_index: None,
});
let cond = ConditionalType {
check_type: interner.tuple(vec![TupleElement {
type_id: t_param,
name: None,
optional: false,
rest: false,
}]),
extends_type: interner.tuple(vec![TupleElement {
type_id: extends_callable,
name: None,
optional: false,
rest: false,
}]),
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let string_callable = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![CallSignature {
params: vec![ParamInfo::unnamed(TypeId::STRING)],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
type_params: Vec::new(),
is_method: false,
}],
construct_signatures: Vec::new(),
properties: Vec::new(),
string_index: None,
number_index: None,
});
let number_callable = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![CallSignature {
params: vec![ParamInfo::unnamed(TypeId::NUMBER)],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
type_params: Vec::new(),
is_method: false,
}],
construct_signatures: Vec::new(),
properties: Vec::new(),
string_index: None,
number_index: None,
});
subst.insert(
t_name,
interner.union(vec![string_callable, number_callable]),
);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_object_call_signature_optional_param_distributive() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_callable = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![CallSignature {
params: vec![ParamInfo {
name: None,
type_id: infer_r,
optional: true,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
type_params: Vec::new(),
is_method: false,
}],
construct_signatures: Vec::new(),
properties: Vec::new(),
string_index: None,
number_index: None,
});
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_callable,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let string_callable = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![CallSignature {
params: vec![ParamInfo {
name: None,
type_id: TypeId::STRING,
optional: true,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
type_params: Vec::new(),
is_method: false,
}],
construct_signatures: Vec::new(),
properties: Vec::new(),
string_index: None,
number_index: None,
});
let number_callable = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![CallSignature {
params: vec![ParamInfo {
name: None,
type_id: TypeId::NUMBER,
optional: true,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
type_params: Vec::new(),
is_method: false,
}],
construct_signatures: Vec::new(),
properties: Vec::new(),
string_index: None,
number_index: None,
});
subst.insert(
t_name,
interner.union(vec![string_callable, number_callable]),
);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER, TypeId::UNDEFINED]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_object_call_signature_optional_param_non_distributive_union_input() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_callable = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![CallSignature {
params: vec![ParamInfo {
name: None,
type_id: infer_r,
optional: true,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
type_params: Vec::new(),
is_method: false,
}],
construct_signatures: Vec::new(),
properties: Vec::new(),
string_index: None,
number_index: None,
});
let cond = ConditionalType {
check_type: interner.tuple(vec![TupleElement {
type_id: t_param,
name: None,
optional: false,
rest: false,
}]),
extends_type: interner.tuple(vec![TupleElement {
type_id: extends_callable,
name: None,
optional: false,
rest: false,
}]),
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let string_callable = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![CallSignature {
params: vec![ParamInfo {
name: None,
type_id: TypeId::STRING,
optional: true,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
type_params: Vec::new(),
is_method: false,
}],
construct_signatures: Vec::new(),
properties: Vec::new(),
string_index: None,
number_index: None,
});
let number_callable = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![CallSignature {
params: vec![ParamInfo {
name: None,
type_id: TypeId::NUMBER,
optional: true,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
type_params: Vec::new(),
is_method: false,
}],
construct_signatures: Vec::new(),
properties: Vec::new(),
string_index: None,
number_index: None,
});
subst.insert(
t_name,
interner.union(vec![string_callable, number_callable]),
);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER, TypeId::UNDEFINED]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_object_call_signature_rest_param_distributive() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_callable = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![CallSignature {
params: vec![ParamInfo {
name: None,
type_id: infer_r,
optional: false,
rest: true,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
type_params: Vec::new(),
is_method: false,
}],
construct_signatures: Vec::new(),
properties: Vec::new(),
string_index: None,
number_index: None,
});
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_callable,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let string_callable = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![CallSignature {
params: vec![ParamInfo {
name: None,
type_id: interner.array(TypeId::STRING),
optional: false,
rest: true,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
type_params: Vec::new(),
is_method: false,
}],
construct_signatures: Vec::new(),
properties: Vec::new(),
string_index: None,
number_index: None,
});
let number_callable = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![CallSignature {
params: vec![ParamInfo {
name: None,
type_id: interner.array(TypeId::NUMBER),
optional: false,
rest: true,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
type_params: Vec::new(),
is_method: false,
}],
construct_signatures: Vec::new(),
properties: Vec::new(),
string_index: None,
number_index: None,
});
subst.insert(
t_name,
interner.union(vec![string_callable, number_callable]),
);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![
interner.array(TypeId::STRING),
interner.array(TypeId::NUMBER),
]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_object_call_signature_rest_param_non_distributive_union_input() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_callable = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![CallSignature {
params: vec![ParamInfo {
name: None,
type_id: infer_r,
optional: false,
rest: true,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
type_params: Vec::new(),
is_method: false,
}],
construct_signatures: Vec::new(),
properties: Vec::new(),
string_index: None,
number_index: None,
});
let cond = ConditionalType {
check_type: interner.tuple(vec![TupleElement {
type_id: t_param,
name: None,
optional: false,
rest: false,
}]),
extends_type: interner.tuple(vec![TupleElement {
type_id: extends_callable,
name: None,
optional: false,
rest: false,
}]),
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let string_callable = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![CallSignature {
params: vec![ParamInfo {
name: None,
type_id: interner.array(TypeId::STRING),
optional: false,
rest: true,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
type_params: Vec::new(),
is_method: false,
}],
construct_signatures: Vec::new(),
properties: Vec::new(),
string_index: None,
number_index: None,
});
let number_callable = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![CallSignature {
params: vec![ParamInfo {
name: None,
type_id: interner.array(TypeId::NUMBER),
optional: false,
rest: true,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
type_params: Vec::new(),
is_method: false,
}],
construct_signatures: Vec::new(),
properties: Vec::new(),
string_index: None,
number_index: None,
});
subst.insert(
t_name,
interner.union(vec![string_callable, number_callable]),
);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![
interner.array(TypeId::STRING),
interner.array(TypeId::NUMBER),
]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_object_call_signature_non_callable_union_branch() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_callable = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![CallSignature {
params: vec![ParamInfo::unnamed(infer_r)],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
type_params: Vec::new(),
is_method: false,
}],
construct_signatures: Vec::new(),
properties: Vec::new(),
string_index: None,
number_index: None,
});
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_callable,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let string_callable = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![CallSignature {
params: vec![ParamInfo::unnamed(TypeId::STRING)],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
type_params: Vec::new(),
is_method: false,
}],
construct_signatures: Vec::new(),
properties: Vec::new(),
string_index: None,
number_index: None,
});
subst.insert(
t_name,
interner.union(vec![string_callable, TypeId::NUMBER]),
);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::STRING);
}
#[test]
fn test_conditional_infer_object_call_signature_non_distributive_union_branch() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_callable = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![CallSignature {
params: vec![ParamInfo::unnamed(infer_r)],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
type_params: Vec::new(),
is_method: false,
}],
construct_signatures: Vec::new(),
properties: Vec::new(),
string_index: None,
number_index: None,
});
let cond = ConditionalType {
check_type: interner.tuple(vec![TupleElement {
type_id: t_param,
name: None,
optional: false,
rest: false,
}]),
extends_type: interner.tuple(vec![TupleElement {
type_id: extends_callable,
name: None,
optional: false,
rest: false,
}]),
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let string_callable = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![CallSignature {
params: vec![ParamInfo::unnamed(TypeId::STRING)],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
type_params: Vec::new(),
is_method: false,
}],
construct_signatures: Vec::new(),
properties: Vec::new(),
string_index: None,
number_index: None,
});
subst.insert(
t_name,
interner.union(vec![string_callable, TypeId::NUMBER]),
);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_conditional_infer_object_call_signature_overload_source_non_distributive() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_callable = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![CallSignature {
params: vec![ParamInfo::unnamed(infer_r)],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
type_params: Vec::new(),
is_method: false,
}],
construct_signatures: Vec::new(),
properties: Vec::new(),
string_index: None,
number_index: None,
});
let cond = ConditionalType {
check_type: interner.tuple(vec![TupleElement {
type_id: t_param,
name: None,
optional: false,
rest: false,
}]),
extends_type: interner.tuple(vec![TupleElement {
type_id: extends_callable,
name: None,
optional: false,
rest: false,
}]),
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let overload_callable = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![
CallSignature {
params: vec![ParamInfo::unnamed(TypeId::STRING)],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
type_params: Vec::new(),
is_method: false,
},
CallSignature {
params: vec![ParamInfo::unnamed(TypeId::NUMBER)],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
type_params: Vec::new(),
is_method: false,
},
],
construct_signatures: Vec::new(),
properties: Vec::new(),
string_index: None,
number_index: None,
});
subst.insert(t_name, overload_callable);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::NUMBER);
}
#[test]
fn test_conditional_infer_object_property_non_distributive_union_all_match() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_obj = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
infer_r,
)]);
let cond = ConditionalType {
check_type: interner.tuple(vec![TupleElement {
type_id: t_param,
name: None,
optional: false,
rest: false,
}]),
extends_type: interner.tuple(vec![TupleElement {
type_id: extends_obj,
name: None,
optional: false,
rest: false,
}]),
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let obj_string = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
TypeId::STRING,
)]);
let obj_number = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
TypeId::NUMBER,
)]);
subst.insert(t_name, interner.union(vec![obj_string, obj_number]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_object_property_non_distributive_union_branch() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_obj = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
infer_r,
)]);
let cond = ConditionalType {
check_type: interner.tuple(vec![TupleElement {
type_id: t_param,
name: None,
optional: false,
rest: false,
}]),
extends_type: interner.tuple(vec![TupleElement {
type_id: extends_obj,
name: None,
optional: false,
rest: false,
}]),
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let obj_match = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
TypeId::STRING,
)]);
subst.insert(t_name, interner.union(vec![obj_match, TypeId::NUMBER]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_conditional_infer_tuple_element_extraction() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_tuple = interner.tuple(vec![TupleElement {
type_id: infer_r,
name: None,
optional: false,
rest: false,
}]);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_tuple,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
subst.insert(
t_name,
interner.union(vec![
interner.tuple(vec![TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
}]),
interner.tuple(vec![TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
}]),
]),
);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_tuple_optional_element_distributive() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_tuple = interner.tuple(vec![TupleElement {
type_id: infer_r,
name: None,
optional: true,
rest: false,
}]);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_tuple,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let string_tuple = interner.tuple(vec![TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
}]);
let empty_tuple = interner.tuple(Vec::new());
subst.insert(t_name, interner.union(vec![string_tuple, empty_tuple]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![TypeId::STRING, TypeId::UNDEFINED]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_tuple_optional_element_non_distributive_union_input() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_tuple = interner.tuple(vec![TupleElement {
type_id: infer_r,
name: None,
optional: true,
rest: false,
}]);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_tuple,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let string_tuple = interner.tuple(vec![TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
}]);
let empty_tuple = interner.tuple(Vec::new());
subst.insert(t_name, interner.union(vec![string_tuple, empty_tuple]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![TypeId::STRING, TypeId::UNDEFINED]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_tuple_optional_element_non_distributive_union_branch() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_tuple = interner.tuple(vec![TupleElement {
type_id: infer_r,
name: None,
optional: true,
rest: false,
}]);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_tuple,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let string_tuple = interner.tuple(vec![TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
}]);
subst.insert(t_name, interner.union(vec![string_tuple, TypeId::NUMBER]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_conditional_infer_tuple_element_non_distributive_union_input() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_tuple = interner.tuple(vec![TupleElement {
type_id: infer_r,
name: None,
optional: false,
rest: false,
}]);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_tuple,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
subst.insert(
t_name,
interner.union(vec![
interner.tuple(vec![TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
}]),
interner.tuple(vec![TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
}]),
]),
);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_tuple_element_non_distributive_union_branch() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_tuple = interner.tuple(vec![TupleElement {
type_id: infer_r,
name: None,
optional: false,
rest: false,
}]);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_tuple,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let tuple_string = interner.tuple(vec![TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
}]);
subst.insert(t_name, interner.union(vec![tuple_string, TypeId::NUMBER]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_conditional_infer_tuple_element_non_tuple_union_branch() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_tuple = interner.tuple(vec![TupleElement {
type_id: infer_r,
name: None,
optional: false,
rest: false,
}]);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_tuple,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let tuple_string = interner.tuple(vec![TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
}]);
subst.insert(t_name, interner.union(vec![tuple_string, TypeId::NUMBER]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::STRING);
}
#[test]
fn test_conditional_infer_tuple_element_with_constraint() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: Some(TypeId::STRING),
default: None,
is_const: false,
}));
let extends_tuple = interner.tuple(vec![TupleElement {
type_id: infer_r,
name: None,
optional: false,
rest: false,
}]);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_tuple,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
subst.insert(
t_name,
interner.union(vec![
interner.tuple(vec![TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
}]),
interner.tuple(vec![TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
}]),
]),
);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::STRING);
}
#[test]
fn test_conditional_infer_optional_tuple_element_with_constraint() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: Some(TypeId::STRING),
default: None,
is_const: false,
}));
let extends_tuple = interner.tuple(vec![TupleElement {
type_id: infer_r,
name: None,
optional: true,
rest: false,
}]);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_tuple,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
subst.insert(
t_name,
interner.union(vec![
interner.tuple(vec![TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: true,
rest: false,
}]),
interner.tuple(vec![TupleElement {
type_id: TypeId::STRING,
name: None,
optional: true,
rest: false,
}]),
]),
);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::STRING);
}
#[test]
fn test_conditional_infer_tuple_rest_distributive() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_tuple = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: infer_r,
name: None,
optional: false,
rest: true,
},
]);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_tuple,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
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,
},
]);
let tuple_string = interner.tuple(vec![TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
}]);
subst.insert(
t_name,
interner.union(vec![tuple_string_number, tuple_string]),
);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![
interner.tuple(vec![TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
}]),
interner.tuple(Vec::new()),
]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_tuple_rest_with_head_infer_distributive() {
let interner = TypeInterner::new();
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 infer_h_name = interner.intern_string("H");
let infer_h = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_h_name,
constraint: None,
default: None,
is_const: false,
}));
let infer_r_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_r_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_tuple = interner.tuple(vec![
TupleElement {
type_id: infer_h,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: infer_r,
name: None,
optional: false,
rest: true,
},
]);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_tuple,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
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,
},
]);
let tuple_boolean = interner.tuple(vec![TupleElement {
type_id: TypeId::BOOLEAN,
name: None,
optional: false,
rest: false,
}]);
subst.insert(
t_name,
interner.union(vec![tuple_string_number, tuple_boolean]),
);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![
interner.tuple(vec![TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
}]),
interner.tuple(Vec::new()),
]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_union_true_branch_distributive() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let cond = ConditionalType {
check_type: t_param,
extends_type: TypeId::STRING,
true_type: interner.union(vec![infer_r, TypeId::NUMBER]),
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
subst.insert(
t_name,
interner.union(vec![TypeId::STRING, TypeId::BOOLEAN]),
);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, interner.union(vec![infer_r, TypeId::NUMBER]));
}
#[test]
fn test_conditional_infer_union_false_branch_distributive() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let cond = ConditionalType {
check_type: t_param,
extends_type: TypeId::STRING,
true_type: TypeId::NEVER,
false_type: interner.union(vec![infer_r, TypeId::NUMBER]),
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
subst.insert(
t_name,
interner.union(vec![TypeId::STRING, TypeId::BOOLEAN]),
);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, interner.union(vec![infer_r, TypeId::NUMBER]));
}
#[test]
fn test_conditional_infer_any_check_type_distributive() {
let interner = TypeInterner::new();
let infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let cond = ConditionalType {
check_type: TypeId::ANY,
extends_type: TypeId::STRING,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: true,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, infer_r);
}
#[test]
fn test_conditional_infer_readonly_array_element_extraction() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_array = interner.intern(TypeData::ReadonlyType(interner.array(infer_r)));
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_array,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let readonly_string_array =
interner.intern(TypeData::ReadonlyType(interner.array(TypeId::STRING)));
let readonly_number_array =
interner.intern(TypeData::ReadonlyType(interner.array(TypeId::NUMBER)));
subst.insert(
t_name,
interner.union(vec![readonly_string_array, readonly_number_array]),
);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_readonly_array_element_non_distributive_union_input() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_array = interner.intern(TypeData::ReadonlyType(interner.array(infer_r)));
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_array,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let readonly_string_array =
interner.intern(TypeData::ReadonlyType(interner.array(TypeId::STRING)));
let readonly_number_array =
interner.intern(TypeData::ReadonlyType(interner.array(TypeId::NUMBER)));
subst.insert(
t_name,
interner.union(vec![readonly_string_array, readonly_number_array]),
);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_readonly_array_element_non_distributive_union_branch() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_array = interner.intern(TypeData::ReadonlyType(interner.array(infer_r)));
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_array,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let readonly_string_array =
interner.intern(TypeData::ReadonlyType(interner.array(TypeId::STRING)));
subst.insert(
t_name,
interner.union(vec![readonly_string_array, TypeId::NUMBER]),
);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_conditional_infer_readonly_array_element_non_array_union_branch() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_array = interner.intern(TypeData::ReadonlyType(interner.array(infer_r)));
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_array,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let readonly_string_array =
interner.intern(TypeData::ReadonlyType(interner.array(TypeId::STRING)));
subst.insert(
t_name,
interner.union(vec![readonly_string_array, TypeId::NUMBER]),
);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::STRING);
}
#[test]
fn test_conditional_infer_readonly_tuple_element_extraction() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_tuple =
interner.intern(TypeData::ReadonlyType(interner.tuple(vec![TupleElement {
type_id: infer_r,
name: None,
optional: false,
rest: false,
}])));
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_tuple,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let readonly_string_tuple =
interner.intern(TypeData::ReadonlyType(interner.tuple(vec![TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
}])));
let readonly_number_tuple =
interner.intern(TypeData::ReadonlyType(interner.tuple(vec![TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
}])));
subst.insert(
t_name,
interner.union(vec![readonly_string_tuple, readonly_number_tuple]),
);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_readonly_tuple_element_non_distributive_union_input() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_tuple =
interner.intern(TypeData::ReadonlyType(interner.tuple(vec![TupleElement {
type_id: infer_r,
name: None,
optional: false,
rest: false,
}])));
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_tuple,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let readonly_string_tuple =
interner.intern(TypeData::ReadonlyType(interner.tuple(vec![TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
}])));
let readonly_number_tuple =
interner.intern(TypeData::ReadonlyType(interner.tuple(vec![TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
}])));
subst.insert(
t_name,
interner.union(vec![readonly_string_tuple, readonly_number_tuple]),
);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_readonly_tuple_element_non_distributive_union_branch() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_tuple =
interner.intern(TypeData::ReadonlyType(interner.tuple(vec![TupleElement {
type_id: infer_r,
name: None,
optional: false,
rest: false,
}])));
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_tuple,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let readonly_string_tuple =
interner.intern(TypeData::ReadonlyType(interner.tuple(vec![TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
}])));
subst.insert(
t_name,
interner.union(vec![readonly_string_tuple, TypeId::NUMBER]),
);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_conditional_infer_readonly_tuple_element_non_tuple_union_branch() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_tuple =
interner.intern(TypeData::ReadonlyType(interner.tuple(vec![TupleElement {
type_id: infer_r,
name: None,
optional: false,
rest: false,
}])));
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_tuple,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let readonly_string_tuple =
interner.intern(TypeData::ReadonlyType(interner.tuple(vec![TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
}])));
subst.insert(
t_name,
interner.union(vec![readonly_string_tuple, TypeId::NUMBER]),
);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::STRING);
}
#[test]
fn test_conditional_infer_readonly_array_mixed_input() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_array = interner.intern(TypeData::ReadonlyType(interner.array(infer_r)));
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_array,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let readonly_string_array =
interner.intern(TypeData::ReadonlyType(interner.array(TypeId::STRING)));
let number_array = interner.array(TypeId::NUMBER);
subst.insert(
t_name,
interner.union(vec![readonly_string_array, number_array]),
);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_instantiated_param_tuple_wrapper_no_distribution() {
let interner = TypeInterner::new();
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 string_or_number = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let lit_true = interner.literal_boolean(true);
let lit_false = interner.literal_boolean(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 cond = ConditionalType {
check_type: tuple_check,
extends_type: tuple_extends,
true_type: lit_true,
false_type: lit_false,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
subst.insert(t_name, string_or_number);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, lit_false);
}
#[test]
fn test_conditional_any_produces_union() {
let interner = TypeInterner::new();
let cond = ConditionalType {
check_type: TypeId::ANY,
extends_type: TypeId::STRING,
true_type: TypeId::NUMBER,
false_type: TypeId::BOOLEAN,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
let expected = interner.union(vec![TypeId::NUMBER, TypeId::BOOLEAN]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_any_error_poisoning() {
let interner = TypeInterner::new();
let cond = ConditionalType {
check_type: TypeId::ANY,
extends_type: TypeId::STRING,
true_type: TypeId::ERROR,
false_type: TypeId::NUMBER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, TypeId::ERROR);
}
#[test]
fn test_conditional_distributive_never() {
let interner = TypeInterner::new();
let cond = ConditionalType {
check_type: TypeId::NEVER,
extends_type: TypeId::STRING,
true_type: TypeId::NUMBER,
false_type: TypeId::BOOLEAN,
is_distributive: true,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_conditional_deferred_type_parameter() {
let interner = TypeInterner::new();
let type_param_t = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: interner.intern_string("T"),
constraint: None,
default: None,
is_const: false,
}));
let cond = ConditionalType {
check_type: type_param_t,
extends_type: TypeId::STRING,
true_type: TypeId::NUMBER,
false_type: TypeId::BOOLEAN,
is_distributive: true,
};
let cond_type = interner.conditional(cond.clone());
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, cond_type);
}
#[test]
fn test_conditional_infer_direct_match() {
let interner = TypeInterner::new();
let r_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: r_name,
constraint: None,
default: None,
is_const: false,
}));
let cond = ConditionalType {
check_type: TypeId::STRING,
extends_type: infer_r,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, TypeId::STRING);
}
#[test]
fn test_conditional_infer_constraint_mismatch() {
let interner = TypeInterner::new();
let r_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: r_name,
constraint: Some(TypeId::NUMBER),
default: None,
is_const: false,
}));
let no = interner.literal_string("no");
let cond = ConditionalType {
check_type: TypeId::STRING,
extends_type: infer_r,
true_type: infer_r,
false_type: no,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, no);
}
#[test]
fn test_conditional_distributive_infer_array_extends() {
let interner = TypeInterner::new();
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 r_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: r_name,
constraint: None,
default: None,
is_const: false,
}));
let cond = ConditionalType {
check_type: t_param,
extends_type: interner.array(infer_r),
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let string_array = interner.array(TypeId::STRING);
let number_array = interner.array(TypeId::NUMBER);
let union_arrays = interner.union(vec![string_array, number_array]);
let mut subst = TypeSubstitution::new();
subst.insert(t_name, union_arrays);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_nested_distributive_infer() {
let interner = TypeInterner::new();
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 r_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: r_name,
constraint: None,
default: None,
is_const: false,
}));
let yes = interner.literal_string("yes");
let no = interner.literal_string("no");
let outer_no = interner.literal_string("outer-no");
let inner_cond = interner.conditional(ConditionalType {
check_type: infer_r,
extends_type: TypeId::STRING,
true_type: yes,
false_type: no,
is_distributive: false,
});
let cond = ConditionalType {
check_type: t_param,
extends_type: infer_r,
true_type: inner_cond,
false_type: outer_no,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let union = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let mut subst = TypeSubstitution::new();
subst.insert(t_name, union);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![yes, no]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_object_property() {
let interner = TypeInterner::new();
let r_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: r_name,
constraint: None,
default: None,
is_const: false,
}));
let prop_name = interner.intern_string("a");
let source = interner.object(vec![PropertyInfo::new(prop_name, TypeId::STRING)]);
let pattern = interner.object(vec![PropertyInfo::new(prop_name, infer_r)]);
let cond = ConditionalType {
check_type: source,
extends_type: pattern,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, TypeId::STRING);
}
#[test]
fn test_conditional_infer_object_string_index_signature() {
let interner = TypeInterner::new();
let r_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: r_name,
constraint: None,
default: None,
is_const: false,
}));
let source = 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 pattern = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: Vec::new(),
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: infer_r,
readonly: false,
}),
number_index: None,
});
let cond = ConditionalType {
check_type: source,
extends_type: pattern,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, TypeId::NUMBER);
}
#[test]
fn test_index_access_object_literal() {
let interner = TypeInterner::new();
let obj = interner.object(vec![
PropertyInfo::new(interner.intern_string("x"), TypeId::NUMBER),
PropertyInfo::new(interner.intern_string("y"), TypeId::STRING),
]);
let key_x = interner.literal_string("x");
let result = evaluate_index_access(&interner, obj, key_x);
assert_eq!(result, TypeId::NUMBER);
}
#[test]
fn test_index_access_object_string_key() {
let interner = TypeInterner::new();
let obj = interner.object(vec![
PropertyInfo::new(interner.intern_string("x"), TypeId::NUMBER),
PropertyInfo::new(interner.intern_string("y"), TypeId::STRING),
]);
let key_y = interner.literal_string("y");
let result = evaluate_index_access(&interner, obj, key_y);
assert_eq!(result, TypeId::STRING);
}
#[test]
fn test_index_access_object_string_index_optional_properties() {
let interner = TypeInterner::new();
let obj = interner.object(vec![
PropertyInfo::opt(interner.intern_string("x"), TypeId::NUMBER),
PropertyInfo::new(interner.intern_string("y"), TypeId::STRING),
]);
let result = evaluate_index_access(&interner, obj, TypeId::STRING);
let expected = interner.union(vec![TypeId::NUMBER, TypeId::STRING, TypeId::UNDEFINED]);
assert_eq!(result, expected);
}
#[test]
fn test_index_access_object_missing_key() {
let interner = TypeInterner::new();
let obj = interner.object(vec![PropertyInfo::new(
interner.intern_string("x"),
TypeId::NUMBER,
)]);
let key_z = interner.literal_string("z");
let result = evaluate_index_access(&interner, obj, key_z);
assert_eq!(result, TypeId::UNDEFINED);
}
#[test]
fn test_index_access_object_union_key() {
let interner = TypeInterner::new();
let obj = interner.object(vec![
PropertyInfo::new(interner.intern_string("x"), TypeId::NUMBER),
PropertyInfo::new(interner.intern_string("y"), TypeId::STRING),
]);
let key_x = interner.literal_string("x");
let key_y = interner.literal_string("y");
let key_union = interner.union(vec![key_x, key_y]);
let result = evaluate_index_access(&interner, obj, key_union);
let expected = interner.union(vec![TypeId::NUMBER, TypeId::STRING]);
assert_eq!(result, expected);
}
#[test]
fn test_index_access_union_object_literal_key() {
let interner = TypeInterner::new();
let obj_a = interner.object(vec![PropertyInfo::new(
interner.intern_string("x"),
TypeId::NUMBER,
)]);
let obj_b = interner.object(vec![PropertyInfo::new(
interner.intern_string("y"),
TypeId::STRING,
)]);
let union_obj = interner.union(vec![obj_a, obj_b]);
let key_x = interner.literal_string("x");
let result = evaluate_index_access(&interner, union_obj, key_x);
assert_eq!(result, TypeId::NUMBER);
}
#[test]
fn test_index_access_union_object_union_key() {
let interner = TypeInterner::new();
let obj_a = interner.object(vec![PropertyInfo::new(
interner.intern_string("x"),
TypeId::NUMBER,
)]);
let obj_b = interner.object(vec![PropertyInfo::new(
interner.intern_string("y"),
TypeId::STRING,
)]);
let union_obj = interner.union(vec![obj_a, obj_b]);
let key_x = interner.literal_string("x");
let key_y = interner.literal_string("y");
let key_union = interner.union(vec![key_x, key_y]);
let result = evaluate_index_access(&interner, union_obj, key_union);
let expected = interner.union(vec![TypeId::NUMBER, TypeId::STRING]);
assert_eq!(result, expected);
}
#[test]
fn test_correlated_union_index_access_cross_product() {
let interner = TypeInterner::new();
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 result = evaluate_index_access(&interner, union_obj, key_union);
let expected = interner.union(vec![TypeId::NUMBER, TypeId::STRING]);
assert_eq!(result, expected);
}
#[test]
fn test_index_access_union_object_union_key_no_unchecked() {
let interner = TypeInterner::new();
let obj_a = interner.object(vec![PropertyInfo::new(
interner.intern_string("x"),
TypeId::NUMBER,
)]);
let obj_b = interner.object(vec![PropertyInfo::new(
interner.intern_string("y"),
TypeId::STRING,
)]);
let union_obj = interner.union(vec![obj_a, obj_b]);
let key_x = interner.literal_string("x");
let key_y = interner.literal_string("y");
let key_union = interner.union(vec![key_x, key_y]);
let mut evaluator = TypeEvaluator::new(&interner);
evaluator.set_no_unchecked_indexed_access(true);
let result = evaluator.evaluate_index_access(union_obj, key_union);
let expected = interner.union(vec![TypeId::NUMBER, TypeId::STRING, TypeId::UNDEFINED]);
assert_eq!(result, expected);
}
#[test]
fn test_index_access_union_object_literal_key_no_unchecked() {
let interner = TypeInterner::new();
let obj_a = interner.object(vec![PropertyInfo::new(
interner.intern_string("x"),
TypeId::NUMBER,
)]);
let obj_b = interner.object(vec![PropertyInfo::new(
interner.intern_string("y"),
TypeId::STRING,
)]);
let union_obj = interner.union(vec![obj_a, obj_b]);
let key_x = interner.literal_string("x");
let mut evaluator = TypeEvaluator::new(&interner);
evaluator.set_no_unchecked_indexed_access(true);
let result = evaluator.evaluate_index_access(union_obj, key_x);
let expected = interner.union(vec![TypeId::NUMBER, TypeId::UNDEFINED]);
assert_eq!(result, expected);
}
#[test]
fn test_index_access_object_with_string_index_signature() {
let interner = TypeInterner::new();
let key_x = interner.intern_string("x");
let key_y = interner.literal_string("y");
let obj = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![PropertyInfo::new(key_x, TypeId::STRING)],
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: TypeId::NUMBER,
readonly: false,
}),
number_index: None,
});
let key_x_literal = interner.literal_string("x");
let result = evaluate_index_access(&interner, obj, key_x_literal);
assert_eq!(result, TypeId::STRING);
let result = evaluate_index_access(&interner, obj, key_y);
assert_eq!(result, TypeId::NUMBER);
let result = evaluate_index_access(&interner, obj, TypeId::STRING);
assert_eq!(result, TypeId::NUMBER);
let key_union = interner.union(vec![key_x_literal, key_y]);
let result = evaluate_index_access(&interner, obj, key_union);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert_eq!(result, expected);
}
#[test]
fn test_index_access_object_with_string_index_signature_optional_property() {
let interner = TypeInterner::new();
let key_x = interner.intern_string("x");
let key_y = interner.literal_string("y");
let obj = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![PropertyInfo::opt(key_x, TypeId::NUMBER)],
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: TypeId::BOOLEAN,
readonly: false,
}),
number_index: None,
});
let key_x_literal = interner.literal_string("x");
let result = evaluate_index_access(&interner, obj, key_x_literal);
let expected = interner.union(vec![TypeId::NUMBER, TypeId::UNDEFINED]);
assert_eq!(result, expected);
let result = evaluate_index_access(&interner, obj, key_y);
assert_eq!(result, TypeId::BOOLEAN);
let key_union = interner.union(vec![key_x_literal, key_y]);
let result = evaluate_index_access(&interner, obj, key_union);
let expected = interner.union(vec![TypeId::NUMBER, TypeId::UNDEFINED, TypeId::BOOLEAN]);
assert_eq!(result, expected);
}
#[test]
fn test_index_access_object_with_string_index_signature_optional_property_no_unchecked() {
let interner = TypeInterner::new();
let obj = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![PropertyInfo::opt(
interner.intern_string("x"),
TypeId::NUMBER,
)],
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: TypeId::BOOLEAN,
readonly: false,
}),
number_index: None,
});
let mut evaluator = TypeEvaluator::new(&interner);
evaluator.set_no_unchecked_indexed_access(true);
let key_x = interner.literal_string("x");
let result = evaluator.evaluate_index_access(obj, key_x);
let expected = interner.union(vec![TypeId::NUMBER, TypeId::UNDEFINED]);
assert_eq!(result, expected);
let key_y = interner.literal_string("y");
let result = evaluator.evaluate_index_access(obj, key_y);
let expected = interner.union(vec![TypeId::BOOLEAN, TypeId::UNDEFINED]);
assert_eq!(result, expected);
let result = evaluator.evaluate_index_access(obj, TypeId::STRING);
let expected = interner.union(vec![TypeId::BOOLEAN, TypeId::UNDEFINED]);
assert_eq!(result, expected);
let key_union = interner.union(vec![key_x, key_y]);
let result = evaluator.evaluate_index_access(obj, key_union);
let expected = interner.union(vec![TypeId::NUMBER, TypeId::BOOLEAN, TypeId::UNDEFINED]);
assert_eq!(result, expected);
}
#[test]
fn test_no_unchecked_object_index_signature_evaluate() {
let interner = TypeInterner::new();
let obj = 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 mut evaluator = TypeEvaluator::new(&interner);
evaluator.set_no_unchecked_indexed_access(true);
let result = evaluator.evaluate_index_access(obj, TypeId::NUMBER);
let expected = interner.union(vec![TypeId::NUMBER, TypeId::UNDEFINED]);
assert_eq!(result, expected);
}
#[test]
fn test_index_access_object_with_number_index_signature() {
let interner = TypeInterner::new();
let obj = 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 result = evaluate_index_access(&interner, obj, TypeId::NUMBER);
assert_eq!(result, TypeId::BOOLEAN);
let one = interner.literal_number(1.0);
let result = evaluate_index_access(&interner, obj, one);
assert_eq!(result, TypeId::BOOLEAN);
}
#[test]
fn test_index_access_object_with_number_index_signature_no_unchecked() {
let interner = TypeInterner::new();
let obj = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: Vec::new(),
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: TypeId::BOOLEAN,
readonly: false,
}),
number_index: Some(IndexSignature {
key_type: TypeId::NUMBER,
value_type: TypeId::NUMBER,
readonly: false,
}),
});
let mut evaluator = TypeEvaluator::new(&interner);
evaluator.set_no_unchecked_indexed_access(true);
let result = evaluator.evaluate_index_access(obj, TypeId::NUMBER);
let expected = interner.union(vec![TypeId::NUMBER, TypeId::UNDEFINED]);
assert_eq!(result, expected);
let zero = interner.literal_number(0.0);
let result = evaluator.evaluate_index_access(obj, zero);
let expected = interner.union(vec![TypeId::NUMBER, TypeId::UNDEFINED]);
assert_eq!(result, expected);
let zero_str = interner.literal_string("0");
let result = evaluator.evaluate_index_access(obj, zero_str);
let expected = interner.union(vec![TypeId::NUMBER, TypeId::UNDEFINED]);
assert_eq!(result, expected);
}
#[test]
fn test_index_access_resolves_ref() {
use crate::TypeEnvironment;
use crate::def::DefId;
let interner = TypeInterner::new();
let mut env = TypeEnvironment::new();
let obj = interner.object(vec![PropertyInfo::new(
interner.intern_string("x"),
TypeId::NUMBER,
)]);
let def_id = DefId(1);
env.insert_def(def_id, obj);
let ref_type = interner.lazy(def_id);
let key_x = interner.literal_string("x");
let mut evaluator = TypeEvaluator::with_resolver(&interner, &env);
let result = evaluator.evaluate_index_access(ref_type, key_x);
assert_eq!(result, TypeId::NUMBER);
}
#[test]
fn test_index_access_type_param_constraint() {
let interner = TypeInterner::new();
let constraint = interner.object(vec![PropertyInfo::new(
interner.intern_string("x"),
TypeId::NUMBER,
)]);
let type_param = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: interner.intern_string("T"),
constraint: Some(constraint),
default: None,
is_const: false,
}));
let key_x = interner.literal_string("x");
let result = evaluate_index_access(&interner, type_param, key_x);
assert_eq!(result, TypeId::NUMBER);
}
#[test]
fn test_index_access_type_param_no_constraint_deferred() {
let interner = TypeInterner::new();
let type_param = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: interner.intern_string("T"),
constraint: None,
default: None,
is_const: false,
}));
let key_x = interner.literal_string("x");
let result = evaluate_index_access(&interner, type_param, key_x);
match interner.lookup(result) {
Some(TypeData::IndexAccess(obj, idx)) => {
assert_eq!(obj, type_param);
assert_eq!(idx, key_x);
}
other => panic!("Expected deferred IndexAccess, got {other:?}"),
}
}
#[test]
fn test_index_access_optional_property() {
let interner = TypeInterner::new();
let obj = interner.object(vec![PropertyInfo::opt(
interner.intern_string("x"),
TypeId::NUMBER,
)]);
let key_x = interner.literal_string("x");
let result = evaluate_index_access(&interner, obj, key_x);
let expected = interner.union(vec![TypeId::NUMBER, TypeId::UNDEFINED]);
assert_eq!(result, expected);
}
#[test]
fn test_index_access_any_is_any() {
let interner = TypeInterner::new();
let result = evaluate_index_access(&interner, TypeId::ANY, TypeId::STRING);
assert_eq!(result, TypeId::ANY);
let result = evaluate_index_access(&interner, TypeId::NUMBER, TypeId::ANY);
assert_eq!(result, TypeId::ANY);
}
#[test]
fn test_index_access_with_no_unchecked_indexed_access() {
let interner = TypeInterner::new();
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 array = interner.array(TypeId::STRING);
let mut evaluator = TypeEvaluator::new(&interner);
evaluator.set_no_unchecked_indexed_access(true);
let result = evaluator.evaluate_index_access(indexed, TypeId::STRING);
let expected = interner.union(vec![TypeId::NUMBER, TypeId::UNDEFINED]);
assert_eq!(result, expected);
let result = evaluator.evaluate_index_access(array, TypeId::NUMBER);
let expected = interner.union(vec![TypeId::STRING, TypeId::UNDEFINED]);
assert_eq!(result, expected);
}
#[test]
fn test_index_access_with_options_helper_no_unchecked_indexed_access() {
let interner = TypeInterner::new();
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 result = evaluate_index_access_with_options(&interner, indexed, TypeId::STRING, true);
let expected = interner.union(vec![TypeId::NUMBER, TypeId::UNDEFINED]);
assert_eq!(result, expected);
}
#[test]
fn test_index_access_array_literal_with_no_unchecked_indexed_access() {
let interner = TypeInterner::new();
let array = interner.array(TypeId::STRING);
let zero = interner.literal_number(0.0);
let mut evaluator = TypeEvaluator::new(&interner);
evaluator.set_no_unchecked_indexed_access(true);
let result = evaluator.evaluate_index_access(array, zero);
let expected = interner.union(vec![TypeId::STRING, TypeId::UNDEFINED]);
assert_eq!(result, expected);
}
#[test]
fn test_index_access_array() {
let interner = TypeInterner::new();
let string_array = interner.array(TypeId::STRING);
let result = evaluate_index_access(&interner, string_array, TypeId::NUMBER);
assert_eq!(result, TypeId::STRING);
}
#[test]
fn test_no_unchecked_indexed_access_array_union_key() {
let interner = TypeInterner::new();
let string_array = interner.array(TypeId::STRING);
let length_key = interner.literal_string("length");
let key_union = interner.union(vec![TypeId::NUMBER, length_key]);
let mut evaluator = TypeEvaluator::new(&interner);
let result = evaluator.evaluate_index_access(string_array, key_union);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert_eq!(result, expected);
evaluator.set_no_unchecked_indexed_access(true);
let result = evaluator.evaluate_index_access(string_array, key_union);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER, TypeId::UNDEFINED]);
assert_eq!(result, expected);
}
#[test]
fn test_index_access_array_string_index() {
let interner = TypeInterner::new();
let string_array = interner.array(TypeId::STRING);
let includes_key = interner.literal_string("includes");
let includes_type = evaluate_index_access(&interner, string_array, includes_key);
let result = evaluate_index_access(&interner, string_array, TypeId::STRING);
let key = interner
.lookup(result)
.expect("expected union for array[string]");
match key {
TypeData::Union(members) => {
let members = interner.type_list(members);
assert!(members.contains(&TypeId::NUMBER));
assert!(members.contains(&includes_type));
assert!(!members.contains(&TypeId::STRING));
}
other => panic!("Expected union, got {other:?}"),
}
}
#[test]
fn test_index_access_array_string_index_with_no_unchecked_indexed_access() {
let interner = TypeInterner::new();
let string_array = interner.array(TypeId::STRING);
let mut evaluator = TypeEvaluator::new(&interner);
evaluator.set_no_unchecked_indexed_access(true);
let result = evaluator.evaluate_index_access(string_array, TypeId::STRING);
let key = interner
.lookup(result)
.expect("expected union for array[string]");
match key {
TypeData::Union(members) => {
let members = interner.type_list(members);
assert!(members.contains(&TypeId::UNDEFINED));
}
other => panic!("Expected union, got {other:?}"),
}
}
#[test]
fn test_index_access_array_string_literal_length() {
let interner = TypeInterner::new();
let string_array = interner.array(TypeId::STRING);
let length_key = interner.literal_string("length");
let result = evaluate_index_access(&interner, string_array, length_key);
assert_eq!(result, TypeId::NUMBER);
}
#[test]
fn test_index_access_array_string_literal_method() {
let interner = TypeInterner::new();
let string_array = interner.array(TypeId::STRING);
let includes_key = interner.literal_string("includes");
let result = evaluate_index_access(&interner, string_array, includes_key);
match interner.lookup(result) {
Some(TypeData::Function(func_id)) => {
let func = interner.function_shape(func_id);
assert_eq!(func.return_type, TypeId::BOOLEAN);
assert_eq!(func.params.len(), 1);
assert!(func.params[0].rest);
}
other => panic!("Expected function type, got {other:?}"),
}
}
#[test]
fn test_index_access_array_string_literal_numeric_key_with_no_unchecked_indexed_access() {
let interner = TypeInterner::new();
let string_array = interner.array(TypeId::STRING);
let zero = interner.literal_string("0");
let mut evaluator = TypeEvaluator::new(&interner);
evaluator.set_no_unchecked_indexed_access(true);
let result = evaluator.evaluate_index_access(string_array, zero);
let expected = interner.union(vec![TypeId::STRING, TypeId::UNDEFINED]);
assert_eq!(result, expected);
}
#[test]
fn test_index_access_readonly_array() {
let interner = TypeInterner::new();
let array = interner.array(TypeId::STRING);
let readonly_array = interner.intern(TypeData::ReadonlyType(array));
let result = evaluate_index_access(&interner, readonly_array, TypeId::NUMBER);
assert_eq!(result, TypeId::STRING);
}
#[test]
fn test_index_access_tuple_literal() {
let interner = TypeInterner::new();
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 zero = interner.literal_number(0.0);
let result = evaluate_index_access(&interner, tuple, zero);
assert_eq!(result, TypeId::STRING);
}
#[test]
fn test_index_access_tuple_rest_array_literal() {
let interner = TypeInterner::new();
let number_array = interner.array(TypeId::NUMBER);
let tuple = 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 one = interner.literal_number(1.0);
let two = interner.literal_number(2.0);
assert_eq!(evaluate_index_access(&interner, tuple, one), TypeId::NUMBER);
assert_eq!(evaluate_index_access(&interner, tuple, two), TypeId::NUMBER);
}
#[test]
fn test_index_access_tuple_rest_tuple_literal() {
let interner = TypeInterner::new();
let rest_tuple = 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,
},
]);
let tuple = 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,
},
]);
let one = interner.literal_number(1.0);
let two = interner.literal_number(2.0);
let three = interner.literal_number(3.0);
assert_eq!(evaluate_index_access(&interner, tuple, one), TypeId::NUMBER);
assert_eq!(
evaluate_index_access(&interner, tuple, two),
TypeId::BOOLEAN
);
assert_eq!(
evaluate_index_access(&interner, tuple, three),
TypeId::UNDEFINED
);
}
#[test]
fn test_index_access_tuple_optional_literal() {
let interner = TypeInterner::new();
let tuple = 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 one = interner.literal_number(1.0);
let result = evaluate_index_access(&interner, tuple, one);
let expected = interner.union(vec![TypeId::NUMBER, TypeId::UNDEFINED]);
assert_eq!(result, expected);
}
#[test]
fn test_index_access_tuple_negative_literal() {
let interner = TypeInterner::new();
let number_array = interner.array(TypeId::NUMBER);
let tuple = 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 negative = interner.literal_number(-1.0);
let result = evaluate_index_access(&interner, tuple, negative);
assert_eq!(result, TypeId::UNDEFINED);
}
#[test]
fn test_index_access_tuple_fractional_literal() {
let interner = TypeInterner::new();
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 fractional = interner.literal_number(1.5);
let result = evaluate_index_access(&interner, tuple, fractional);
assert_eq!(result, TypeId::UNDEFINED);
}
#[test]
fn test_index_access_tuple_negative_string_literal() {
let interner = TypeInterner::new();
let number_array = interner.array(TypeId::NUMBER);
let tuple = 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 negative = interner.literal_string("-1");
let result = evaluate_index_access(&interner, tuple, negative);
assert_eq!(result, TypeId::UNDEFINED);
}
#[test]
fn test_index_access_tuple_fractional_string_literal() {
let interner = TypeInterner::new();
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 fractional = interner.literal_string("1.5");
let result = evaluate_index_access(&interner, tuple, fractional);
assert_eq!(result, TypeId::UNDEFINED);
}
#[test]
fn test_index_access_tuple_string_index() {
let interner = TypeInterner::new();
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 map_key = interner.literal_string("map");
let map_type = evaluate_index_access(&interner, tuple, map_key);
let result = evaluate_index_access(&interner, tuple, TypeId::STRING);
let key = interner
.lookup(result)
.expect("expected union for tuple[string]");
match key {
TypeData::Union(members) => {
let members = interner.type_list(members);
assert!(members.contains(&TypeId::STRING));
assert!(members.contains(&TypeId::NUMBER));
assert!(members.contains(&map_type));
}
other => panic!("Expected union, got {other:?}"),
}
}
#[test]
fn test_index_access_tuple_string_index_with_no_unchecked_indexed_access() {
let interner = TypeInterner::new();
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 mut evaluator = TypeEvaluator::new(&interner);
evaluator.set_no_unchecked_indexed_access(true);
let result = evaluator.evaluate_index_access(tuple, TypeId::STRING);
let key = interner
.lookup(result)
.expect("expected union for tuple[string]");
match key {
TypeData::Union(members) => {
let members = interner.type_list(members);
assert!(members.contains(&TypeId::UNDEFINED));
}
other => panic!("Expected union, got {other:?}"),
}
}
#[test]
fn test_index_access_tuple_string_literal_length() {
let interner = TypeInterner::new();
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 length_key = interner.literal_string("length");
let result = evaluate_index_access(&interner, tuple, length_key);
assert_eq!(result, TypeId::NUMBER);
}
#[test]
fn test_index_access_tuple_string_literal_numeric_key() {
let interner = TypeInterner::new();
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 zero = interner.literal_string("0");
let result = evaluate_index_access(&interner, tuple, zero);
assert_eq!(result, TypeId::STRING);
}
#[test]
fn test_index_access_readonly_tuple_literal() {
let interner = TypeInterner::new();
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 readonly_tuple = interner.intern(TypeData::ReadonlyType(tuple));
let one = interner.literal_number(1.0);
let result = evaluate_index_access(&interner, readonly_tuple, one);
assert_eq!(result, TypeId::NUMBER);
}
#[test]
fn test_index_access_string_number() {
let interner = TypeInterner::new();
let result = evaluate_index_access(&interner, TypeId::STRING, TypeId::NUMBER);
assert_eq!(result, TypeId::STRING);
}
#[test]
fn test_index_access_string_literal_numeric_key() {
let interner = TypeInterner::new();
let zero = interner.literal_string("0");
let result = evaluate_index_access(&interner, TypeId::STRING, zero);
assert_eq!(result, TypeId::STRING);
}
#[test]
fn test_index_access_string_number_with_no_unchecked_indexed_access() {
let interner = TypeInterner::new();
let mut evaluator = TypeEvaluator::new(&interner);
evaluator.set_no_unchecked_indexed_access(true);
let result = evaluator.evaluate_index_access(TypeId::STRING, TypeId::NUMBER);
let expected = interner.union(vec![TypeId::STRING, TypeId::UNDEFINED]);
assert_eq!(result, expected);
}
#[test]
fn test_index_access_string_literal_numeric_key_with_no_unchecked_indexed_access() {
let interner = TypeInterner::new();
let mut evaluator = TypeEvaluator::new(&interner);
evaluator.set_no_unchecked_indexed_access(true);
let zero = interner.literal_string("0");
let result = evaluator.evaluate_index_access(TypeId::STRING, zero);
let expected = interner.union(vec![TypeId::STRING, TypeId::UNDEFINED]);
assert_eq!(result, expected);
}
#[test]
fn test_index_access_string_literal_member() {
let interner = TypeInterner::new();
let length_key = interner.literal_string("length");
let length_type = evaluate_index_access(&interner, TypeId::STRING, length_key);
assert_eq!(length_type, TypeId::NUMBER);
let to_string_key = interner.literal_string("toString");
let to_string_type = evaluate_index_access(&interner, TypeId::STRING, to_string_key);
match interner.lookup(to_string_type) {
Some(TypeData::Function(func_id)) => {
let func = interner.function_shape(func_id);
assert_eq!(func.return_type, TypeId::STRING);
assert_eq!(func.params.len(), 1);
assert!(func.params[0].rest);
}
other => panic!("Expected function type, got {other:?}"),
}
}
#[test]
fn test_index_access_template_literal_members() {
let interner = TypeInterner::new();
let template = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("prefix")),
TemplateSpan::Type(TypeId::STRING),
TemplateSpan::Text(interner.intern_string("suffix")),
]);
let length_key = interner.literal_string("length");
let length_type = evaluate_index_access(&interner, template, length_key);
assert_eq!(length_type, TypeId::NUMBER);
let number_index = evaluate_index_access(&interner, template, TypeId::NUMBER);
assert_eq!(number_index, TypeId::STRING);
}
#[test]
fn test_keyof_readonly_array() {
let interner = TypeInterner::new();
let array = interner.array(TypeId::STRING);
let readonly_array = interner.intern(TypeData::ReadonlyType(array));
let result = evaluate_keyof(&interner, readonly_array);
let key = interner
.lookup(result)
.expect("expected union for keyof readonly array");
match key {
TypeData::Union(members) => {
let members = interner.type_list(members);
let length = interner.literal_string("length");
let map = interner.literal_string("map");
assert!(members.contains(&TypeId::NUMBER));
assert!(members.contains(&length));
assert!(members.contains(&map));
}
other => panic!("Expected union, got {other:?}"),
}
}
#[test]
fn test_keyof_readonly_tuple() {
let interner = TypeInterner::new();
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 readonly_tuple = interner.intern(TypeData::ReadonlyType(tuple));
let result = evaluate_keyof(&interner, readonly_tuple);
let key = interner
.lookup(result)
.expect("expected union for keyof readonly tuple");
match key {
TypeData::Union(members) => {
let members = interner.type_list(members);
let key_0 = interner.literal_string("0");
let key_1 = interner.literal_string("1");
let length = interner.literal_string("length");
let map = interner.literal_string("map");
assert!(members.contains(&key_0));
assert!(members.contains(&key_1));
assert!(members.contains(&TypeId::NUMBER));
assert!(members.contains(&length));
assert!(members.contains(&map));
}
other => panic!("Expected union, got {other:?}"),
}
}
#[test]
fn test_keyof_type_param_constraint() {
let interner = TypeInterner::new();
let constraint = interner.object(vec![
PropertyInfo::new(interner.intern_string("x"), TypeId::NUMBER),
PropertyInfo::new(interner.intern_string("y"), TypeId::STRING),
]);
let type_param = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: interner.intern_string("T"),
constraint: Some(constraint),
default: None,
is_const: false,
}));
let result = evaluate_keyof(&interner, type_param);
let expected = interner.union(vec![
interner.literal_string("x"),
interner.literal_string("y"),
]);
assert_eq!(result, expected);
}
#[test]
fn test_base_constraint_assignability_evaluate_keyof() {
let interner = TypeInterner::new();
let constraint = interner.object(vec![
PropertyInfo::new(interner.intern_string("x"), TypeId::NUMBER),
PropertyInfo::new(interner.intern_string("y"), TypeId::STRING),
]);
let type_param = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: interner.intern_string("T"),
constraint: Some(constraint),
default: None,
is_const: false,
}));
let key_of = interner.intern(TypeData::KeyOf(type_param));
let result = evaluate_type(&interner, key_of);
let expected = interner.union(vec![
interner.literal_string("x"),
interner.literal_string("y"),
]);
assert_eq!(result, expected);
}
#[test]
fn test_keyof_type_param_no_constraint_deferred() {
let interner = TypeInterner::new();
let type_param = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: interner.intern_string("T"),
constraint: None,
default: None,
is_const: false,
}));
let result = evaluate_keyof(&interner, type_param);
match interner.lookup(result) {
Some(TypeData::KeyOf(inner)) => assert_eq!(inner, type_param),
other => panic!("Expected deferred KeyOf, got {other:?}"),
}
}
#[test]
fn test_keyof_resolves_ref() {
use crate::TypeEnvironment;
use crate::def::DefId;
let interner = TypeInterner::new();
let mut env = TypeEnvironment::new();
let obj = interner.object(vec![
PropertyInfo::new(interner.intern_string("x"), TypeId::NUMBER),
PropertyInfo::new(interner.intern_string("y"), TypeId::STRING),
]);
let def_id = DefId(2);
env.insert_def(def_id, obj);
let ref_type = interner.lazy(def_id);
let mut evaluator = TypeEvaluator::with_resolver(&interner, &env);
let result = evaluator.evaluate_keyof(ref_type);
let expected = interner.union(vec![
interner.literal_string("x"),
interner.literal_string("y"),
]);
assert_eq!(result, expected);
}
#[test]
fn test_index_access_tuple_second() {
let interner = TypeInterner::new();
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 one = interner.literal_number(1.0);
let result = evaluate_index_access(&interner, tuple, one);
assert_eq!(result, TypeId::NUMBER);
}
#[test]
fn test_index_access_tuple_number() {
let interner = TypeInterner::new();
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 result = evaluate_index_access(&interner, tuple, TypeId::NUMBER);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert_eq!(result, expected);
}
#[test]
fn test_index_access_tuple_optional_number() {
let interner = TypeInterner::new();
let tuple = 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 result = evaluate_index_access(&interner, tuple, TypeId::NUMBER);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER, TypeId::UNDEFINED]);
assert_eq!(result, expected);
}
#[test]
fn test_nested_conditional() {
let interner = TypeInterner::new();
let yes = interner.literal_string("yes");
let no = interner.literal_string("no");
let outer_no = interner.literal_string("outer-no");
let inner_cond = interner.conditional(ConditionalType {
check_type: TypeId::NUMBER,
extends_type: TypeId::NUMBER,
true_type: yes,
false_type: no,
is_distributive: false,
});
let cond = ConditionalType {
check_type: TypeId::STRING,
extends_type: TypeId::STRING,
true_type: inner_cond,
false_type: outer_no,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, yes);
}
#[test]
fn test_evaluate_type_non_meta() {
let interner = TypeInterner::new();
assert_eq!(evaluate_type(&interner, TypeId::STRING), TypeId::STRING);
assert_eq!(evaluate_type(&interner, TypeId::NUMBER), TypeId::NUMBER);
let obj = interner.object(vec![PropertyInfo::new(
interner.intern_string("x"),
TypeId::NUMBER,
)]);
assert_eq!(evaluate_type(&interner, obj), obj);
}
#[test]
fn test_keyof_object() {
let interner = TypeInterner::new();
let obj = interner.object(vec![
PropertyInfo::new(interner.intern_string("x"), TypeId::NUMBER),
PropertyInfo::new(interner.intern_string("y"), TypeId::STRING),
]);
let result = evaluate_keyof(&interner, obj);
let key_x = interner.literal_string("x");
let key_y = interner.literal_string("y");
let expected = interner.union(vec![key_x, key_y]);
assert_eq!(result, expected);
}
#[test]
fn test_keyof_object_with_string_index_signature() {
let interner = TypeInterner::new();
let key_x = interner.intern_string("x");
let obj = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![PropertyInfo::new(key_x, TypeId::NUMBER)],
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: TypeId::BOOLEAN,
readonly: false,
}),
number_index: None,
});
let result = evaluate_keyof(&interner, obj);
let expected = interner.union(vec![
interner.literal_string("x"),
TypeId::STRING,
TypeId::NUMBER,
]);
assert_eq!(result, expected);
}
#[test]
fn test_keyof_object_with_number_index_signature() {
let interner = TypeInterner::new();
let key_x = interner.intern_string("x");
let obj = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![PropertyInfo::new(key_x, TypeId::NUMBER)],
string_index: None,
number_index: Some(IndexSignature {
key_type: TypeId::NUMBER,
value_type: TypeId::STRING,
readonly: false,
}),
});
let result = evaluate_keyof(&interner, obj);
let expected = interner.union(vec![interner.literal_string("x"), TypeId::NUMBER]);
assert_eq!(result, expected);
}
#[test]
fn test_keyof_union_disjoint_objects() {
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_overlap_objects() {
let interner = TypeInterner::new();
let obj_a = interner.object(vec![
PropertyInfo::new(interner.intern_string("a"), TypeId::NUMBER),
PropertyInfo::new(interner.intern_string("b"), TypeId::STRING),
]);
let obj_b = interner.object(vec![
PropertyInfo::new(interner.intern_string("b"), TypeId::BOOLEAN),
PropertyInfo::new(interner.intern_string("c"), TypeId::STRING),
]);
let union = interner.union(vec![obj_a, obj_b]);
let result = evaluate_keyof(&interner, union);
let expected = interner.literal_string("b");
assert_eq!(result, expected);
}
#[test]
fn test_keyof_intersection_unions_keys() {
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_string_index_overlap_literal() {
let interner = TypeInterner::new();
let obj_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 obj_literal = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
TypeId::BOOLEAN,
)]);
let union = interner.union(vec![obj_index, obj_literal]);
let result = evaluate_keyof(&interner, union);
let expected = interner.literal_string("a");
assert_eq!(result, expected);
}
#[test]
fn test_keyof_union_index_signature_intersection() {
let interner = TypeInterner::new();
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 result = evaluate_keyof(&interner, union);
assert_eq!(result, TypeId::NUMBER);
}
#[test]
fn test_keyof_empty_object() {
let interner = TypeInterner::new();
let obj = interner.object(vec![]);
let result = evaluate_keyof(&interner, obj);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_keyof_array() {
let interner = TypeInterner::new();
let arr = interner.array(TypeId::STRING);
let result = evaluate_keyof(&interner, arr);
let key = interner
.lookup(result)
.expect("expected union for keyof array");
match key {
TypeData::Union(members) => {
let members = interner.type_list(members);
let length = interner.literal_string("length");
let map = interner.literal_string("map");
assert!(members.contains(&TypeId::NUMBER));
assert!(members.contains(&length));
assert!(members.contains(&map));
}
other => panic!("Expected union, got {other:?}"),
}
}
#[test]
fn test_keyof_tuple() {
let interner = TypeInterner::new();
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 result = evaluate_keyof(&interner, tuple);
let key = interner
.lookup(result)
.expect("expected union for keyof tuple");
match key {
TypeData::Union(members) => {
let members = interner.type_list(members);
let key_0 = interner.literal_string("0");
let key_1 = interner.literal_string("1");
let length = interner.literal_string("length");
let map = interner.literal_string("map");
assert!(members.contains(&key_0));
assert!(members.contains(&key_1));
assert!(members.contains(&TypeId::NUMBER));
assert!(members.contains(&length));
assert!(members.contains(&map));
}
other => panic!("Expected union, got {other:?}"),
}
}
#[test]
fn test_keyof_tuple_with_rest_tuple() {
let interner = TypeInterner::new();
let rest_tuple = 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,
},
]);
let tuple = 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,
},
]);
let result = evaluate_keyof(&interner, tuple);
let key = interner
.lookup(result)
.expect("expected union for keyof tuple with rest");
match key {
TypeData::Union(members) => {
let members = interner.type_list(members);
let key_0 = interner.literal_string("0");
let key_1 = interner.literal_string("1");
let key_2 = interner.literal_string("2");
let length = interner.literal_string("length");
assert!(members.contains(&key_0));
assert!(members.contains(&key_1));
assert!(members.contains(&key_2));
assert!(members.contains(&TypeId::NUMBER));
assert!(members.contains(&length));
}
other => panic!("Expected union, got {other:?}"),
}
}
#[test]
fn test_keyof_any() {
let interner = TypeInterner::new();
let result = evaluate_keyof(&interner, TypeId::ANY);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER, TypeId::SYMBOL]);
assert_eq!(result, expected);
}
#[test]
fn test_keyof_unknown() {
let interner = TypeInterner::new();
let result = evaluate_keyof(&interner, TypeId::UNKNOWN);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_keyof_object_keyword() {
let interner = TypeInterner::new();
let result = evaluate_keyof(&interner, TypeId::OBJECT);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_object_trifecta_keyof_object_interface() {
use crate::TypeEnvironment;
let interner = TypeInterner::new();
let mut env = TypeEnvironment::new();
let object_interface = interner.object(vec![
PropertyInfo::new(interner.intern_string("toString"), TypeId::STRING),
PropertyInfo::new(interner.intern_string("valueOf"), TypeId::NUMBER),
]);
let def_id = DefId(1);
env.insert_def(def_id, object_interface);
let ref_type = interner.lazy(def_id);
let mut evaluator = TypeEvaluator::with_resolver(&interner, &env);
let result = evaluator.evaluate_keyof(ref_type);
let key = interner
.lookup(result)
.expect("expected union for keyof Object interface");
match key {
TypeData::Union(members) => {
let members = interner.type_list(members);
let to_string = interner.literal_string("toString");
let value_of = interner.literal_string("valueOf");
assert!(members.contains(&to_string));
assert!(members.contains(&value_of));
}
other => panic!("Expected union, got {other:?}"),
}
}
#[test]
fn test_keyof_never() {
let interner = TypeInterner::new();
let result = evaluate_keyof(&interner, TypeId::NEVER);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_keyof_nullish() {
let interner = TypeInterner::new();
assert_eq!(evaluate_keyof(&interner, TypeId::NULL), TypeId::NEVER);
assert_eq!(evaluate_keyof(&interner, TypeId::UNDEFINED), TypeId::NEVER);
assert_eq!(evaluate_keyof(&interner, TypeId::VOID), TypeId::NEVER);
}
#[test]
fn test_keyof_string_apparent_members() {
let interner = TypeInterner::new();
let result = evaluate_keyof(&interner, TypeId::STRING);
let key = interner
.lookup(result)
.expect("expected union for keyof string");
match key {
TypeData::Union(members) => {
let members = interner.type_list(members);
let length = interner.literal_string("length");
let to_string = interner.literal_string("toString");
assert!(members.contains(&length));
assert!(members.contains(&to_string));
assert!(members.contains(&TypeId::NUMBER));
}
other => panic!("Expected union, got {other:?}"),
}
}
#[test]
fn test_apparent_number_keyof_members() {
let interner = TypeInterner::new();
let result = evaluate_keyof(&interner, TypeId::NUMBER);
let key = interner
.lookup(result)
.expect("expected union for keyof number");
match key {
TypeData::Union(members) => {
let members = interner.type_list(members);
let to_fixed = interner.literal_string("toFixed");
let value_of = interner.literal_string("valueOf");
assert!(members.contains(&to_fixed));
assert!(members.contains(&value_of));
}
other => panic!("Expected union, got {other:?}"),
}
}
#[test]
fn test_keyof_template_literal_matches_string() {
let interner = TypeInterner::new();
let template = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("prefix")),
TemplateSpan::Type(TypeId::STRING),
TemplateSpan::Text(interner.intern_string("suffix")),
]);
let result = evaluate_keyof(&interner, template);
let expected = evaluate_keyof(&interner, TypeId::STRING);
assert_eq!(result, expected);
}
#[test]
fn test_keyof_basic_object_type() {
let interner = TypeInterner::new();
let obj = interner.object(vec![
PropertyInfo::new(interner.intern_string("a"), TypeId::STRING),
PropertyInfo::new(interner.intern_string("b"), TypeId::NUMBER),
PropertyInfo::new(interner.intern_string("c"), TypeId::BOOLEAN),
]);
let result = evaluate_keyof(&interner, obj);
let key_a = interner.literal_string("a");
let key_b = interner.literal_string("b");
let key_c = interner.literal_string("c");
let expected = interner.union(vec![key_a, key_b, key_c]);
assert_eq!(result, expected);
}
#[test]
fn test_keyof_single_property() {
let interner = TypeInterner::new();
let obj = interner.object(vec![PropertyInfo::new(
interner.intern_string("only"),
TypeId::STRING,
)]);
let result = evaluate_keyof(&interner, obj);
let expected = interner.literal_string("only");
assert_eq!(result, expected);
}
#[test]
fn test_keyof_intersection_produces_union() {
let interner = TypeInterner::new();
let obj1 = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
TypeId::STRING,
)]);
let obj2 = interner.object(vec![PropertyInfo::new(
interner.intern_string("b"),
TypeId::NUMBER,
)]);
let intersection = interner.intersection(vec![obj1, obj2]);
let result = evaluate_keyof(&interner, intersection);
let key_a = interner.literal_string("a");
let key_b = interner.literal_string("b");
let expected = interner.union(vec![key_a, key_b]);
assert_eq!(result, expected);
}
#[test]
fn test_keyof_intersection_overlapping_keys() {
let interner = TypeInterner::new();
let obj1 = interner.object(vec![
PropertyInfo::new(interner.intern_string("a"), TypeId::STRING),
PropertyInfo::new(interner.intern_string("b"), TypeId::NUMBER),
]);
let obj2 = interner.object(vec![
PropertyInfo::new(interner.intern_string("b"), TypeId::BOOLEAN),
PropertyInfo::new(interner.intern_string("c"), TypeId::STRING),
]);
let intersection = interner.intersection(vec![obj1, obj2]);
let result = evaluate_keyof(&interner, intersection);
let key_a = interner.literal_string("a");
let key_b = interner.literal_string("b");
let key_c = interner.literal_string("c");
let expected = interner.union(vec![key_a, key_b, key_c]);
assert_eq!(result, expected);
}
#[test]
fn test_keyof_union_common_keys_only() {
let interner = TypeInterner::new();
let obj1 = interner.object(vec![
PropertyInfo::new(interner.intern_string("a"), TypeId::STRING),
PropertyInfo::new(interner.intern_string("b"), TypeId::NUMBER),
]);
let obj2 = interner.object(vec![
PropertyInfo::new(interner.intern_string("b"), TypeId::BOOLEAN),
PropertyInfo::new(interner.intern_string("c"), TypeId::STRING),
]);
let union = interner.union(vec![obj1, obj2]);
let result = evaluate_keyof(&interner, union);
let expected = interner.literal_string("b");
assert_eq!(result, expected);
}
#[test]
fn test_keyof_union_no_common_keys() {
let interner = TypeInterner::new();
let obj1 = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
TypeId::STRING,
)]);
let obj2 = interner.object(vec![PropertyInfo::new(
interner.intern_string("b"),
TypeId::NUMBER,
)]);
let union = interner.union(vec![obj1, obj2]);
let result = evaluate_keyof(&interner, union);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_keyof_mapped_type_basic() {
let interner = TypeInterner::new();
let key_x = interner.literal_string("x");
let key_y = interner.literal_string("y");
let keys = interner.union(vec![key_x, key_y]);
let 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: None,
};
let mapped_result = evaluate_mapped(&interner, &mapped);
let result = evaluate_keyof(&interner, mapped_result);
let expected = interner.union(vec![key_x, key_y]);
assert_eq!(result, expected);
}
#[test]
fn test_keyof_mapped_type_remapped_keys() {
let interner = TypeInterner::new();
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_a_key = interner.literal_string("a_key");
let key_b_key = interner.literal_string("b_key");
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 inner_cond = interner.conditional(ConditionalType {
check_type: key_param_id,
extends_type: key_b,
true_type: key_b_key,
false_type: TypeId::NEVER,
is_distributive: false,
});
let name_type = interner.conditional(ConditionalType {
check_type: key_param_id,
extends_type: key_a,
true_type: key_a_key,
false_type: inner_cond,
is_distributive: false,
});
let mapped = MappedType {
type_param: key_param,
constraint: keys,
name_type: Some(name_type),
template: TypeId::STRING,
readonly_modifier: None,
optional_modifier: None,
};
let mapped_result = evaluate_mapped(&interner, &mapped);
let result = evaluate_keyof(&interner, mapped_result);
let expected = interner.union(vec![key_a_key, key_b_key]);
assert_eq!(result, expected);
}
#[test]
fn test_keyof_readonly_and_optional_properties() {
let interner = TypeInterner::new();
let obj = interner.object(vec![
PropertyInfo {
name: interner.intern_string("a"),
type_id: TypeId::STRING,
write_type: TypeId::STRING,
optional: false,
readonly: true, is_method: false,
visibility: Visibility::Public,
parent_id: None,
},
PropertyInfo {
name: interner.intern_string("b"),
type_id: TypeId::NUMBER,
write_type: TypeId::NUMBER,
optional: true, readonly: false,
is_method: false,
visibility: Visibility::Public,
parent_id: None,
},
]);
let result = evaluate_keyof(&interner, obj);
let key_a = interner.literal_string("a");
let key_b = interner.literal_string("b");
let expected = interner.union(vec![key_a, key_b]);
assert_eq!(result, expected);
}
#[test]
fn test_keyof_triple_intersection() {
let interner = TypeInterner::new();
let obj1 = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
TypeId::STRING,
)]);
let obj2 = interner.object(vec![PropertyInfo::new(
interner.intern_string("b"),
TypeId::NUMBER,
)]);
let obj3 = interner.object(vec![PropertyInfo::new(
interner.intern_string("c"),
TypeId::BOOLEAN,
)]);
let intersection = interner.intersection(vec![obj1, obj2, obj3]);
let result = evaluate_keyof(&interner, intersection);
let key_a = interner.literal_string("a");
let key_b = interner.literal_string("b");
let key_c = interner.literal_string("c");
let expected = interner.union(vec![key_a, key_b, key_c]);
assert_eq!(result, expected);
}
#[test]
fn test_keyof_union_identical_keys() {
let interner = TypeInterner::new();
let obj1 = interner.object(vec![
PropertyInfo::new(interner.intern_string("a"), TypeId::STRING),
PropertyInfo::new(interner.intern_string("b"), TypeId::NUMBER),
]);
let obj2 = interner.object(vec![
PropertyInfo::new(interner.intern_string("a"), TypeId::BOOLEAN),
PropertyInfo::new(interner.intern_string("b"), TypeId::STRING),
]);
let union = interner.union(vec![obj1, obj2]);
let result = evaluate_keyof(&interner, union);
let key_a = interner.literal_string("a");
let key_b = interner.literal_string("b");
let expected = interner.union(vec![key_a, key_b]);
assert_eq!(result, expected);
}
#[test]
fn test_keyof_nested_object_only_top_level() {
let interner = TypeInterner::new();
let inner_obj = interner.object(vec![PropertyInfo::new(
interner.intern_string("b"),
TypeId::NUMBER,
)]);
let outer_obj = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
inner_obj,
)]);
let result = evaluate_keyof(&interner, outer_obj);
let expected = interner.literal_string("a");
assert_eq!(result, expected);
}
#[test]
fn test_keyof_both_index_signatures() {
let interner = TypeInterner::new();
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::ANY,
readonly: false,
}),
});
let result = evaluate_keyof(&interner, obj);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert_eq!(result, expected);
}
#[test]
fn test_keyof_numeric_literal_keys() {
let interner = TypeInterner::new();
let obj = interner.object(vec![
PropertyInfo::new(interner.intern_string("0"), TypeId::STRING),
PropertyInfo::new(interner.intern_string("1"), TypeId::NUMBER),
]);
let result = evaluate_keyof(&interner, obj);
let key_0 = interner.literal_string("0");
let key_1 = interner.literal_string("1");
let expected = interner.union(vec![key_0, key_1]);
assert_eq!(result, expected);
}
#[test]
fn test_keyof_mixed_optional_required() {
let interner = TypeInterner::new();
let obj = interner.object(vec![
PropertyInfo::new(interner.intern_string("a"), TypeId::STRING),
PropertyInfo::opt(interner.intern_string("b"), TypeId::NUMBER),
PropertyInfo::new(interner.intern_string("c"), TypeId::BOOLEAN),
]);
let result = evaluate_keyof(&interner, obj);
let key_a = interner.literal_string("a");
let key_b = interner.literal_string("b");
let key_c = interner.literal_string("c");
let expected = interner.union(vec![key_a, key_b, key_c]);
assert_eq!(result, expected);
}
#[test]
fn test_keyof_function_type() {
let interner = TypeInterner::new();
let func = interner.function(FunctionShape {
params: vec![],
this_type: None,
return_type: TypeId::VOID,
type_params: vec![],
type_predicate: None,
is_constructor: false,
is_method: false,
});
let result = evaluate_keyof(&interner, func);
assert_ne!(result, func);
}
#[test]
fn test_keyof_deeply_nested_union() {
let interner = TypeInterner::new();
let obj_a = interner.object(vec![
PropertyInfo::new(interner.intern_string("shared"), TypeId::STRING),
PropertyInfo::new(interner.intern_string("a"), TypeId::NUMBER),
]);
let obj_b = interner.object(vec![
PropertyInfo::new(interner.intern_string("shared"), TypeId::NUMBER),
PropertyInfo::new(interner.intern_string("b"), TypeId::BOOLEAN),
]);
let obj_c = interner.object(vec![
PropertyInfo::new(interner.intern_string("shared"), TypeId::BOOLEAN),
PropertyInfo::new(interner.intern_string("c"), TypeId::STRING),
]);
let inner_union = interner.union(vec![obj_b, obj_c]);
let outer_union = interner.union(vec![obj_a, inner_union]);
let result = evaluate_keyof(&interner, outer_union);
let expected = interner.literal_string("shared");
assert_eq!(result, expected);
}
#[test]
fn test_keyof_deeply_nested_intersection() {
let interner = TypeInterner::new();
let obj_a = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
TypeId::STRING,
)]);
let obj_b = interner.object(vec![PropertyInfo::new(
interner.intern_string("b"),
TypeId::NUMBER,
)]);
let obj_c = interner.object(vec![PropertyInfo::new(
interner.intern_string("c"),
TypeId::BOOLEAN,
)]);
let inner_intersection = interner.intersection(vec![obj_b, obj_c]);
let outer_intersection = interner.intersection(vec![obj_a, inner_intersection]);
let result = evaluate_keyof(&interner, outer_intersection);
let key_a = interner.literal_string("a");
let key_b = interner.literal_string("b");
let key_c = interner.literal_string("c");
let expected = interner.union(vec![key_a, key_b, key_c]);
assert_eq!(result, expected);
}
#[test]
fn test_keyof_with_method_property() {
let interner = TypeInterner::new();
let method_type = interner.function(FunctionShape {
params: vec![],
this_type: None,
return_type: TypeId::VOID,
type_params: vec![],
type_predicate: None,
is_constructor: false,
is_method: false,
});
let obj = interner.object(vec![
PropertyInfo::method(interner.intern_string("fn"), method_type),
PropertyInfo::new(interner.intern_string("prop"), TypeId::STRING),
]);
let result = evaluate_keyof(&interner, obj);
let key_fn = interner.literal_string("fn");
let key_prop = interner.literal_string("prop");
let expected = interner.union(vec![key_fn, key_prop]);
assert_eq!(result, expected);
}
#[test]
fn test_keyof_union_with_index_signature_and_literal() {
let interner = TypeInterner::new();
let obj_literal = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
TypeId::STRING,
)]);
let obj_indexed = 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 union = interner.union(vec![obj_literal, obj_indexed]);
let result = evaluate_keyof(&interner, union);
let expected = interner.literal_string("a");
assert_eq!(result, expected);
}
#[test]
fn test_keyof_intersection_with_index_signature() {
let interner = TypeInterner::new();
let obj_literal = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
TypeId::STRING,
)]);
let obj_indexed = 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_literal, obj_indexed]);
let result = evaluate_keyof(&interner, intersection);
let key = interner.lookup(result);
match key {
Some(TypeData::Intrinsic(IntrinsicKind::String)) | Some(TypeData::Union(_)) => (),
_ => panic!("Expected string or union type, got {key:?}"),
}
}
#[test]
fn test_keyof_single_property_equals_literal() {
let interner = TypeInterner::new();
let obj = interner.object(vec![PropertyInfo::new(
interner.intern_string("only"),
TypeId::STRING,
)]);
let result = evaluate_keyof(&interner, obj);
let expected = interner.literal_string("only");
assert_eq!(result, expected);
}
#[test]
fn test_keyof_readonly_properties_included() {
let interner = TypeInterner::new();
let obj = interner.object(vec![
PropertyInfo::readonly(interner.intern_string("a"), TypeId::STRING),
PropertyInfo::new(interner.intern_string("b"), TypeId::NUMBER),
]);
let result = evaluate_keyof(&interner, obj);
let key_a = interner.literal_string("a");
let key_b = interner.literal_string("b");
let expected = interner.union(vec![key_a, key_b]);
assert_eq!(result, expected);
}
#[test]
fn test_keyof_bigint() {
let interner = TypeInterner::new();
let result = evaluate_keyof(&interner, TypeId::BIGINT);
assert_ne!(result, TypeId::BIGINT);
}
#[test]
fn test_keyof_symbol() {
let interner = TypeInterner::new();
let result = evaluate_keyof(&interner, TypeId::SYMBOL);
assert_ne!(result, TypeId::SYMBOL);
}
#[test]
fn test_keyof_boolean() {
let interner = TypeInterner::new();
let result = evaluate_keyof(&interner, TypeId::BOOLEAN);
assert_ne!(result, TypeId::BOOLEAN);
}
#[test]
fn test_intersection_reduction_disjoint_discriminant_evaluates_never() {
let interner = TypeInterner::new();
let kind = interner.intern_string("kind");
let obj_a = interner.object(vec![PropertyInfo::new(kind, interner.literal_string("a"))]);
let obj_b = interner.object(vec![PropertyInfo::new(kind, interner.literal_string("b"))]);
let intersection = interner.intersection(vec![obj_a, obj_b]);
let result = evaluate_type(&interner, intersection);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_mapped_type_basic() {
let interner = TypeInterner::new();
let key_x = interner.literal_string("x");
let key_y = interner.literal_string("y");
let keys = interner.union(vec![key_x, key_y]);
let 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: None,
};
let result = evaluate_mapped(&interner, &mapped);
let expected = interner.object(vec![
PropertyInfo::new(interner.intern_string("x"), TypeId::NUMBER),
PropertyInfo::new(interner.intern_string("y"), TypeId::NUMBER),
]);
assert_eq!(result, expected);
}
#[test]
fn test_mapped_type_over_string_keys() {
let interner = TypeInterner::new();
let constraint = interner.intern(TypeData::KeyOf(TypeId::STRING));
let 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 result = evaluate_mapped(&interner, &mapped);
let key = interner.lookup(result).expect("Expected object type");
match key {
TypeData::ObjectWithIndex(shape_id) => {
let shape = interner.object_shape(shape_id);
let length = interner.intern_string("length");
let to_string = interner.intern_string("toString");
let mut saw_length = false;
let mut saw_to_string = false;
for prop in &shape.properties {
if prop.name == length {
assert_eq!(prop.type_id, TypeId::BOOLEAN);
saw_length = true;
}
if prop.name == to_string {
assert_eq!(prop.type_id, TypeId::BOOLEAN);
saw_to_string = true;
}
}
assert!(saw_length, "missing length property");
assert!(saw_to_string, "missing toString property");
let number_index = shape
.number_index
.as_ref()
.expect("expected number index signature");
assert_eq!(number_index.key_type, TypeId::NUMBER);
assert_eq!(number_index.value_type, TypeId::BOOLEAN);
}
other => panic!("Expected object type, got {other:?}"),
}
}
#[test]
fn test_mapped_type_over_number_keys() {
let interner = TypeInterner::new();
let constraint = interner.intern(TypeData::KeyOf(TypeId::NUMBER));
let 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 result = evaluate_mapped(&interner, &mapped);
let key = interner.lookup(result).expect("Expected object type");
match key {
TypeData::Object(shape_id) => {
let shape = interner.object_shape(shape_id);
let to_fixed = interner.intern_string("toFixed");
let value_of = interner.intern_string("valueOf");
let has_own = interner.intern_string("hasOwnProperty");
let mut saw_to_fixed = false;
let mut saw_value_of = false;
let mut saw_has_own = false;
for prop in &shape.properties {
if prop.name == to_fixed {
assert_eq!(prop.type_id, TypeId::BOOLEAN);
saw_to_fixed = true;
}
if prop.name == value_of {
assert_eq!(prop.type_id, TypeId::BOOLEAN);
saw_value_of = true;
}
if prop.name == has_own {
assert_eq!(prop.type_id, TypeId::BOOLEAN);
saw_has_own = true;
}
}
assert!(saw_to_fixed, "missing toFixed property");
assert!(saw_value_of, "missing valueOf property");
assert!(saw_has_own, "missing hasOwnProperty property");
}
other => panic!("Expected object type, got {other:?}"),
}
}
#[test]
fn test_mapped_type_over_number_keys_evaluate_type() {
let interner = TypeInterner::new();
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 result = evaluate_type(&interner, mapped);
let key = interner.lookup(result).expect("Expected object type");
match key {
TypeData::Object(shape_id) => {
let shape = interner.object_shape(shape_id);
let to_fixed = interner.intern_string("toFixed");
let mut saw_to_fixed = false;
for prop in &shape.properties {
if prop.name == to_fixed {
assert_eq!(prop.type_id, TypeId::BOOLEAN);
saw_to_fixed = true;
}
}
assert!(saw_to_fixed, "missing toFixed property");
}
other => panic!("Expected object type, got {other:?}"),
}
}
#[test]
fn test_mapped_type_over_boolean_keys() {
let interner = TypeInterner::new();
let constraint = interner.intern(TypeData::KeyOf(TypeId::BOOLEAN));
let 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 result = evaluate_mapped(&interner, &mapped);
let key = interner.lookup(result).expect("Expected object type");
match key {
TypeData::Object(shape_id) => {
let shape = interner.object_shape(shape_id);
let to_string = interner.intern_string("toString");
let value_of = interner.intern_string("valueOf");
let has_own = interner.intern_string("hasOwnProperty");
let mut saw_to_string = false;
let mut saw_value_of = false;
let mut saw_has_own = false;
for prop in &shape.properties {
if prop.name == to_string {
assert_eq!(prop.type_id, TypeId::NUMBER);
saw_to_string = true;
}
if prop.name == value_of {
assert_eq!(prop.type_id, TypeId::NUMBER);
saw_value_of = true;
}
if prop.name == has_own {
assert_eq!(prop.type_id, TypeId::NUMBER);
saw_has_own = true;
}
}
assert!(saw_to_string, "missing toString property");
assert!(saw_value_of, "missing valueOf property");
assert!(saw_has_own, "missing hasOwnProperty property");
}
other => panic!("Expected object type, got {other:?}"),
}
}
#[test]
fn test_mapped_type_over_symbol_keys() {
let interner = TypeInterner::new();
let constraint = interner.intern(TypeData::KeyOf(TypeId::SYMBOL));
let 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 result = evaluate_mapped(&interner, &mapped);
let key = interner.lookup(result).expect("Expected object type");
match key {
TypeData::Object(shape_id) => {
let shape = interner.object_shape(shape_id);
let description = interner.intern_string("description");
let to_string = interner.intern_string("toString");
let value_of = interner.intern_string("valueOf");
let mut saw_description = false;
let mut saw_to_string = false;
let mut saw_value_of = false;
for prop in &shape.properties {
if prop.name == description {
assert_eq!(prop.type_id, TypeId::BOOLEAN);
saw_description = true;
}
if prop.name == to_string {
assert_eq!(prop.type_id, TypeId::BOOLEAN);
saw_to_string = true;
}
if prop.name == value_of {
assert_eq!(prop.type_id, TypeId::BOOLEAN);
saw_value_of = true;
}
}
assert!(saw_description, "missing description property");
assert!(saw_to_string, "missing toString property");
assert!(saw_value_of, "missing valueOf property");
}
other => panic!("Expected object type, got {other:?}"),
}
}
#[test]
fn test_mapped_type_over_bigint_keys() {
let interner = TypeInterner::new();
let constraint = interner.intern(TypeData::KeyOf(TypeId::BIGINT));
let mapped = MappedType {
type_param: TypeParamInfo {
name: interner.intern_string("K"),
constraint: None,
default: None,
is_const: false,
},
constraint,
name_type: None,
template: TypeId::STRING,
readonly_modifier: None,
optional_modifier: None,
};
let result = evaluate_mapped(&interner, &mapped);
let key = interner.lookup(result).expect("Expected object type");
match key {
TypeData::Object(shape_id) => {
let shape = interner.object_shape(shape_id);
let to_string = interner.intern_string("toString");
let value_of = interner.intern_string("valueOf");
let has_own = interner.intern_string("hasOwnProperty");
let mut saw_to_string = false;
let mut saw_value_of = false;
let mut saw_has_own = false;
for prop in &shape.properties {
if prop.name == to_string {
assert_eq!(prop.type_id, TypeId::STRING);
saw_to_string = true;
}
if prop.name == value_of {
assert_eq!(prop.type_id, TypeId::STRING);
saw_value_of = true;
}
if prop.name == has_own {
assert_eq!(prop.type_id, TypeId::STRING);
saw_has_own = true;
}
}
assert!(saw_to_string, "missing toString property");
assert!(saw_value_of, "missing valueOf property");
assert!(saw_has_own, "missing hasOwnProperty property");
}
other => panic!("Expected object type, got {other:?}"),
}
}
#[test]
fn test_mapped_type_string_index_signature() {
let interner = TypeInterner::new();
let mapped = MappedType {
type_param: TypeParamInfo {
name: interner.intern_string("K"),
constraint: None,
default: None,
is_const: false,
},
constraint: TypeId::STRING,
name_type: None,
template: TypeId::NUMBER,
readonly_modifier: Some(MappedModifier::Add),
optional_modifier: Some(MappedModifier::Add),
};
let result = evaluate_mapped(&interner, &mapped);
let key = interner.lookup(result).expect("Expected object type");
match key {
TypeData::ObjectWithIndex(shape_id) => {
let shape = interner.object_shape(shape_id);
assert!(shape.properties.is_empty());
assert!(shape.number_index.is_none());
let string_index = shape
.string_index
.as_ref()
.expect("expected string index signature");
assert_eq!(string_index.key_type, TypeId::STRING);
let expected_value = interner.union(vec![TypeId::NUMBER, TypeId::UNDEFINED]);
assert_eq!(string_index.value_type, expected_value);
assert!(string_index.readonly);
}
other => panic!("Expected object type, got {other:?}"),
}
}
#[test]
fn test_mapped_type_number_index_signature() {
let interner = TypeInterner::new();
let mapped = MappedType {
type_param: TypeParamInfo {
name: interner.intern_string("K"),
constraint: None,
default: None,
is_const: false,
},
constraint: TypeId::NUMBER,
name_type: None,
template: TypeId::STRING,
readonly_modifier: None,
optional_modifier: None,
};
let result = evaluate_mapped(&interner, &mapped);
let key = interner.lookup(result).expect("Expected object type");
match key {
TypeData::ObjectWithIndex(shape_id) => {
let shape = interner.object_shape(shape_id);
assert!(shape.properties.is_empty());
assert!(shape.string_index.is_none());
let number_index = shape
.number_index
.as_ref()
.expect("expected number index signature");
assert_eq!(number_index.key_type, TypeId::NUMBER);
assert_eq!(number_index.value_type, TypeId::STRING);
assert!(!number_index.readonly);
}
other => panic!("Expected object type, got {other:?}"),
}
}
#[test]
fn test_mapped_type_single_key() {
let interner = TypeInterner::new();
let key_foo = interner.literal_string("foo");
let mapped = MappedType {
type_param: TypeParamInfo {
name: interner.intern_string("K"),
constraint: None,
default: None,
is_const: false,
},
constraint: key_foo,
name_type: None,
template: TypeId::STRING,
readonly_modifier: None,
optional_modifier: None,
};
let result = evaluate_mapped(&interner, &mapped);
let expected = interner.object(vec![PropertyInfo::new(
interner.intern_string("foo"),
TypeId::STRING,
)]);
assert_eq!(result, expected);
}
#[test]
fn test_mapped_type_with_optional_modifier() {
let interner = TypeInterner::new();
let key_x = interner.literal_string("x");
let key_y = interner.literal_string("y");
let keys = interner.union(vec![key_x, key_y]);
let 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 result = evaluate_mapped(&interner, &mapped);
let expected = interner.object(vec![
PropertyInfo::opt(interner.intern_string("x"), TypeId::NUMBER),
PropertyInfo::opt(interner.intern_string("y"), TypeId::NUMBER),
]);
assert_eq!(result, expected);
}
#[test]
fn test_mapped_type_with_readonly_modifier() {
let interner = TypeInterner::new();
let key_x = interner.literal_string("x");
let mapped = MappedType {
type_param: TypeParamInfo {
name: interner.intern_string("K"),
constraint: None,
default: None,
is_const: false,
},
constraint: key_x,
name_type: None,
template: TypeId::NUMBER,
readonly_modifier: Some(MappedModifier::Add),
optional_modifier: None,
};
let result = evaluate_mapped(&interner, &mapped);
let expected = interner.object(vec![PropertyInfo::readonly(
interner.intern_string("x"),
TypeId::NUMBER,
)]);
assert_eq!(result, expected);
}
#[test]
fn test_mapped_type_with_template_substitution() {
let interner = TypeInterner::new();
let key_x = interner.literal_string("x");
let key_y = interner.literal_string("y");
let keys = interner.union(vec![key_x, key_y]);
let type_param_k = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: interner.intern_string("K"),
constraint: None,
default: None,
is_const: false,
}));
let mapped = MappedType {
type_param: TypeParamInfo {
name: interner.intern_string("K"),
constraint: None,
default: None,
is_const: false,
},
constraint: keys,
name_type: None,
template: type_param_k,
readonly_modifier: None,
optional_modifier: None,
};
let result = evaluate_mapped(&interner, &mapped);
let expected = interner.object(vec![
PropertyInfo::new(interner.intern_string("x"), key_x),
PropertyInfo::new(interner.intern_string("y"), key_y),
]);
assert_eq!(result, expected);
}
#[test]
fn test_mapped_type_key_remap_filters_keys() {
let interner = TypeInterner::new();
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 = MappedType {
type_param: key_param,
constraint: keys,
name_type: Some(name_type),
template,
readonly_modifier: None,
optional_modifier: None,
};
let result = evaluate_mapped(&interner, &mapped);
let expected = interner.object(vec![PropertyInfo {
name: prop_b.name,
type_id: prop_b.type_id,
write_type: prop_b.write_type,
optional: false,
readonly: false,
is_method: false,
visibility: Visibility::Public,
parent_id: None,
}]);
assert_eq!(result, expected);
}
#[test]
fn test_mapped_type_deferred() {
let interner = TypeInterner::new();
let type_param_t = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: interner.intern_string("T"),
constraint: None,
default: None,
is_const: false,
}));
let mapped = MappedType {
type_param: TypeParamInfo {
name: interner.intern_string("K"),
constraint: None,
default: None,
is_const: false,
},
constraint: type_param_t,
name_type: None,
template: TypeId::NUMBER,
readonly_modifier: None,
optional_modifier: None,
};
let mapped_type = interner.mapped(mapped.clone());
let result = evaluate_mapped(&interner, &mapped);
assert_eq!(result, mapped_type);
}
#[test]
fn test_mapped_type_remove_readonly_modifier() {
let interner = TypeInterner::new();
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 = 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: Some(MappedModifier::Remove), optional_modifier: None,
};
let result = evaluate_mapped(&interner, &mapped);
let a_name = interner.intern_string("a");
let b_name = interner.intern_string("b");
let expected = interner.object(vec![
PropertyInfo {
name: a_name,
type_id: TypeId::STRING,
write_type: TypeId::STRING,
optional: false,
readonly: false, is_method: false,
visibility: Visibility::Public,
parent_id: None,
},
PropertyInfo::new(b_name, TypeId::STRING),
]);
assert_eq!(result, expected);
}
#[test]
fn test_mapped_type_remove_optional_modifier() {
let interner = TypeInterner::new();
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 = 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 result = evaluate_mapped(&interner, &mapped);
let a_name = interner.intern_string("a");
let b_name = interner.intern_string("b");
let expected = interner.object(vec![
PropertyInfo {
name: a_name,
type_id: TypeId::NUMBER,
write_type: TypeId::NUMBER,
optional: false, readonly: false,
is_method: false,
visibility: Visibility::Public,
parent_id: None,
},
PropertyInfo::new(b_name, TypeId::NUMBER),
]);
assert_eq!(result, expected);
}
#[test]
fn test_mapped_type_add_readonly_modifier() {
let interner = TypeInterner::new();
let key_x = interner.literal_string("x");
let key_y = interner.literal_string("y");
let keys = interner.union(vec![key_x, key_y]);
let mapped = MappedType {
type_param: TypeParamInfo {
name: interner.intern_string("K"),
constraint: None,
default: None,
is_const: false,
},
constraint: keys,
name_type: None,
template: TypeId::BOOLEAN,
readonly_modifier: Some(MappedModifier::Add), optional_modifier: None,
};
let result = evaluate_mapped(&interner, &mapped);
let x_name = interner.intern_string("x");
let y_name = interner.intern_string("y");
let expected = interner.object(vec![
PropertyInfo {
name: x_name,
type_id: TypeId::BOOLEAN,
write_type: TypeId::BOOLEAN,
optional: false,
readonly: true, is_method: false,
visibility: Visibility::Public,
parent_id: None,
},
PropertyInfo::readonly(y_name, TypeId::BOOLEAN),
]);
assert_eq!(result, expected);
}
#[test]
fn test_mapped_type_add_optional_modifier() {
let interner = TypeInterner::new();
let key_foo = interner.literal_string("foo");
let key_bar = interner.literal_string("bar");
let keys = interner.union(vec![key_foo, key_bar]);
let 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::Add), };
let result = evaluate_mapped(&interner, &mapped);
let foo_name = interner.intern_string("foo");
let bar_name = interner.intern_string("bar");
let expected = interner.object(vec![
PropertyInfo {
name: bar_name,
type_id: TypeId::STRING,
write_type: TypeId::STRING,
optional: true, readonly: false,
is_method: false,
visibility: Visibility::Public,
parent_id: None,
},
PropertyInfo::opt(foo_name, TypeId::STRING),
]);
assert_eq!(result, expected);
}
#[test]
fn test_mapped_type_both_modifiers() {
let interner = TypeInterner::new();
let key_id = interner.literal_string("id");
let mapped = MappedType {
type_param: TypeParamInfo {
name: interner.intern_string("K"),
constraint: None,
default: None,
is_const: false,
},
constraint: key_id,
name_type: None,
template: TypeId::NUMBER,
readonly_modifier: Some(MappedModifier::Add), optional_modifier: Some(MappedModifier::Add), };
let result = evaluate_mapped(&interner, &mapped);
let id_name = interner.intern_string("id");
let expected = interner.object(vec![PropertyInfo {
name: id_name,
type_id: TypeId::NUMBER,
write_type: TypeId::NUMBER,
optional: true,
readonly: true,
is_method: false,
visibility: Visibility::Public,
parent_id: None,
}]);
assert_eq!(result, expected);
}
#[test]
fn test_mapped_type_both_remove_modifiers() {
let interner = TypeInterner::new();
let key_data = interner.literal_string("data");
let mapped = MappedType {
type_param: TypeParamInfo {
name: interner.intern_string("K"),
constraint: None,
default: None,
is_const: false,
},
constraint: key_data,
name_type: None,
template: TypeId::STRING,
readonly_modifier: Some(MappedModifier::Remove), optional_modifier: Some(MappedModifier::Remove), };
let result = evaluate_mapped(&interner, &mapped);
let data_name = interner.intern_string("data");
let expected = interner.object(vec![PropertyInfo {
name: data_name,
type_id: TypeId::STRING,
write_type: TypeId::STRING,
optional: false, readonly: false, is_method: false,
visibility: Visibility::Public,
parent_id: None,
}]);
assert_eq!(result, expected);
}
#[test]
fn test_mapped_type_add_readonly_remove_optional() {
let interner = TypeInterner::new();
let key_value = interner.literal_string("value");
let mapped = MappedType {
type_param: TypeParamInfo {
name: interner.intern_string("K"),
constraint: None,
default: None,
is_const: false,
},
constraint: key_value,
name_type: None,
template: TypeId::NUMBER,
readonly_modifier: Some(MappedModifier::Add), optional_modifier: Some(MappedModifier::Remove), };
let result = evaluate_mapped(&interner, &mapped);
let value_name = interner.intern_string("value");
let expected = interner.object(vec![PropertyInfo {
name: value_name,
type_id: TypeId::NUMBER,
write_type: TypeId::NUMBER,
optional: false, readonly: true, is_method: false,
visibility: Visibility::Public,
parent_id: None,
}]);
assert_eq!(result, expected);
}
#[test]
fn test_mapped_type_remove_readonly_add_optional() {
let interner = TypeInterner::new();
let key_config = interner.literal_string("config");
let mapped = MappedType {
type_param: TypeParamInfo {
name: interner.intern_string("K"),
constraint: None,
default: None,
is_const: false,
},
constraint: key_config,
name_type: None,
template: TypeId::BOOLEAN,
readonly_modifier: Some(MappedModifier::Remove), optional_modifier: Some(MappedModifier::Add), };
let result = evaluate_mapped(&interner, &mapped);
let config_name = interner.intern_string("config");
let expected = interner.object(vec![PropertyInfo {
name: config_name,
type_id: TypeId::BOOLEAN,
write_type: TypeId::BOOLEAN,
optional: true, readonly: false, is_method: false,
visibility: Visibility::Public,
parent_id: None,
}]);
assert_eq!(result, expected);
}
#[test]
fn test_mapped_type_minus_readonly_on_readonly_source() {
let interner = TypeInterner::new();
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 = 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: Some(MappedModifier::Remove), optional_modifier: None,
};
let result = evaluate_mapped(&interner, &mapped);
let a_name = interner.intern_string("a");
let b_name = interner.intern_string("b");
let expected = interner.object(vec![
PropertyInfo {
name: a_name,
type_id: TypeId::STRING,
write_type: TypeId::STRING,
optional: false,
readonly: false, is_method: false,
visibility: Visibility::Public,
parent_id: None,
},
PropertyInfo::new(b_name, TypeId::STRING),
]);
assert_eq!(result, expected);
}
#[test]
fn test_mapped_type_plus_optional_on_required_source() {
let interner = TypeInterner::new();
let key_x = interner.literal_string("x");
let key_y = interner.literal_string("y");
let keys = interner.union(vec![key_x, key_y]);
let 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 result = evaluate_mapped(&interner, &mapped);
let x_name = interner.intern_string("x");
let y_name = interner.intern_string("y");
let expected = interner.object(vec![
PropertyInfo {
name: x_name,
type_id: TypeId::NUMBER,
write_type: TypeId::NUMBER,
optional: true, readonly: false,
is_method: false,
visibility: Visibility::Public,
parent_id: None,
},
PropertyInfo::opt(y_name, TypeId::NUMBER),
]);
assert_eq!(result, expected);
}
#[test]
fn test_mapped_type_key_remap_uppercase() {
let interner = TypeInterner::new();
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_upper_a = interner.literal_string("A");
let key_upper_b = interner.literal_string("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 inner_cond = interner.conditional(ConditionalType {
check_type: key_param_id,
extends_type: key_b,
true_type: key_upper_b,
false_type: TypeId::NEVER,
is_distributive: false,
});
let name_type = interner.conditional(ConditionalType {
check_type: key_param_id,
extends_type: key_a,
true_type: key_upper_a,
false_type: inner_cond,
is_distributive: false,
});
let mapped = MappedType {
type_param: key_param,
constraint: keys,
name_type: Some(name_type),
template: TypeId::STRING,
readonly_modifier: None,
optional_modifier: None,
};
let result = evaluate_mapped(&interner, &mapped);
let a_upper_name = interner.intern_string("A");
let b_upper_name = interner.intern_string("B");
let expected = interner.object(vec![
PropertyInfo::new(a_upper_name, TypeId::STRING),
PropertyInfo::new(b_upper_name, TypeId::STRING),
]);
assert_eq!(result, expected);
}
#[test]
fn test_mapped_type_key_remap_with_prefix() {
let interner = TypeInterner::new();
let key_name = interner.literal_string("name");
let key_age = interner.literal_string("age");
let keys = interner.union(vec![key_name, key_age]);
let key_get_name = interner.literal_string("get_name");
let key_get_age = interner.literal_string("get_age");
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 inner_cond = interner.conditional(ConditionalType {
check_type: key_param_id,
extends_type: key_age,
true_type: key_get_age,
false_type: TypeId::NEVER,
is_distributive: false,
});
let name_type = interner.conditional(ConditionalType {
check_type: key_param_id,
extends_type: key_name,
true_type: key_get_name,
false_type: inner_cond,
is_distributive: false,
});
let mapped = MappedType {
type_param: key_param,
constraint: keys,
name_type: Some(name_type),
template: TypeId::STRING,
readonly_modifier: None,
optional_modifier: None,
};
let result = evaluate_mapped(&interner, &mapped);
let get_age_name = interner.intern_string("get_age");
let get_name_name = interner.intern_string("get_name");
let expected = interner.object(vec![
PropertyInfo::new(get_age_name, TypeId::STRING),
PropertyInfo::new(get_name_name, TypeId::STRING),
]);
assert_eq!(result, expected);
}
#[test]
fn test_mapped_type_add_both_modifiers_on_source() {
let interner = TypeInterner::new();
let key_value = interner.literal_string("value");
let mapped = MappedType {
type_param: TypeParamInfo {
name: interner.intern_string("K"),
constraint: None,
default: None,
is_const: false,
},
constraint: key_value,
name_type: None,
template: TypeId::STRING,
readonly_modifier: Some(MappedModifier::Add), optional_modifier: Some(MappedModifier::Add), };
let result = evaluate_mapped(&interner, &mapped);
let value_name = interner.intern_string("value");
let expected = interner.object(vec![PropertyInfo {
name: value_name,
type_id: TypeId::STRING,
write_type: TypeId::STRING,
optional: true,
readonly: true,
is_method: false,
visibility: Visibility::Public,
parent_id: None,
}]);
assert_eq!(result, expected);
}
#[test]
fn test_mapped_type_remove_both_modifiers_required_pattern() {
let interner = TypeInterner::new();
let key_data = interner.literal_string("data");
let mapped = MappedType {
type_param: TypeParamInfo {
name: interner.intern_string("K"),
constraint: None,
default: None,
is_const: false,
},
constraint: key_data,
name_type: None,
template: TypeId::STRING,
readonly_modifier: Some(MappedModifier::Remove), optional_modifier: Some(MappedModifier::Remove), };
let result = evaluate_mapped(&interner, &mapped);
let data_name = interner.intern_string("data");
let expected = interner.object(vec![PropertyInfo::new(data_name, TypeId::STRING)]);
assert_eq!(result, expected);
}
#[test]
fn test_mapped_type_key_remap_filter_out_key() {
let interner = TypeInterner::new();
let key_a = interner.literal_string("a");
let key_b = interner.literal_string("b");
let key_c = interner.literal_string("c");
let keys = interner.union(vec![key_a, key_b, key_c]);
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_b,
true_type: TypeId::NEVER,
false_type: key_param_id,
is_distributive: true,
});
let mapped = MappedType {
type_param: key_param,
constraint: keys,
name_type: Some(name_type),
template: TypeId::STRING,
readonly_modifier: None,
optional_modifier: None,
};
let result = evaluate_mapped(&interner, &mapped);
let a_name = interner.intern_string("a");
let c_name = interner.intern_string("c");
let expected = interner.object(vec![
PropertyInfo::new(a_name, TypeId::STRING),
PropertyInfo::new(c_name, TypeId::STRING),
]);
assert_eq!(result, expected);
}
#[test]
fn test_mapped_type_preserves_source_types() {
let interner = TypeInterner::new();
let key_str = interner.literal_string("str");
let key_num = interner.literal_string("num");
let key_bool = interner.literal_string("bool");
let keys = interner.union(vec![key_str, key_num, key_bool]);
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 mapped = MappedType {
type_param: key_param,
constraint: keys,
name_type: None,
template: key_param_id, readonly_modifier: None,
optional_modifier: None,
};
let result = evaluate_mapped(&interner, &mapped);
let str_name = interner.intern_string("str");
let num_name = interner.intern_string("num");
let bool_name = interner.intern_string("bool");
let expected = interner.object(vec![
PropertyInfo::new(bool_name, key_bool),
PropertyInfo::new(num_name, key_num),
PropertyInfo::new(str_name, key_str),
]);
assert_eq!(result, expected);
}
#[test]
fn test_mapped_type_basic_as_clause() {
let interner = TypeInterner::new();
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_a_key = interner.literal_string("a_key");
let key_b_key = interner.literal_string("b_key");
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 inner_cond = interner.conditional(ConditionalType {
check_type: key_param_id,
extends_type: key_b,
true_type: key_b_key,
false_type: TypeId::NEVER,
is_distributive: false,
});
let name_type = interner.conditional(ConditionalType {
check_type: key_param_id,
extends_type: key_a,
true_type: key_a_key,
false_type: inner_cond,
is_distributive: false,
});
let mapped = MappedType {
type_param: key_param,
constraint: keys,
name_type: Some(name_type),
template: TypeId::STRING,
readonly_modifier: None,
optional_modifier: None,
};
let result = evaluate_mapped(&interner, &mapped);
let a_key_name = interner.intern_string("a_key");
let b_key_name = interner.intern_string("b_key");
let expected = interner.object(vec![
PropertyInfo::new(a_key_name, TypeId::STRING),
PropertyInfo::new(b_key_name, TypeId::STRING),
]);
assert_eq!(result, expected);
}
#[test]
fn test_mapped_type_as_extract_specific_keys() {
let interner = TypeInterner::new();
let key_a = interner.literal_string("a");
let key_b = interner.literal_string("b");
let key_c = interner.literal_string("c");
let keys = interner.union(vec![key_a, key_b, key_c]);
let allowed_keys = interner.union(vec![key_a, key_c]);
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: allowed_keys,
true_type: key_param_id,
false_type: TypeId::NEVER,
is_distributive: true,
});
let mapped = MappedType {
type_param: key_param,
constraint: keys,
name_type: Some(name_type),
template: TypeId::NUMBER,
readonly_modifier: None,
optional_modifier: None,
};
let result = evaluate_mapped(&interner, &mapped);
let a_name = interner.intern_string("a");
let c_name = interner.intern_string("c");
let expected = interner.object(vec![
PropertyInfo::new(a_name, TypeId::NUMBER),
PropertyInfo::new(c_name, TypeId::NUMBER),
]);
assert_eq!(result, expected);
}
#[test]
fn test_mapped_type_as_template_literal() {
let interner = TypeInterner::new();
let key_name = interner.literal_string("name");
let key_value = interner.literal_string("value");
let keys = interner.union(vec![key_name, key_value]);
let on_name_change = interner.literal_string("onNameChange");
let on_value_change = interner.literal_string("onValueChange");
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 inner_cond = interner.conditional(ConditionalType {
check_type: key_param_id,
extends_type: key_value,
true_type: on_value_change,
false_type: TypeId::NEVER,
is_distributive: false,
});
let name_type = interner.conditional(ConditionalType {
check_type: key_param_id,
extends_type: key_name,
true_type: on_name_change,
false_type: inner_cond,
is_distributive: false,
});
let void_fn = 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 mapped = MappedType {
type_param: key_param,
constraint: keys,
name_type: Some(name_type),
template: void_fn,
readonly_modifier: None,
optional_modifier: None,
};
let result = evaluate_mapped(&interner, &mapped);
let on_name_change_name = interner.intern_string("onNameChange");
let on_value_change_name = interner.intern_string("onValueChange");
let expected = interner.object(vec![
PropertyInfo::new(on_name_change_name, void_fn),
PropertyInfo::new(on_value_change_name, void_fn),
]);
assert_eq!(result, expected);
}
#[test]
fn test_mapped_type_as_conditional_transformation() {
let interner = TypeInterner::new();
let key_id = interner.literal_string("id");
let key_name = interner.literal_string("name");
let keys = interner.union(vec![key_id, key_name]);
let id_number = interner.literal_string("id_number");
let name_string = interner.literal_string("name_string");
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 inner_cond = interner.conditional(ConditionalType {
check_type: key_param_id,
extends_type: key_name,
true_type: name_string,
false_type: TypeId::NEVER,
is_distributive: false,
});
let name_type = interner.conditional(ConditionalType {
check_type: key_param_id,
extends_type: key_id,
true_type: id_number,
false_type: inner_cond,
is_distributive: false,
});
let mapped = MappedType {
type_param: key_param,
constraint: keys,
name_type: Some(name_type),
template: key_param_id, readonly_modifier: None,
optional_modifier: None,
};
let result = evaluate_mapped(&interner, &mapped);
let id_number_name = interner.intern_string("id_number");
let name_string_name = interner.intern_string("name_string");
let expected = interner.object(vec![
PropertyInfo::new(id_number_name, key_id),
PropertyInfo::new(name_string_name, key_name),
]);
assert_eq!(result, expected);
}
#[test]
fn test_mapped_type_as_exclude_key() {
let interner = TypeInterner::new();
let key_a = interner.literal_string("a");
let key_b = interner.literal_string("b");
let key_c = interner.literal_string("c");
let keys = interner.union(vec![key_a, key_b, key_c]);
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_b,
true_type: TypeId::NEVER,
false_type: key_param_id,
is_distributive: true,
});
let mapped = MappedType {
type_param: key_param,
constraint: keys,
name_type: Some(name_type),
template: TypeId::BOOLEAN,
readonly_modifier: None,
optional_modifier: None,
};
let result = evaluate_mapped(&interner, &mapped);
let a_name = interner.intern_string("a");
let c_name = interner.intern_string("c");
let expected = interner.object(vec![
PropertyInfo::new(a_name, TypeId::BOOLEAN),
PropertyInfo::new(c_name, TypeId::BOOLEAN),
]);
assert_eq!(result, expected);
}
#[test]
fn test_mapped_type_as_identity() {
let interner = TypeInterner::new();
let key_x = interner.literal_string("x");
let key_y = interner.literal_string("y");
let keys = interner.union(vec![key_x, key_y]);
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 mapped = MappedType {
type_param: key_param,
constraint: keys,
name_type: Some(key_param_id), template: TypeId::NUMBER,
readonly_modifier: None,
optional_modifier: None,
};
let result = evaluate_mapped(&interner, &mapped);
let x_name = interner.intern_string("x");
let y_name = interner.intern_string("y");
let expected = interner.object(vec![
PropertyInfo::new(x_name, TypeId::NUMBER),
PropertyInfo::new(y_name, TypeId::NUMBER),
]);
assert_eq!(result, expected);
}
#[test]
fn test_mapped_type_as_never_all_keys() {
let interner = TypeInterner::new();
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 mapped = MappedType {
type_param: key_param,
constraint: keys,
name_type: Some(TypeId::NEVER),
template: TypeId::STRING,
readonly_modifier: None,
optional_modifier: None,
};
let result = evaluate_mapped(&interner, &mapped);
let expected = interner.object(vec![]);
assert_eq!(result, expected);
}
#[test]
fn test_mapped_type_as_single_key() {
let interner = TypeInterner::new();
let key_only = interner.literal_string("only");
let prefix_only = interner.literal_string("prefix_only");
let key_param = TypeParamInfo {
name: interner.intern_string("K"),
constraint: Some(key_only),
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_only,
true_type: prefix_only,
false_type: TypeId::NEVER,
is_distributive: false,
});
let mapped = MappedType {
type_param: key_param,
constraint: key_only,
name_type: Some(name_type),
template: key_param_id,
readonly_modifier: None,
optional_modifier: None,
};
let result = evaluate_mapped(&interner, &mapped);
let prefix_only_name = interner.intern_string("prefix_only");
let expected = interner.object(vec![PropertyInfo::new(prefix_only_name, key_only)]);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_void_check_type() {
let interner = TypeInterner::new();
let lit_true = interner.literal_boolean(true);
let lit_false = interner.literal_boolean(false);
let cond = ConditionalType {
check_type: TypeId::VOID,
extends_type: TypeId::UNDEFINED,
true_type: lit_true,
false_type: lit_false,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, lit_false, "void extends undefined should be false");
}
#[test]
fn test_conditional_null_check_type() {
let interner = TypeInterner::new();
let lit_true = interner.literal_boolean(true);
let lit_false = interner.literal_boolean(false);
let cond = ConditionalType {
check_type: TypeId::NULL,
extends_type: TypeId::OBJECT,
true_type: lit_true,
false_type: lit_false,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, lit_false, "null extends object should be false");
}
#[test]
fn test_conditional_function_extends_function() {
let interner = TypeInterner::new();
let lit_true = interner.literal_boolean(true);
let lit_false = interner.literal_boolean(false);
let void_fn = 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 cond = ConditionalType {
check_type: void_fn,
extends_type: void_fn,
true_type: lit_true,
false_type: lit_false,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(
result, lit_true,
"() => void extends () => void should be true"
);
}
#[test]
fn test_conditional_array_extends_array() {
let interner = TypeInterner::new();
let lit_true = interner.literal_boolean(true);
let lit_false = interner.literal_boolean(false);
let string_array = interner.array(TypeId::STRING);
let any_array = interner.array(TypeId::ANY);
let cond = ConditionalType {
check_type: string_array,
extends_type: any_array,
true_type: lit_true,
false_type: lit_false,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, lit_true, "string[] extends any[] should be true");
}
#[test]
fn test_conditional_tuple_extends_array() {
let interner = TypeInterner::new();
let lit_true = interner.literal_boolean(true);
let lit_false = interner.literal_boolean(false);
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 any_array = interner.array(TypeId::ANY);
let cond = ConditionalType {
check_type: tuple,
extends_type: any_array,
true_type: lit_true,
false_type: lit_false,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(
result, lit_true,
"[string, number] extends any[] should be true"
);
}
#[test]
fn test_conditional_object_structural_subtype() {
let interner = TypeInterner::new();
let lit_true = interner.literal_boolean(true);
let lit_false = interner.literal_boolean(false);
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::NUMBER),
]);
let obj_a = interner.object(vec![PropertyInfo::new(a_name, TypeId::STRING)]);
let cond = ConditionalType {
check_type: obj_ab,
extends_type: obj_a,
true_type: lit_true,
false_type: lit_false,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(
result, lit_true,
"{{a: string, b: number}} extends {{a: string}} should be true"
);
}
#[test]
fn test_conditional_bigint_extends_number() {
let interner = TypeInterner::new();
let lit_true = interner.literal_boolean(true);
let lit_false = interner.literal_boolean(false);
let cond = ConditionalType {
check_type: TypeId::BIGINT,
extends_type: TypeId::NUMBER,
true_type: lit_true,
false_type: lit_false,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, lit_false, "bigint extends number should be false");
}
#[test]
fn test_conditional_symbol_extends_string() {
let interner = TypeInterner::new();
let lit_true = interner.literal_boolean(true);
let lit_false = interner.literal_boolean(false);
let cond = ConditionalType {
check_type: TypeId::SYMBOL,
extends_type: TypeId::STRING,
true_type: lit_true,
false_type: lit_false,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, lit_false, "symbol extends string should be false");
}
#[test]
fn test_conditional_infer_extract_state_pattern() {
let interner = TypeInterner::new();
let infer_s_name = interner.intern_string("S");
let infer_s = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_s_name,
constraint: None,
default: None,
is_const: false,
}));
let any_action = interner.object(vec![PropertyInfo::new(
interner.intern_string("type"),
TypeId::STRING,
)]);
let state_param = interner.union(vec![infer_s, TypeId::UNDEFINED]);
let extends_fn = interner.function(FunctionShape {
type_params: vec![],
params: vec![
ParamInfo {
name: Some(interner.intern_string("state")),
type_id: state_param,
optional: false,
rest: false,
},
ParamInfo {
name: Some(interner.intern_string("action")),
type_id: any_action,
optional: false,
rest: false,
},
],
this_type: None,
return_type: infer_s,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let concrete_state = TypeId::NUMBER;
let concrete_state_param = interner.union(vec![concrete_state, TypeId::UNDEFINED]);
let concrete_reducer = interner.function(FunctionShape {
type_params: vec![],
params: vec![
ParamInfo {
name: Some(interner.intern_string("state")),
type_id: concrete_state_param,
optional: false,
rest: false,
},
ParamInfo {
name: Some(interner.intern_string("action")),
type_id: any_action,
optional: false,
rest: false,
},
],
this_type: None,
return_type: concrete_state,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let cond = ConditionalType {
check_type: concrete_reducer,
extends_type: extends_fn,
true_type: infer_s,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, concrete_state);
}
#[test]
fn test_conditional_infer_extract_action_pattern() {
let interner = TypeInterner::new();
let infer_a_name = interner.intern_string("A");
let infer_a = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_a_name,
constraint: None,
default: None,
is_const: false,
}));
let state_param = interner.union(vec![TypeId::ANY, TypeId::UNDEFINED]);
let extends_fn = interner.function(FunctionShape {
type_params: vec![],
params: vec![
ParamInfo {
name: Some(interner.intern_string("state")),
type_id: state_param,
optional: false,
rest: false,
},
ParamInfo {
name: Some(interner.intern_string("action")),
type_id: infer_a,
optional: false,
rest: false,
},
],
this_type: None,
return_type: TypeId::ANY,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let action_inc = interner.object(vec![PropertyInfo::new(
interner.intern_string("type"),
interner.literal_string("inc"),
)]);
let action_dec = interner.object(vec![PropertyInfo::new(
interner.intern_string("type"),
interner.literal_string("dec"),
)]);
let concrete_action = interner.union(vec![action_inc, action_dec]);
let concrete_state_param = interner.union(vec![TypeId::NUMBER, TypeId::UNDEFINED]);
let concrete_reducer = interner.function(FunctionShape {
type_params: vec![],
params: vec![
ParamInfo {
name: Some(interner.intern_string("state")),
type_id: concrete_state_param,
optional: false,
rest: false,
},
ParamInfo {
name: Some(interner.intern_string("action")),
type_id: concrete_action,
optional: false,
rest: false,
},
],
this_type: None,
return_type: TypeId::NUMBER,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let cond = ConditionalType {
check_type: concrete_reducer,
extends_type: extends_fn,
true_type: infer_a,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, concrete_action);
}
#[test]
fn test_conditional_infer_extract_state_non_matching() {
let interner = TypeInterner::new();
let infer_s_name = interner.intern_string("S");
let infer_s = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_s_name,
constraint: None,
default: None,
is_const: false,
}));
let any_action = interner.object(vec![PropertyInfo::new(
interner.intern_string("type"),
TypeId::STRING,
)]);
let state_param = interner.union(vec![infer_s, TypeId::UNDEFINED]);
let extends_fn = interner.function(FunctionShape {
type_params: vec![],
params: vec![
ParamInfo {
name: Some(interner.intern_string("state")),
type_id: state_param,
optional: false,
rest: false,
},
ParamInfo {
name: Some(interner.intern_string("action")),
type_id: any_action,
optional: false,
rest: false,
},
],
this_type: None,
return_type: infer_s,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let non_reducer = TypeId::STRING;
let cond = ConditionalType {
check_type: non_reducer,
extends_type: extends_fn,
true_type: infer_s,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_conditional_infer_extract_state_union_distributive() {
let interner = TypeInterner::new();
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 infer_s_name = interner.intern_string("S");
let infer_s = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_s_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_fn = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: infer_s,
optional: false,
rest: false,
}],
this_type: None,
return_type: infer_s,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let reducer_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::NUMBER,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let reducer_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::STRING,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_fn,
true_type: infer_s,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
subst.insert(t_name, interner.union(vec![reducer_number, reducer_string]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![TypeId::NUMBER, TypeId::STRING]);
assert_eq!(result, expected);
}
#[test]
fn test_application_ref_expansion_box_string() {
use crate::evaluate::TypeEvaluator;
use crate::subtype::TypeEnvironment;
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 value_name = interner.intern_string("value");
let box_body = interner.object(vec![PropertyInfo::new(value_name, t_type)]);
let box_ref = interner.lazy(DefId(1));
let box_string = interner.application(box_ref, vec![TypeId::STRING]);
let mut env = TypeEnvironment::new();
env.insert_def_with_params(DefId(1), box_body, vec![t_param]);
let mut evaluator = TypeEvaluator::with_resolver(&interner, &env);
let result = evaluator.evaluate(box_string);
let expected = interner.object(vec![PropertyInfo::new(value_name, TypeId::STRING)]);
assert_eq!(
result, expected,
"Box<string> should expand to {{ value: string }}"
);
}
#[test]
fn test_application_ref_expansion_reducer_function() {
use crate::evaluate::TypeEvaluator;
use crate::subtype::TypeEnvironment;
let interner = TypeInterner::new();
let s_name = interner.intern_string("S");
let a_name = interner.intern_string("A");
let s_param = TypeParamInfo {
name: s_name,
constraint: None,
default: None,
is_const: false,
};
let a_param = TypeParamInfo {
name: a_name,
constraint: None,
default: None,
is_const: false,
};
let s_type = interner.intern(TypeData::TypeParameter(s_param.clone()));
let a_type = interner.intern(TypeData::TypeParameter(a_param.clone()));
let state_name = interner.intern_string("state");
let action_name = interner.intern_string("action");
let s_or_undefined = interner.union(vec![s_type, TypeId::UNDEFINED]);
let reducer_body = interner.function(FunctionShape {
type_params: vec![], params: vec![
ParamInfo::required(state_name, s_or_undefined),
ParamInfo::required(action_name, a_type),
],
this_type: None,
return_type: s_type,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let reducer_ref = interner.lazy(DefId(1));
let type_name = interner.intern_string("type");
let any_action = interner.object(vec![PropertyInfo::new(type_name, TypeId::STRING)]);
let reducer_number_action = interner.application(reducer_ref, vec![TypeId::NUMBER, any_action]);
let mut env = TypeEnvironment::new();
env.insert_def_with_params(DefId(1), reducer_body, vec![s_param, a_param]);
let mut evaluator = TypeEvaluator::with_resolver(&interner, &env);
let result = evaluator.evaluate(reducer_number_action);
let number_or_undefined = interner.union(vec![TypeId::NUMBER, TypeId::UNDEFINED]);
let expected = interner.function(FunctionShape {
type_params: vec![],
params: vec![
ParamInfo::required(state_name, number_or_undefined),
ParamInfo::required(action_name, any_action),
],
this_type: None,
return_type: TypeId::NUMBER,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert_eq!(
result, expected,
"Reducer<number, AnyAction> should expand to (state: number | undefined, action: AnyAction) => number"
);
}
#[test]
fn test_application_ref_expansion_nested() {
use crate::evaluate::TypeEvaluator;
use crate::subtype::TypeEnvironment;
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 value_name = interner.intern_string("value");
let box_body = interner.object(vec![PropertyInfo::new(value_name, t_type)]);
let result_name = interner.intern_string("result");
let promise_body = interner.object(vec![PropertyInfo::new(result_name, t_type)]);
let box_ref = interner.lazy(DefId(1));
let promise_ref = interner.lazy(DefId(2));
let box_string = interner.application(box_ref, vec![TypeId::STRING]);
let promise_box_string = interner.application(promise_ref, vec![box_string]);
let mut env = TypeEnvironment::new();
env.insert_def_with_params(DefId(1), box_body, vec![t_param.clone()]);
env.insert_def_with_params(DefId(2), promise_body, vec![t_param]);
let mut evaluator = TypeEvaluator::with_resolver(&interner, &env);
let result = evaluator.evaluate(promise_box_string);
let inner_box = interner.object(vec![PropertyInfo::new(value_name, TypeId::STRING)]);
let expected = interner.object(vec![PropertyInfo::new(result_name, inner_box)]);
assert_eq!(
result, expected,
"Promise<Box<string>> should expand to {{ result: {{ value: string }} }}"
);
}
#[test]
fn test_application_ref_expansion_with_defaults() {
use crate::evaluate::TypeEvaluator;
use crate::subtype::TypeEnvironment;
let interner = TypeInterner::new();
let t_name = interner.intern_string("T");
let d_name = interner.intern_string("D");
let t_param = TypeParamInfo {
name: t_name,
constraint: None,
default: None,
is_const: false,
};
let d_param = TypeParamInfo {
name: d_name,
constraint: None,
default: Some(TypeId::UNDEFINED), is_const: false,
};
let t_type = interner.intern(TypeData::TypeParameter(t_param.clone()));
let d_type = interner.intern(TypeData::TypeParameter(d_param.clone()));
let optional_body = interner.union(vec![t_type, d_type]);
let optional_ref = interner.lazy(DefId(1));
let optional_string = interner.application(optional_ref, vec![TypeId::STRING]);
let optional_string_null =
interner.application(optional_ref, vec![TypeId::STRING, TypeId::NULL]);
let mut env = TypeEnvironment::new();
env.insert_def_with_params(DefId(1), optional_body, vec![t_param, d_param]);
let mut evaluator = TypeEvaluator::with_resolver(&interner, &env);
let result1 = evaluator.evaluate(optional_string);
let expected1 = interner.union(vec![TypeId::STRING, TypeId::UNDEFINED]);
let result2 = evaluator.evaluate(optional_string_null);
let expected2 = interner.union(vec![TypeId::STRING, TypeId::NULL]);
assert_eq!(
result1, expected1,
"Optional<string> should expand to string | undefined (using default)"
);
assert_eq!(
result2, expected2,
"Optional<string, null> should expand to string | null"
);
}
#[test]
fn test_application_ref_expansion_with_constraints() {
use crate::evaluate::TypeEvaluator;
use crate::subtype::TypeEnvironment;
let interner = TypeInterner::new();
let t_name = interner.intern_string("T");
let t_param = TypeParamInfo {
name: t_name,
constraint: Some(TypeId::NUMBER), default: None,
is_const: false,
};
let t_type = interner.intern(TypeData::TypeParameter(t_param.clone()));
let value_name = interner.intern_string("value");
let numeric_box_body = interner.object(vec![PropertyInfo::new(value_name, t_type)]);
let numeric_box_ref = interner.lazy(DefId(1));
let lit_42 = interner.literal_number(42.0);
let numeric_box_42 = interner.application(numeric_box_ref, vec![lit_42]);
let numeric_box_string = interner.application(numeric_box_ref, vec![TypeId::STRING]);
let mut env = TypeEnvironment::new();
env.insert_def_with_params(DefId(1), numeric_box_body, vec![t_param]);
let mut evaluator = TypeEvaluator::with_resolver(&interner, &env);
let result_valid = evaluator.evaluate(numeric_box_42);
let expected_valid = interner.object(vec![PropertyInfo::new(value_name, lit_42)]);
let result_invalid = evaluator.evaluate(numeric_box_string);
let expected_invalid = interner.object(vec![PropertyInfo::new(value_name, TypeId::STRING)]);
assert_eq!(
result_valid, expected_valid,
"NumericBox<42> should expand to {{ value: 42 }}"
);
assert_eq!(
result_invalid, expected_invalid,
"NumericBox<string> should expand to {{ value: string }}, \
constraint checking should happen separately"
);
}
#[test]
fn test_application_ref_expansion_with_never_arg() {
use crate::evaluate::TypeEvaluator;
use crate::subtype::TypeEnvironment;
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 value_name = interner.intern_string("value");
let box_body = interner.object(vec![PropertyInfo::new(value_name, t_type)]);
let box_ref = interner.lazy(DefId(1));
let box_never = interner.application(box_ref, vec![TypeId::NEVER]);
let mut env = TypeEnvironment::new();
env.insert_def_with_params(DefId(1), box_body, vec![t_param]);
let mut evaluator = TypeEvaluator::with_resolver(&interner, &env);
let result = evaluator.evaluate(box_never);
let expected = interner.object(vec![PropertyInfo::new(value_name, TypeId::NEVER)]);
assert_eq!(
result, expected,
"Box<never> should expand to {{ value: never }}"
);
}
#[test]
fn test_application_ref_expansion_with_unknown_arg() {
use crate::evaluate::TypeEvaluator;
use crate::subtype::TypeEnvironment;
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 value_name = interner.intern_string("value");
let box_body = interner.object(vec![PropertyInfo::new(value_name, t_type)]);
let box_ref = interner.lazy(DefId(1));
let box_unknown = interner.application(box_ref, vec![TypeId::UNKNOWN]);
let mut env = TypeEnvironment::new();
env.insert_def_with_params(DefId(1), box_body, vec![t_param]);
let mut evaluator = TypeEvaluator::with_resolver(&interner, &env);
let result = evaluator.evaluate(box_unknown);
let expected = interner.object(vec![PropertyInfo::new(value_name, TypeId::UNKNOWN)]);
assert_eq!(
result, expected,
"Box<unknown> should expand to {{ value: unknown }}"
);
}
#[test]
fn test_application_ref_expansion_with_any_arg() {
use crate::evaluate::TypeEvaluator;
use crate::subtype::TypeEnvironment;
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 value_name = interner.intern_string("value");
let box_body = interner.object(vec![PropertyInfo::new(value_name, t_type)]);
let box_ref = interner.lazy(DefId(1));
let box_any = interner.application(box_ref, vec![TypeId::ANY]);
let mut env = TypeEnvironment::new();
env.insert_def_with_params(DefId(1), box_body, vec![t_param]);
let mut evaluator = TypeEvaluator::with_resolver(&interner, &env);
let result = evaluator.evaluate(box_any);
let expected = interner.object(vec![PropertyInfo::new(value_name, TypeId::ANY)]);
assert_eq!(
result, expected,
"Box<any> should expand to {{ value: any }}"
);
}
#[test]
fn test_application_ref_expansion_with_union_arg() {
use crate::evaluate::TypeEvaluator;
use crate::subtype::TypeEnvironment;
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 value_name = interner.intern_string("value");
let box_body = interner.object(vec![PropertyInfo::new(value_name, t_type)]);
let box_ref = interner.lazy(DefId(1));
let string_or_number = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let box_union = interner.application(box_ref, vec![string_or_number]);
let mut env = TypeEnvironment::new();
env.insert_def_with_params(DefId(1), box_body, vec![t_param]);
let mut evaluator = TypeEvaluator::with_resolver(&interner, &env);
let result = evaluator.evaluate(box_union);
let expected = interner.object(vec![PropertyInfo::new(value_name, string_or_number)]);
assert_eq!(
result, expected,
"Box<string | number> should expand to {{ value: string | number }}"
);
}
#[test]
fn test_application_non_ref_base_passthrough() {
use crate::evaluate::TypeEvaluator;
use crate::subtype::TypeEnvironment;
let interner = TypeInterner::new();
let object_base = interner.object(vec![]);
let weird_application = interner.application(object_base, vec![TypeId::STRING]);
let env = TypeEnvironment::new();
let mut evaluator = TypeEvaluator::with_resolver(&interner, &env);
let result = evaluator.evaluate(weird_application);
assert_eq!(
result, weird_application,
"Application with non-Ref base should pass through unchanged"
);
}
#[test]
fn test_application_ref_expansion_recursive() {
use crate::evaluate::TypeEvaluator;
use crate::subtype::TypeEnvironment;
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 list_def_id = DefId(1);
let list_ref = interner.intern(TypeData::Lazy(list_def_id));
let list_t = interner.application(list_ref, vec![t_type]);
let next_type = interner.union(vec![list_t, TypeId::NULL]);
let value_name = interner.intern_string("value");
let next_name = interner.intern_string("next");
let list_body = interner.object(vec![
PropertyInfo::new(value_name, t_type),
PropertyInfo::new(next_name, next_type),
]);
let list_string = interner.application(list_ref, vec![TypeId::STRING]);
let mut env = TypeEnvironment::new();
env.insert_def_with_params(list_def_id, list_body, vec![t_param]);
let mut evaluator = TypeEvaluator::with_resolver(&interner, &env);
let result = evaluator.evaluate(list_string);
let list_string_inner = interner.application(list_ref, vec![TypeId::STRING]);
let next_type_expected = interner.union(vec![list_string_inner, TypeId::NULL]);
let expected = interner.object(vec![
PropertyInfo::new(value_name, TypeId::STRING),
PropertyInfo::new(next_name, next_type_expected),
]);
assert_eq!(
result, expected,
"List<string> should expand to {{ value: string, next: List<string> | null }}"
);
}
#[test]
fn test_application_ref_expansion_with_intersection_arg() {
use crate::evaluate::TypeEvaluator;
use crate::subtype::TypeEnvironment;
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 value_name = interner.intern_string("value");
let box_body = interner.object(vec![PropertyInfo::new(value_name, t_type)]);
let box_ref = interner.lazy(DefId(1));
let length_name = interner.intern_string("length");
let length_obj = interner.object(vec![PropertyInfo::new(length_name, TypeId::NUMBER)]);
let string_with_length = interner.intersection(vec![TypeId::STRING, length_obj]);
let box_intersection = interner.application(box_ref, vec![string_with_length]);
let mut env = TypeEnvironment::new();
env.insert_def_with_params(DefId(1), box_body, vec![t_param]);
let mut evaluator = TypeEvaluator::with_resolver(&interner, &env);
let result = evaluator.evaluate(box_intersection);
let expected = interner.object(vec![PropertyInfo::new(value_name, string_with_length)]);
assert_eq!(
result, expected,
"Box<string & {{ length: number }}> should expand to {{ value: string & {{ length: number }} }}"
);
}
#[test]
fn test_application_ref_expansion_multi_param() {
use crate::evaluate::TypeEvaluator;
use crate::subtype::TypeEnvironment;
let interner = TypeInterner::new();
let k_name = interner.intern_string("K");
let k_param = TypeParamInfo {
name: k_name,
constraint: None,
default: None,
is_const: false,
};
let k_type = interner.intern(TypeData::TypeParameter(k_param.clone()));
let v_name = interner.intern_string("V");
let v_param = TypeParamInfo {
name: v_name,
constraint: None,
default: None,
is_const: false,
};
let v_type = interner.intern(TypeData::TypeParameter(v_param.clone()));
let key_name = interner.intern_string("key");
let value_name = interner.intern_string("value");
let map_body = interner.object(vec![
PropertyInfo::new(key_name, k_type),
PropertyInfo::new(value_name, v_type),
]);
let map_ref = interner.lazy(DefId(1));
let map_string_number = interner.application(map_ref, vec![TypeId::STRING, TypeId::NUMBER]);
let mut env = TypeEnvironment::new();
env.insert_def_with_params(DefId(1), map_body, vec![k_param, v_param]);
let mut evaluator = TypeEvaluator::with_resolver(&interner, &env);
let result = evaluator.evaluate(map_string_number);
let expected = interner.object(vec![
PropertyInfo::new(key_name, TypeId::STRING),
PropertyInfo::new(value_name, TypeId::NUMBER),
]);
assert_eq!(
result, expected,
"Map<string, number> should expand to {{ key: string, value: number }}"
);
}
#[test]
fn test_application_ref_expansion_with_conditional_body() {
use crate::evaluate::TypeEvaluator;
use crate::subtype::TypeEnvironment;
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 conditional_body = interner.conditional(ConditionalType {
check_type: t_type,
extends_type: TypeId::STRING,
true_type: TypeId::STRING, false_type: TypeId::NUMBER, is_distributive: false,
});
let is_string_ref = interner.lazy(DefId(1));
let is_string_string = interner.application(is_string_ref, vec![TypeId::STRING]);
let is_string_number = interner.application(is_string_ref, vec![TypeId::NUMBER]);
let mut env = TypeEnvironment::new();
env.insert_def_with_params(DefId(1), conditional_body, vec![t_param]);
let mut evaluator = TypeEvaluator::with_resolver(&interner, &env);
let result_string = evaluator.evaluate(is_string_string);
let result_number = evaluator.evaluate(is_string_number);
assert_eq!(
result_string,
TypeId::STRING,
"IsString<string> should evaluate to string (true branch)"
);
assert_eq!(
result_number,
TypeId::NUMBER,
"IsString<number> should evaluate to number (false branch)"
);
}
#[test]
fn test_application_ref_expansion_with_tuple_arg() {
use crate::evaluate::TypeEvaluator;
use crate::subtype::TypeEnvironment;
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 value_name = interner.intern_string("value");
let box_body = interner.object(vec![PropertyInfo::new(value_name, t_type)]);
let box_ref = interner.lazy(DefId(1));
let tuple_type = 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 box_tuple = interner.application(box_ref, vec![tuple_type]);
let mut env = TypeEnvironment::new();
env.insert_def_with_params(DefId(1), box_body, vec![t_param]);
let mut evaluator = TypeEvaluator::with_resolver(&interner, &env);
let result = evaluator.evaluate(box_tuple);
let expected = interner.object(vec![PropertyInfo::new(value_name, tuple_type)]);
assert_eq!(
result, expected,
"Box<[string, number]> should expand to {{ value: [string, number] }}"
);
}
#[test]
fn test_application_ref_expansion_with_array_body() {
use crate::evaluate::TypeEvaluator;
use crate::subtype::TypeEnvironment;
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 array_body = interner.array(t_type);
let array_of_ref = interner.lazy(DefId(1));
let array_of_string = interner.application(array_of_ref, vec![TypeId::STRING]);
let mut env = TypeEnvironment::new();
env.insert_def_with_params(DefId(1), array_body, vec![t_param]);
let mut evaluator = TypeEvaluator::with_resolver(&interner, &env);
let result = evaluator.evaluate(array_of_string);
let expected = interner.array(TypeId::STRING);
assert_eq!(
result, expected,
"ArrayOf<string> should expand to string[]"
);
}
#[test]
fn test_application_ref_expansion_with_readonly_property() {
use crate::evaluate::TypeEvaluator;
use crate::subtype::TypeEnvironment;
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 value_name = interner.intern_string("value");
let readonly_box_body = interner.object(vec![PropertyInfo {
name: value_name,
type_id: t_type,
write_type: t_type,
optional: false,
readonly: true, is_method: false,
visibility: Visibility::Public,
parent_id: None,
}]);
let readonly_box_ref = interner.lazy(DefId(1));
let readonly_box_number = interner.application(readonly_box_ref, vec![TypeId::NUMBER]);
let mut env = TypeEnvironment::new();
env.insert_def_with_params(DefId(1), readonly_box_body, vec![t_param]);
let mut evaluator = TypeEvaluator::with_resolver(&interner, &env);
let result = evaluator.evaluate(readonly_box_number);
let expected = interner.object(vec![PropertyInfo::readonly(value_name, TypeId::NUMBER)]);
assert_eq!(
result, expected,
"ReadonlyBox<number> should expand to {{ readonly value: number }}"
);
}
#[test]
fn test_application_ref_expansion_with_optional_property() {
use crate::evaluate::TypeEvaluator;
use crate::subtype::TypeEnvironment;
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 value_name = interner.intern_string("value");
let optional_box_body = interner.object(vec![PropertyInfo {
name: value_name,
type_id: t_type,
write_type: t_type,
optional: true, readonly: false,
is_method: false,
visibility: Visibility::Public,
parent_id: None,
}]);
let optional_box_ref = interner.lazy(DefId(1));
let optional_box_string = interner.application(optional_box_ref, vec![TypeId::STRING]);
let mut env = TypeEnvironment::new();
env.insert_def_with_params(DefId(1), optional_box_body, vec![t_param]);
let mut evaluator = TypeEvaluator::with_resolver(&interner, &env);
let result = evaluator.evaluate(optional_box_string);
let expected = interner.object(vec![PropertyInfo::opt(value_name, TypeId::STRING)]);
assert_eq!(
result, expected,
"OptionalBox<string> should expand to {{ value?: string }}"
);
}
#[test]
fn test_application_ref_expansion_with_method() {
use crate::evaluate::TypeEvaluator;
use crate::subtype::TypeEnvironment;
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 method_type = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: t_type,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let get_name = interner.intern_string("get");
let with_method_body = interner.object(vec![PropertyInfo::method(get_name, method_type)]);
let with_method_ref = interner.lazy(DefId(1));
let with_method_boolean = interner.application(with_method_ref, vec![TypeId::BOOLEAN]);
let mut env = TypeEnvironment::new();
env.insert_def_with_params(DefId(1), with_method_body, vec![t_param]);
let mut evaluator = TypeEvaluator::with_resolver(&interner, &env);
let result = evaluator.evaluate(with_method_boolean);
let expected_method_type = 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 expected = interner.object(vec![PropertyInfo::method(get_name, expected_method_type)]);
assert_eq!(
result, expected,
"WithMethod<boolean> should expand to {{ get(): boolean }}"
);
}
#[test]
fn test_application_ref_expansion_with_rest_param() {
use crate::evaluate::TypeEvaluator;
use crate::subtype::TypeEnvironment;
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 args_name = interner.intern_string("args");
let t_array = interner.array(t_type);
let varargs_body = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(args_name),
type_id: t_array,
optional: false,
rest: true, }],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let varargs_ref = interner.lazy(DefId(1));
let varargs_string = interner.application(varargs_ref, vec![TypeId::STRING]);
let mut env = TypeEnvironment::new();
env.insert_def_with_params(DefId(1), varargs_body, vec![t_param]);
let mut evaluator = TypeEvaluator::with_resolver(&interner, &env);
let result = evaluator.evaluate(varargs_string);
let string_array = interner.array(TypeId::STRING);
let expected = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo::rest(args_name, string_array)],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert_eq!(
result, expected,
"VarArgs<string> should expand to (...args: string[]) => void"
);
}
#[test]
fn test_application_ref_expansion_with_index_signature() {
use crate::evaluate::TypeEvaluator;
use crate::subtype::TypeEnvironment;
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 dict_body = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: t_type,
readonly: false,
}),
number_index: None,
});
let dict_ref = interner.lazy(DefId(1));
let dict_number = interner.application(dict_ref, vec![TypeId::NUMBER]);
let mut env = TypeEnvironment::new();
env.insert_def_with_params(DefId(1), dict_body, vec![t_param]);
let mut evaluator = TypeEvaluator::with_resolver(&interner, &env);
let result = evaluator.evaluate(dict_number);
let expected = 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_eq!(
result, expected,
"Dict<number> should expand to {{ [key: string]: number }}"
);
}
#[test]
fn test_application_ref_expansion_with_number_index_signature() {
use crate::evaluate::TypeEvaluator;
use crate::subtype::TypeEnvironment;
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 numeric_dict_body = 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: t_type,
readonly: false,
}),
});
let numeric_dict_ref = interner.lazy(DefId(1));
let numeric_dict_string = interner.application(numeric_dict_ref, vec![TypeId::STRING]);
let mut env = TypeEnvironment::new();
env.insert_def_with_params(DefId(1), numeric_dict_body, vec![t_param]);
let mut evaluator = TypeEvaluator::with_resolver(&interner, &env);
let result = evaluator.evaluate(numeric_dict_string);
let expected = 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_eq!(
result, expected,
"NumericDict<string> should expand to {{ [index: number]: string }}"
);
}
#[test]
fn test_application_ref_expansion_with_literal_arg() {
use crate::evaluate::TypeEvaluator;
use crate::subtype::TypeEnvironment;
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 value_name = interner.intern_string("value");
let box_body = interner.object(vec![PropertyInfo::new(value_name, t_type)]);
let box_ref = interner.lazy(DefId(1));
let hello_literal = interner.literal_string("hello");
let box_hello = interner.application(box_ref, vec![hello_literal]);
let mut env = TypeEnvironment::new();
env.insert_def_with_params(DefId(1), box_body, vec![t_param]);
let mut evaluator = TypeEvaluator::with_resolver(&interner, &env);
let result = evaluator.evaluate(box_hello);
let expected = interner.object(vec![PropertyInfo::new(value_name, hello_literal)]);
assert_eq!(
result, expected,
"Box<\"hello\"> should expand to {{ value: \"hello\" }}"
);
}
#[test]
fn test_application_ref_expansion_with_numeric_literal_arg() {
use crate::evaluate::TypeEvaluator;
use crate::subtype::TypeEnvironment;
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 value_name = interner.intern_string("value");
let box_body = interner.object(vec![PropertyInfo::new(value_name, t_type)]);
let box_ref = interner.lazy(DefId(1));
let lit_42 = interner.literal_number(42.0);
let box_42 = interner.application(box_ref, vec![lit_42]);
let mut env = TypeEnvironment::new();
env.insert_def_with_params(DefId(1), box_body, vec![t_param]);
let mut evaluator = TypeEvaluator::with_resolver(&interner, &env);
let result = evaluator.evaluate(box_42);
let expected = interner.object(vec![PropertyInfo::new(value_name, lit_42)]);
assert_eq!(result, expected, "Box<42> should expand to {{ value: 42 }}");
}
#[test]
fn test_application_ref_expansion_with_multiple_refs_to_same_param() {
use crate::evaluate::TypeEvaluator;
use crate::subtype::TypeEnvironment;
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 first_name = interner.intern_string("first");
let second_name = interner.intern_string("second");
let pair_body = interner.object(vec![
PropertyInfo::new(first_name, t_type),
PropertyInfo::new(second_name, t_type),
]);
let pair_ref = interner.lazy(DefId(1));
let pair_string = interner.application(pair_ref, vec![TypeId::STRING]);
let mut env = TypeEnvironment::new();
env.insert_def_with_params(DefId(1), pair_body, vec![t_param]);
let mut evaluator = TypeEvaluator::with_resolver(&interner, &env);
let result = evaluator.evaluate(pair_string);
let expected = interner.object(vec![
PropertyInfo::new(first_name, TypeId::STRING),
PropertyInfo::new(second_name, TypeId::STRING),
]);
assert_eq!(
result, expected,
"Pair<string> should expand to {{ first: string; second: string }}"
);
}
#[test]
fn test_application_ref_expansion_with_boolean_literal_arg() {
use crate::evaluate::TypeEvaluator;
use crate::subtype::TypeEnvironment;
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 value_name = interner.intern_string("value");
let box_body = interner.object(vec![PropertyInfo::new(value_name, t_type)]);
let box_ref = interner.lazy(DefId(1));
let lit_true = interner.literal_boolean(true);
let box_true = interner.application(box_ref, vec![lit_true]);
let mut env = TypeEnvironment::new();
env.insert_def_with_params(DefId(1), box_body, vec![t_param]);
let mut evaluator = TypeEvaluator::with_resolver(&interner, &env);
let result = evaluator.evaluate(box_true);
let expected = interner.object(vec![PropertyInfo::new(value_name, lit_true)]);
assert_eq!(
result, expected,
"Box<true> should expand to {{ value: true }}"
);
}
#[test]
fn test_application_ref_expansion_with_union_body() {
use crate::evaluate::TypeEvaluator;
use crate::subtype::TypeEnvironment;
let interner = TypeInterner::new();
let l_name = interner.intern_string("L");
let r_name = interner.intern_string("R");
let l_param = TypeParamInfo {
name: l_name,
constraint: None,
default: None,
is_const: false,
};
let r_param = TypeParamInfo {
name: r_name,
constraint: None,
default: None,
is_const: false,
};
let l_type = interner.intern(TypeData::TypeParameter(l_param.clone()));
let r_type = interner.intern(TypeData::TypeParameter(r_param.clone()));
let either_body = interner.union(vec![l_type, r_type]);
let either_ref = interner.lazy(DefId(1));
let either_string_number =
interner.application(either_ref, vec![TypeId::STRING, TypeId::NUMBER]);
let mut env = TypeEnvironment::new();
env.insert_def_with_params(DefId(1), either_body, vec![l_param, r_param]);
let mut evaluator = TypeEvaluator::with_resolver(&interner, &env);
let result = evaluator.evaluate(either_string_number);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert_eq!(
result, expected,
"Either<string, number> should expand to string | number"
);
}
#[test]
fn test_application_ref_expansion_with_intersection_body() {
use crate::evaluate::TypeEvaluator;
use crate::subtype::TypeEnvironment;
let interner = TypeInterner::new();
let a_name = interner.intern_string("A");
let b_name = interner.intern_string("B");
let a_param = TypeParamInfo {
name: a_name,
constraint: None,
default: None,
is_const: false,
};
let b_param = TypeParamInfo {
name: b_name,
constraint: None,
default: None,
is_const: false,
};
let a_type = interner.intern(TypeData::TypeParameter(a_param.clone()));
let b_type = interner.intern(TypeData::TypeParameter(b_param.clone()));
let both_body = interner.intersection(vec![a_type, b_type]);
let both_ref = interner.lazy(DefId(1));
let x_name = interner.intern_string("x");
let y_name = interner.intern_string("y");
let obj_x = interner.object(vec![PropertyInfo::new(x_name, TypeId::NUMBER)]);
let obj_y = interner.object(vec![PropertyInfo::new(y_name, TypeId::STRING)]);
let both_xy = interner.application(both_ref, vec![obj_x, obj_y]);
let mut env = TypeEnvironment::new();
env.insert_def_with_params(DefId(1), both_body, vec![a_param, b_param]);
let mut evaluator = TypeEvaluator::with_resolver(&interner, &env);
let result = evaluator.evaluate(both_xy);
let expected = interner.intersection(vec![obj_x, obj_y]);
assert_eq!(
result, expected,
"Both<{{x: number}}, {{y: string}}> should expand to {{x: number}} & {{y: string}}"
);
}
#[test]
fn test_application_ref_expansion_with_this_param() {
use crate::evaluate::TypeEvaluator;
use crate::subtype::TypeEnvironment;
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 bound_method_body = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: Some(t_type), return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let bound_method_ref = interner.lazy(DefId(1));
let x_name = interner.intern_string("x");
let obj_x = interner.object(vec![PropertyInfo::new(x_name, TypeId::NUMBER)]);
let bound_method_obj = interner.application(bound_method_ref, vec![obj_x]);
let mut env = TypeEnvironment::new();
env.insert_def_with_params(DefId(1), bound_method_body, vec![t_param]);
let mut evaluator = TypeEvaluator::with_resolver(&interner, &env);
let result = evaluator.evaluate(bound_method_obj);
let expected = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: Some(obj_x),
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert_eq!(
result, expected,
"BoundMethod<{{x: number}}> should expand to (this: {{x: number}}) => void"
);
}
#[test]
fn test_application_ref_expansion_with_optional_param() {
use crate::evaluate::TypeEvaluator;
use crate::subtype::TypeEnvironment;
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 x_name = interner.intern_string("x");
let optional_fn_body = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(x_name),
type_id: t_type,
optional: true, rest: false,
}],
this_type: None,
return_type: t_type,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let optional_fn_ref = interner.lazy(DefId(1));
let optional_fn_string = interner.application(optional_fn_ref, vec![TypeId::STRING]);
let mut env = TypeEnvironment::new();
env.insert_def_with_params(DefId(1), optional_fn_body, vec![t_param]);
let mut evaluator = TypeEvaluator::with_resolver(&interner, &env);
let result = evaluator.evaluate(optional_fn_string);
let expected = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo::optional(x_name, TypeId::STRING)],
this_type: None,
return_type: TypeId::STRING,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert_eq!(
result, expected,
"OptionalFn<string> should expand to (x?: string) => string"
);
}
#[test]
fn test_application_ref_expansion_with_readonly_array_body() {
use crate::evaluate::TypeEvaluator;
use crate::subtype::TypeEnvironment;
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 t_array = interner.array(t_type);
let readonly_array_body = interner.intern(TypeData::ReadonlyType(t_array));
let readonly_array_ref = interner.lazy(DefId(1));
let readonly_array_number = interner.application(readonly_array_ref, vec![TypeId::NUMBER]);
let mut env = TypeEnvironment::new();
env.insert_def_with_params(DefId(1), readonly_array_body, vec![t_param]);
let mut evaluator = TypeEvaluator::with_resolver(&interner, &env);
let result = evaluator.evaluate(readonly_array_number);
let number_array = interner.array(TypeId::NUMBER);
let expected = interner.intern(TypeData::ReadonlyType(number_array));
assert_eq!(
result, expected,
"ReadonlyArrayOf<number> should expand to readonly number[]"
);
}
#[test]
fn test_application_ref_expansion_with_mixed_modifiers() {
use crate::evaluate::TypeEvaluator;
use crate::subtype::TypeEnvironment;
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 id_name = interner.intern_string("id");
let value_name = interner.intern_string("value");
let config_body = interner.object(vec![
PropertyInfo {
name: id_name,
type_id: TypeId::STRING,
write_type: TypeId::STRING,
optional: false,
readonly: true, is_method: false,
visibility: Visibility::Public,
parent_id: None,
},
PropertyInfo {
name: value_name,
type_id: t_type,
write_type: t_type,
optional: true, readonly: false,
is_method: false,
visibility: Visibility::Public,
parent_id: None,
},
]);
let config_ref = interner.lazy(DefId(1));
let config_number = interner.application(config_ref, vec![TypeId::NUMBER]);
let mut env = TypeEnvironment::new();
env.insert_def_with_params(DefId(1), config_body, vec![t_param]);
let mut evaluator = TypeEvaluator::with_resolver(&interner, &env);
let result = evaluator.evaluate(config_number);
let expected = interner.object(vec![
PropertyInfo::readonly(id_name, TypeId::STRING),
PropertyInfo::opt(value_name, TypeId::NUMBER),
]);
assert_eq!(
result, expected,
"Config<number> should expand to {{ readonly id: string; value?: number }}"
);
}
#[test]
fn test_application_ref_expansion_with_callable_body() {
use crate::evaluate::TypeEvaluator;
use crate::subtype::TypeEnvironment;
let interner = TypeInterner::new();
let t_name = interner.intern_string("T");
let r_name = interner.intern_string("R");
let t_param = TypeParamInfo {
name: t_name,
constraint: None,
default: None,
is_const: false,
};
let r_param = TypeParamInfo {
name: r_name,
constraint: None,
default: None,
is_const: false,
};
let t_type = interner.intern(TypeData::TypeParameter(t_param.clone()));
let r_type = interner.intern(TypeData::TypeParameter(r_param.clone()));
let arg_name = interner.intern_string("arg");
let call_sig = CallSignature {
type_params: vec![],
params: vec![ParamInfo::required(arg_name, t_type)],
this_type: None,
return_type: r_type,
type_predicate: None,
is_method: false,
};
let callback_body = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![call_sig],
construct_signatures: vec![],
properties: vec![],
..Default::default()
});
let callback_ref = interner.lazy(DefId(1));
let callback_string_bool =
interner.application(callback_ref, vec![TypeId::STRING, TypeId::BOOLEAN]);
let mut env = TypeEnvironment::new();
env.insert_def_with_params(DefId(1), callback_body, vec![t_param, r_param]);
let mut evaluator = TypeEvaluator::with_resolver(&interner, &env);
let result = evaluator.evaluate(callback_string_bool);
let expected_call_sig = CallSignature {
type_params: vec![],
params: vec![ParamInfo::required(arg_name, TypeId::STRING)],
this_type: None,
return_type: TypeId::BOOLEAN,
type_predicate: None,
is_method: false,
};
let expected = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![expected_call_sig],
construct_signatures: vec![],
properties: vec![],
..Default::default()
});
assert_eq!(
result, expected,
"Callback<string, boolean> should expand to {{ (arg: string): boolean }}"
);
}
#[test]
fn test_application_ref_expansion_with_construct_signature() {
use crate::evaluate::TypeEvaluator;
use crate::subtype::TypeEnvironment;
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 construct_sig = CallSignature {
type_params: vec![],
params: vec![],
this_type: None,
return_type: t_type,
type_predicate: None,
is_method: false,
};
let constructor_body = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![],
construct_signatures: vec![construct_sig],
properties: vec![],
..Default::default()
});
let constructor_ref = interner.lazy(DefId(1));
let x_name = interner.intern_string("x");
let obj_x = interner.object(vec![PropertyInfo::new(x_name, TypeId::NUMBER)]);
let constructor_obj = interner.application(constructor_ref, vec![obj_x]);
let mut env = TypeEnvironment::new();
env.insert_def_with_params(DefId(1), constructor_body, vec![t_param]);
let mut evaluator = TypeEvaluator::with_resolver(&interner, &env);
let result = evaluator.evaluate(constructor_obj);
let expected_construct_sig = CallSignature {
type_params: vec![],
params: vec![],
this_type: None,
return_type: obj_x,
type_predicate: None,
is_method: false,
};
let expected = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![],
construct_signatures: vec![expected_construct_sig],
properties: vec![],
..Default::default()
});
assert_eq!(
result, expected,
"Constructor<{{x: number}}> should expand to {{ new (): {{x: number}} }}"
);
}
#[test]
fn test_application_ref_expansion_with_deeply_nested_param() {
use crate::evaluate::TypeEvaluator;
use crate::subtype::TypeEnvironment;
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 value_name = interner.intern_string("value");
let inner_obj = interner.object(vec![PropertyInfo::new(value_name, t_type)]);
let inner_name = interner.intern_string("inner");
let wrapper_body = interner.object(vec![PropertyInfo::new(inner_name, inner_obj)]);
let wrapper_ref = interner.lazy(DefId(1));
let wrapper_string = interner.application(wrapper_ref, vec![TypeId::STRING]);
let mut env = TypeEnvironment::new();
env.insert_def_with_params(DefId(1), wrapper_body, vec![t_param]);
let mut evaluator = TypeEvaluator::with_resolver(&interner, &env);
let result = evaluator.evaluate(wrapper_string);
let expected_inner = interner.object(vec![PropertyInfo::new(value_name, TypeId::STRING)]);
let expected = interner.object(vec![PropertyInfo::new(inner_name, expected_inner)]);
assert_eq!(
result, expected,
"Wrapper<string> should expand to {{ inner: {{ value: string }} }}"
);
}
#[test]
fn test_conditional_unknown_check_type() {
let interner = TypeInterner::new();
let lit_true = interner.literal_boolean(true);
let lit_false = interner.literal_boolean(false);
let cond = ConditionalType {
check_type: TypeId::UNKNOWN,
extends_type: TypeId::STRING,
true_type: lit_true,
false_type: lit_false,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, lit_false, "unknown extends string should be false");
}
#[test]
fn test_conditional_unknown_extends_unknown() {
let interner = TypeInterner::new();
let lit_true = interner.literal_boolean(true);
let lit_false = interner.literal_boolean(false);
let cond = ConditionalType {
check_type: TypeId::UNKNOWN,
extends_type: TypeId::UNKNOWN,
true_type: lit_true,
false_type: lit_false,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, lit_true, "unknown extends unknown should be true");
}
#[test]
fn test_conditional_intersection_check_type() {
let interner = TypeInterner::new();
let lit_true = interner.literal_boolean(true);
let lit_false = interner.literal_boolean(false);
let length_name = interner.intern_string("length");
let length_obj = interner.object(vec![PropertyInfo::new(length_name, TypeId::NUMBER)]);
let string_intersection = interner.intersection(vec![TypeId::STRING, length_obj]);
let cond = ConditionalType {
check_type: string_intersection,
extends_type: TypeId::STRING,
true_type: lit_true,
false_type: lit_false,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(
result, lit_true,
"string intersection extends string should be true"
);
}
#[test]
fn test_conditional_never_check_type_non_distributive() {
let interner = TypeInterner::new();
let lit_true = interner.literal_boolean(true);
let lit_false = interner.literal_boolean(false);
let cond = ConditionalType {
check_type: TypeId::NEVER,
extends_type: TypeId::STRING,
true_type: lit_true,
false_type: lit_false,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(
result, lit_true,
"never extends string (non-distributive) should be true"
);
}
#[test]
fn test_conditional_extends_never() {
let interner = TypeInterner::new();
let lit_true = interner.literal_boolean(true);
let lit_false = interner.literal_boolean(false);
let cond = ConditionalType {
check_type: TypeId::STRING,
extends_type: TypeId::NEVER,
true_type: lit_true,
false_type: lit_false,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, lit_false, "string extends never should be false");
}
#[test]
fn test_conditional_never_extends_never() {
let interner = TypeInterner::new();
let lit_true = interner.literal_boolean(true);
let lit_false = interner.literal_boolean(false);
let cond = ConditionalType {
check_type: TypeId::NEVER,
extends_type: TypeId::NEVER,
true_type: lit_true,
false_type: lit_false,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, lit_true, "never extends never should be true");
}
#[test]
fn test_conditional_infer_tuple_multiple_positions() {
let interner = TypeInterner::new();
let tuple_type = 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 a_name = interner.intern_string("A");
let b_name = interner.intern_string("B");
let infer_a = interner.intern(TypeData::Infer(TypeParamInfo {
name: a_name,
constraint: None,
default: None,
is_const: false,
}));
let infer_b = interner.intern(TypeData::Infer(TypeParamInfo {
name: b_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = interner.tuple(vec![
TupleElement {
type_id: infer_a,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: infer_b,
name: None,
optional: false,
rest: false,
},
]);
let swapped = interner.tuple(vec![
TupleElement {
type_id: infer_b,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: infer_a,
name: None,
optional: false,
rest: false,
},
]);
let cond = ConditionalType {
check_type: tuple_type,
extends_type: pattern,
true_type: swapped,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
let expected = 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_eq!(
result, expected,
"[string, number] with [infer A, infer B] should swap to [number, string]"
);
}
#[test]
fn test_conditional_nested_in_true_branch() {
let interner = TypeInterner::new();
let hello_lit = interner.literal_string("hello");
let greeting_lit = interner.literal_string("greeting");
let other_lit = interner.literal_string("other");
let not_string_lit = interner.literal_string("not string");
let inner_cond = interner.conditional(ConditionalType {
check_type: hello_lit,
extends_type: hello_lit,
true_type: greeting_lit,
false_type: other_lit,
is_distributive: false,
});
let outer_cond = ConditionalType {
check_type: hello_lit,
extends_type: TypeId::STRING,
true_type: inner_cond,
false_type: not_string_lit,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &outer_cond);
assert_eq!(
result, greeting_lit,
"nested conditional should resolve to 'greeting'"
);
}
#[test]
fn test_conditional_distributive_literal_union() {
let interner = TypeInterner::new();
let lit_a = interner.literal_string("a");
let lit_b = interner.literal_string("b");
let lit_c = interner.literal_string("c");
let yes_lit = interner.literal_string("yes");
let no_lit = interner.literal_string("no");
let abc_union = interner.union(vec![lit_a, lit_b, lit_c]);
let cond = ConditionalType {
check_type: abc_union,
extends_type: lit_a,
true_type: yes_lit,
false_type: no_lit,
is_distributive: true,
};
let result = evaluate_conditional(&interner, &cond);
let expected = interner.union(vec![yes_lit, no_lit]);
assert_eq!(
result, expected,
"distributive over literal union should produce 'yes' | 'no'"
);
}
#[test]
fn test_conditional_extends_any() {
let interner = TypeInterner::new();
let lit_true = interner.literal_boolean(true);
let lit_false = interner.literal_boolean(false);
let cond = ConditionalType {
check_type: TypeId::STRING,
extends_type: TypeId::ANY,
true_type: lit_true,
false_type: lit_false,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, lit_true, "string extends any should be true");
}
#[test]
fn test_conditional_infer_constraint_mismatch_edge() {
let interner = TypeInterner::new();
let x_name = interner.intern_string("x");
let t_name = interner.intern_string("T");
let obj_number = interner.object(vec![PropertyInfo::new(x_name, TypeId::NUMBER)]);
let infer_t = interner.intern(TypeData::Infer(TypeParamInfo {
name: t_name,
constraint: Some(TypeId::STRING),
default: None,
is_const: false,
}));
let pattern = interner.object(vec![PropertyInfo::new(x_name, infer_t)]);
let cond = ConditionalType {
check_type: obj_number,
extends_type: pattern,
true_type: infer_t,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(
result,
TypeId::NEVER,
"infer with mismatched constraint should produce never"
);
}
#[test]
fn test_template_literal_hyphen_prefix_extraction() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_template = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("hello-")),
TemplateSpan::Type(infer_r),
]);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_template,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
subst.insert(t_name, interner.literal_string("hello-world"));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.literal_string("world");
assert_eq!(result, expected);
}
#[test]
fn test_template_literal_hyphen_two_part_extraction() {
let interner = TypeInterner::new();
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 infer_first = interner.intern(TypeData::Infer(TypeParamInfo {
name: interner.intern_string("First"),
constraint: None,
default: None,
is_const: false,
}));
let infer_rest = interner.intern(TypeData::Infer(TypeParamInfo {
name: interner.intern_string("Rest"),
constraint: None,
default: None,
is_const: false,
}));
let extends_template = interner.template_literal(vec![
TemplateSpan::Type(infer_first),
TemplateSpan::Text(interner.intern_string("-")),
TemplateSpan::Type(infer_rest),
]);
let true_type = interner.tuple(vec![
TupleElement {
type_id: infer_first,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: infer_rest,
name: None,
optional: false,
rest: false,
},
]);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_template,
true_type,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
subst.insert(t_name, interner.literal_string("foo-bar-baz"));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.tuple(vec![
TupleElement {
type_id: interner.literal_string("foo"),
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: interner.literal_string("bar-baz"),
name: None,
optional: false,
rest: false,
},
]);
assert_eq!(result, expected);
}
#[test]
fn test_template_literal_hyphen_suffix_pattern() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_template = interner.template_literal(vec![
TemplateSpan::Type(infer_r),
TemplateSpan::Text(interner.intern_string("-handler")),
]);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_template,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
subst.insert(t_name, interner.literal_string("click-handler"));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.literal_string("click");
assert_eq!(result, expected);
}
#[test]
fn test_template_literal_hyphen_distributive_union() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_template = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("event-")),
TemplateSpan::Type(infer_r),
]);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_template,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let lit_click = interner.literal_string("event-click");
let lit_load = interner.literal_string("event-load");
let mut subst = TypeSubstitution::new();
subst.insert(t_name, interner.union(vec![lit_click, lit_load]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![
interner.literal_string("click"),
interner.literal_string("load"),
]);
assert_eq!(result, expected);
}
#[test]
fn test_template_literal_hyphen_no_match_returns_never() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_template = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("prefix-")),
TemplateSpan::Type(infer_r),
]);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_template,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
subst.insert(t_name, interner.literal_string("other-value"));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_template_literal_prefix_infer_suffix_extraction() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("M");
let infer_m = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_template = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("start-")),
TemplateSpan::Type(infer_m),
TemplateSpan::Text(interner.intern_string("-end")),
]);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_template,
true_type: infer_m,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
subst.insert(t_name, interner.literal_string("start-middle-end"));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.literal_string("middle");
assert_eq!(result, expected);
}
#[test]
fn test_template_literal_prefix_infer_suffix_multiple_hyphens() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("Route");
let infer_route = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_template = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("api-")),
TemplateSpan::Type(infer_route),
TemplateSpan::Text(interner.intern_string("-handler")),
]);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_template,
true_type: infer_route,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
subst.insert(t_name, interner.literal_string("api-user-profile-handler"));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.literal_string("user-profile");
assert_eq!(result, expected);
}
#[test]
fn test_template_literal_prefix_infer_suffix_distributive() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("E");
let infer_e = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_template = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("on-")),
TemplateSpan::Type(infer_e),
TemplateSpan::Text(interner.intern_string("-event")),
]);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_template,
true_type: infer_e,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let lit_click = interner.literal_string("on-click-event");
let lit_load = interner.literal_string("on-load-event");
let mut subst = TypeSubstitution::new();
subst.insert(t_name, interner.union(vec![lit_click, lit_load]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![
interner.literal_string("click"),
interner.literal_string("load"),
]);
assert_eq!(result, expected);
}
#[test]
fn test_template_literal_extract_numeric_id() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("Id");
let infer_id = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_template = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("user-")),
TemplateSpan::Type(infer_id),
]);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_template,
true_type: infer_id,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
subst.insert(t_name, interner.literal_string("user-42"));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.literal_string("42");
assert_eq!(result, expected);
}
#[test]
fn test_template_literal_extract_version_numbers() {
let interner = TypeInterner::new();
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 infer_major = interner.intern(TypeData::Infer(TypeParamInfo {
name: interner.intern_string("Major"),
constraint: None,
default: None,
is_const: false,
}));
let infer_minor = interner.intern(TypeData::Infer(TypeParamInfo {
name: interner.intern_string("Minor"),
constraint: None,
default: None,
is_const: false,
}));
let extends_template = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("v")),
TemplateSpan::Type(infer_major),
TemplateSpan::Text(interner.intern_string(".")),
TemplateSpan::Type(infer_minor),
]);
let true_type = interner.tuple(vec![
TupleElement {
type_id: infer_major,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: infer_minor,
name: None,
optional: false,
rest: false,
},
]);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_template,
true_type,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
subst.insert(t_name, interner.literal_string("v1.2"));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.tuple(vec![
TupleElement {
type_id: interner.literal_string("1"),
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: interner.literal_string("2"),
name: None,
optional: false,
rest: false,
},
]);
assert_eq!(result, expected);
}
#[test]
fn test_template_literal_extract_index_from_array_key() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("Index");
let infer_index = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_template = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("item[")),
TemplateSpan::Type(infer_index),
TemplateSpan::Text(interner.intern_string("]")),
]);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_template,
true_type: infer_index,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let lit_0 = interner.literal_string("item[0]");
let lit_1 = interner.literal_string("item[1]");
let lit_2 = interner.literal_string("item[2]");
let mut subst = TypeSubstitution::new();
subst.insert(t_name, interner.union(vec![lit_0, lit_1, lit_2]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![
interner.literal_string("0"),
interner.literal_string("1"),
interner.literal_string("2"),
]);
assert_eq!(result, expected);
}
#[test]
fn test_template_literal_extract_port_number() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("Port");
let infer_port = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_template = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("localhost:")),
TemplateSpan::Type(infer_port),
]);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_template,
true_type: infer_port,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
subst.insert(t_name, interner.literal_string("localhost:3000"));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.literal_string("3000");
assert_eq!(result, expected);
}
#[test]
fn test_template_literal_extract_coordinates() {
let interner = TypeInterner::new();
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 infer_x = interner.intern(TypeData::Infer(TypeParamInfo {
name: interner.intern_string("X"),
constraint: None,
default: None,
is_const: false,
}));
let infer_y = interner.intern(TypeData::Infer(TypeParamInfo {
name: interner.intern_string("Y"),
constraint: None,
default: None,
is_const: false,
}));
let extends_template = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("(")),
TemplateSpan::Type(infer_x),
TemplateSpan::Text(interner.intern_string(",")),
TemplateSpan::Type(infer_y),
TemplateSpan::Text(interner.intern_string(")")),
]);
let true_type = interner.tuple(vec![
TupleElement {
type_id: infer_x,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: infer_y,
name: None,
optional: false,
rest: false,
},
]);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_template,
true_type,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
subst.insert(t_name, interner.literal_string("(10,20)"));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.tuple(vec![
TupleElement {
type_id: interner.literal_string("10"),
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: interner.literal_string("20"),
name: None,
optional: false,
rest: false,
},
]);
assert_eq!(result, expected);
}
#[test]
fn test_variadic_tuple_spread_at_end() {
let interner = TypeInterner::new();
let number_array = interner.array(TypeId::NUMBER);
let variadic_tuple = 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!(matches!(
interner.lookup(variadic_tuple),
Some(TypeData::Tuple(_))
));
assert_ne!(variadic_tuple, TypeId::NEVER);
assert_ne!(variadic_tuple, TypeId::UNKNOWN);
}
#[test]
fn test_variadic_tuple_spread_at_start() {
let interner = TypeInterner::new();
let string_array = interner.array(TypeId::STRING);
let variadic_tuple = interner.tuple(vec![
TupleElement {
type_id: string_array,
name: None,
optional: false,
rest: true,
},
TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
},
]);
assert!(matches!(
interner.lookup(variadic_tuple),
Some(TypeData::Tuple(_))
));
assert_ne!(variadic_tuple, TypeId::NEVER);
assert_ne!(variadic_tuple, TypeId::UNKNOWN);
}
#[test]
fn test_variadic_tuple_infer_rest_elements() {
let interner = TypeInterner::new();
let t_name = interner.intern_string("T");
let rest_name = interner.intern_string("Rest");
let t_param = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: t_name,
constraint: None,
default: None,
is_const: false,
}));
let infer_rest = interner.intern(TypeData::Infer(TypeParamInfo {
name: rest_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_tuple = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: infer_rest,
name: None,
optional: false,
rest: true,
},
]);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_tuple,
true_type: infer_rest,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let input_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,
},
TupleElement {
type_id: TypeId::BOOLEAN,
name: None,
optional: false,
rest: false,
},
]);
subst.insert(t_name, input_tuple);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = 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_eq!(result, expected);
}
#[test]
fn test_variadic_tuple_infer_first_element() {
let interner = TypeInterner::new();
let t_name = interner.intern_string("T");
let first_name = interner.intern_string("First");
let rest_name = interner.intern_string("Rest");
let t_param = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: t_name,
constraint: None,
default: None,
is_const: false,
}));
let infer_first = interner.intern(TypeData::Infer(TypeParamInfo {
name: first_name,
constraint: None,
default: None,
is_const: false,
}));
let infer_rest = interner.intern(TypeData::Infer(TypeParamInfo {
name: rest_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_tuple = interner.tuple(vec![
TupleElement {
type_id: infer_first,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: infer_rest,
name: None,
optional: false,
rest: true,
},
]);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_tuple,
true_type: infer_first,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let input_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,
},
TupleElement {
type_id: TypeId::BOOLEAN,
name: None,
optional: false,
rest: false,
},
]);
subst.insert(t_name, input_tuple);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::NUMBER);
}
#[test]
fn test_variadic_tuple_empty_rest() {
let interner = TypeInterner::new();
let t_name = interner.intern_string("T");
let r_name = interner.intern_string("R");
let t_param = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: t_name,
constraint: None,
default: None,
is_const: false,
}));
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: r_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_tuple = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: infer_r,
name: None,
optional: false,
rest: true,
},
]);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_tuple,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let input_tuple = interner.tuple(vec![TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
}]);
subst.insert(t_name, input_tuple);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.tuple(Vec::new());
assert_eq!(result, expected);
}
#[test]
fn test_keyof_with_index_access_combination() {
let interner = TypeInterner::new();
let name_prop = interner.intern_string("name");
let age_prop = interner.intern_string("age");
let obj = interner.object(vec![
PropertyInfo::new(name_prop, TypeId::STRING),
PropertyInfo::new(age_prop, TypeId::NUMBER),
]);
let result = evaluate_keyof(&interner, obj);
let expected = interner.union(vec![
interner.literal_string("age"),
interner.literal_string("name"),
]);
assert_eq!(result, expected);
}
#[test]
fn test_index_access_with_keyof() {
let interner = TypeInterner::new();
let x_prop = interner.intern_string("x");
let y_prop = interner.intern_string("y");
let obj = interner.object(vec![
PropertyInfo::new(x_prop, TypeId::STRING),
PropertyInfo::new(y_prop, TypeId::NUMBER),
]);
let key_x = interner.literal_string("x");
let result_x = evaluate_index_access(&interner, obj, key_x);
assert_eq!(result_x, TypeId::STRING);
let key_y = interner.literal_string("y");
let result_y = evaluate_index_access(&interner, obj, key_y);
assert_eq!(result_y, TypeId::NUMBER);
}
#[test]
fn test_index_access_nested_object() {
let interner = TypeInterner::new();
let inner_prop = interner.intern_string("inner");
let inner_obj = interner.object(vec![PropertyInfo::new(inner_prop, TypeId::STRING)]);
let outer_prop = interner.intern_string("outer");
let outer_obj = interner.object(vec![PropertyInfo::new(outer_prop, inner_obj)]);
let outer_key = interner.literal_string("outer");
let first_result = evaluate_index_access(&interner, outer_obj, outer_key);
assert_eq!(first_result, inner_obj);
let inner_key = interner.literal_string("inner");
let final_result = evaluate_index_access(&interner, first_result, inner_key);
assert_eq!(final_result, TypeId::STRING);
}
#[test]
fn test_indexed_access_basic_literal_key() {
let interner = TypeInterner::new();
let obj = interner.object(vec![
PropertyInfo::new(interner.intern_string("a"), TypeId::STRING),
PropertyInfo::new(interner.intern_string("b"), TypeId::NUMBER),
]);
let key_a = interner.literal_string("a");
let result = evaluate_index_access(&interner, obj, key_a);
assert_eq!(result, TypeId::STRING);
let key_b = interner.literal_string("b");
let result_b = evaluate_index_access(&interner, obj, key_b);
assert_eq!(result_b, TypeId::NUMBER);
}
#[test]
fn test_indexed_access_union_key_produces_union() {
let interner = TypeInterner::new();
let obj = interner.object(vec![
PropertyInfo::new(interner.intern_string("a"), TypeId::STRING),
PropertyInfo::new(interner.intern_string("b"), TypeId::NUMBER),
PropertyInfo::new(interner.intern_string("c"), TypeId::BOOLEAN),
]);
let key_a = interner.literal_string("a");
let key_b = interner.literal_string("b");
let key_union = interner.union(vec![key_a, key_b]);
let result = evaluate_index_access(&interner, obj, key_union);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert_eq!(result, expected);
}
#[test]
fn test_indexed_access_triple_union_key() {
let interner = TypeInterner::new();
let obj = interner.object(vec![
PropertyInfo::new(interner.intern_string("a"), TypeId::STRING),
PropertyInfo::new(interner.intern_string("b"), TypeId::NUMBER),
PropertyInfo::new(interner.intern_string("c"), TypeId::BOOLEAN),
]);
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]);
let result = evaluate_index_access(&interner, obj, key_union);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER, TypeId::BOOLEAN]);
assert_eq!(result, expected);
}
#[test]
fn test_indexed_access_recursive_three_levels() {
let interner = TypeInterner::new();
let inner_obj = interner.object(vec![PropertyInfo::new(
interner.intern_string("inner"),
TypeId::STRING,
)]);
let middle_obj = interner.object(vec![PropertyInfo::new(
interner.intern_string("middle"),
inner_obj,
)]);
let outer_obj = interner.object(vec![PropertyInfo::new(
interner.intern_string("outer"),
middle_obj,
)]);
let outer_key = interner.literal_string("outer");
let first_result = evaluate_index_access(&interner, outer_obj, outer_key);
assert_eq!(first_result, middle_obj);
let middle_key = interner.literal_string("middle");
let second_result = evaluate_index_access(&interner, first_result, middle_key);
assert_eq!(second_result, inner_obj);
let inner_key = interner.literal_string("inner");
let final_result = evaluate_index_access(&interner, second_result, inner_key);
assert_eq!(final_result, TypeId::STRING);
}
#[test]
fn test_indexed_access_optional_property() {
let interner = TypeInterner::new();
let obj = interner.object(vec![PropertyInfo {
name: interner.intern_string("a"),
type_id: TypeId::STRING,
write_type: TypeId::STRING,
optional: true, readonly: false,
is_method: false,
visibility: Visibility::Public,
parent_id: None,
}]);
let key_a = interner.literal_string("a");
let result = evaluate_index_access(&interner, obj, key_a);
let expected = interner.union(vec![TypeId::STRING, TypeId::UNDEFINED]);
assert_eq!(result, expected);
}
#[test]
fn test_indexed_access_mixed_optional_required() {
let interner = TypeInterner::new();
let obj = interner.object(vec![
PropertyInfo {
name: interner.intern_string("a"),
type_id: TypeId::STRING,
write_type: TypeId::STRING,
optional: false, readonly: false,
is_method: false,
visibility: Visibility::Public,
parent_id: None,
},
PropertyInfo {
name: interner.intern_string("b"),
type_id: TypeId::NUMBER,
write_type: TypeId::NUMBER,
optional: true, readonly: false,
is_method: false,
visibility: Visibility::Public,
parent_id: None,
},
]);
let key_a = interner.literal_string("a");
let key_b = interner.literal_string("b");
let key_union = interner.union(vec![key_a, key_b]);
let result = evaluate_index_access(&interner, obj, key_union);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER, TypeId::UNDEFINED]);
assert_eq!(result, expected);
}
#[test]
fn test_indexed_access_array_number_key() {
let interner = TypeInterner::new();
let string_array = interner.array(TypeId::STRING);
let result = evaluate_index_access(&interner, string_array, TypeId::NUMBER);
assert_eq!(result, TypeId::STRING);
}
#[test]
fn test_indexed_access_tuple_literal_index() {
let interner = TypeInterner::new();
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,
},
TupleElement {
type_id: TypeId::BOOLEAN,
name: None,
optional: false,
rest: false,
},
]);
let key_0 = interner.literal_number(0.0);
let result_0 = evaluate_index_access(&interner, tuple, key_0);
assert_eq!(result_0, TypeId::STRING);
let key_1 = interner.literal_number(1.0);
let result_1 = evaluate_index_access(&interner, tuple, key_1);
assert_eq!(result_1, TypeId::NUMBER);
let key_2 = interner.literal_number(2.0);
let result_2 = evaluate_index_access(&interner, tuple, key_2);
assert_eq!(result_2, TypeId::BOOLEAN);
}
#[test]
fn test_indexed_access_union_object() {
let interner = TypeInterner::new();
let obj1 = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
TypeId::STRING,
)]);
let obj2 = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
TypeId::NUMBER,
)]);
let union_obj = interner.union(vec![obj1, obj2]);
let key_a = interner.literal_string("a");
let result = evaluate_index_access(&interner, union_obj, key_a);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert_eq!(result, expected);
}
#[test]
fn test_indexed_access_all_optional_properties() {
let interner = TypeInterner::new();
let obj = interner.object(vec![
PropertyInfo::opt(interner.intern_string("a"), TypeId::STRING),
PropertyInfo::opt(interner.intern_string("b"), TypeId::NUMBER),
]);
let key_a = interner.literal_string("a");
let key_b = interner.literal_string("b");
let key_union = interner.union(vec![key_a, key_b]);
let result = evaluate_index_access(&interner, obj, key_union);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER, TypeId::UNDEFINED]);
assert_eq!(result, expected);
}
#[test]
fn test_indexed_access_readonly_property() {
let interner = TypeInterner::new();
let obj = interner.object(vec![PropertyInfo {
name: interner.intern_string("a"),
type_id: TypeId::STRING,
write_type: TypeId::STRING,
optional: false,
readonly: true, is_method: false,
visibility: Visibility::Public,
parent_id: None,
}]);
let key_a = interner.literal_string("a");
let result = evaluate_index_access(&interner, obj, key_a);
assert_eq!(result, TypeId::STRING);
}
#[test]
fn test_generator_function_return_type_extraction() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_fn = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: infer_r,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_fn,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let input_fn = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::NUMBER,
type_predicate: None,
is_constructor: false,
is_method: false,
});
subst.insert(t_name, input_fn);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::NUMBER);
}
#[test]
fn test_generator_function_yield_type_simulation() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("Y");
let infer_y = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let value_prop = interner.intern_string("value");
let done_prop = interner.intern_string("done");
let iterator_result = interner.object(vec![
PropertyInfo::readonly(value_prop, infer_y),
PropertyInfo::readonly(done_prop, TypeId::BOOLEAN),
]);
let cond = ConditionalType {
check_type: t_param,
extends_type: iterator_result,
true_type: infer_y,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let input_obj = interner.object(vec![
PropertyInfo::readonly(value_prop, TypeId::STRING),
PropertyInfo::readonly(done_prop, TypeId::BOOLEAN),
]);
subst.insert(t_name, input_obj);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::STRING);
}
#[test]
fn test_generator_function_async_return() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let then_prop = interner.intern_string("then");
let promise_like = interner.object(vec![PropertyInfo {
name: then_prop,
type_id: infer_r, write_type: infer_r,
optional: false,
readonly: true,
is_method: true,
visibility: Visibility::Public,
parent_id: None,
}]);
let cond = ConditionalType {
check_type: t_param,
extends_type: promise_like,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let input_obj = interner.object(vec![PropertyInfo {
name: then_prop,
type_id: TypeId::STRING,
write_type: TypeId::STRING,
optional: false,
readonly: true,
is_method: true,
visibility: Visibility::Public,
parent_id: None,
}]);
subst.insert(t_name, input_obj);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::STRING);
}
#[test]
fn test_generator_function_next_param_type() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("A");
let infer_a = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let arg_name = interner.intern_string("arg");
let extends_fn = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo::required(arg_name, infer_a)],
this_type: None,
return_type: TypeId::ANY,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_fn,
true_type: infer_a,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let x_name = interner.intern_string("x");
let input_fn = 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,
});
subst.insert(t_name, input_fn);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::NUMBER);
}
#[test]
fn test_generator_function_multiple_params() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("P");
let infer_p = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let args_name = interner.intern_string("args");
let extends_fn = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo::rest(args_name, infer_p)],
this_type: None,
return_type: TypeId::ANY,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_fn,
true_type: infer_p,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let a_name = interner.intern_string("a");
let b_name = interner.intern_string("b");
let input_fn = interner.function(FunctionShape {
type_params: vec![],
params: vec![
ParamInfo::required(a_name, TypeId::STRING),
ParamInfo::required(b_name, TypeId::NUMBER),
],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
subst.insert(t_name, input_fn);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert!(
result == TypeId::NEVER
|| matches!(
interner.lookup(result),
Some(TypeData::Tuple(_) | TypeData::Array(_) | _)
)
);
}
#[test]
fn test_module_augmentation_object_merge() {
let interner = TypeInterner::new();
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 x_prop = interner.intern_string("x");
let obj1 = interner.object(vec![PropertyInfo::new(x_prop, TypeId::STRING)]);
let y_prop = interner.intern_string("y");
let obj2 = interner.object(vec![PropertyInfo::new(y_prop, TypeId::NUMBER)]);
let merged = interner.intersection(vec![obj1, obj2]);
let cond = ConditionalType {
check_type: t_param,
extends_type: merged,
true_type: t_param,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let combined = interner.object(vec![
PropertyInfo::new(x_prop, TypeId::STRING),
PropertyInfo::new(y_prop, TypeId::NUMBER),
]);
subst.insert(t_name, combined);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, combined);
}
#[test]
fn test_module_augmentation_function_overload() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_fn = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: infer_r,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_fn,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let input_fn = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::STRING,
type_predicate: None,
is_constructor: false,
is_method: false,
});
subst.insert(t_name, input_fn);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::STRING);
}
#[test]
fn test_module_augmentation_namespace_merge() {
let interner = TypeInterner::new();
let version_prop = interner.intern_string("version");
let ns1 = interner.object(vec![PropertyInfo::readonly(version_prop, TypeId::STRING)]);
let utils_prop = interner.intern_string("utils");
let format_prop = interner.intern_string("format");
let format_fn = 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 utils_obj = interner.object(vec![PropertyInfo::method(format_prop, format_fn)]);
let ns2 = interner.object(vec![PropertyInfo::new(utils_prop, utils_obj)]);
let merged_ns = interner.intersection(vec![ns1, ns2]);
match interner.lookup(merged_ns) {
Some(TypeData::Object(shape_id) | TypeData::ObjectWithIndex(shape_id)) => {
let shape = interner.object_shape(shape_id);
let has_version = shape.properties.iter().any(|p| p.name == version_prop);
let has_utils = shape.properties.iter().any(|p| p.name == utils_prop);
assert!(
has_version && has_utils,
"merged namespace should include both props"
);
}
other => panic!("unexpected merged namespace representation: {other:?}"),
}
}
#[test]
fn test_module_augmentation_class_extension() {
let interner = TypeInterner::new();
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 instance_type = interner.object(vec![]);
let constructor = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: instance_type,
type_predicate: None,
is_constructor: true,
is_method: false,
});
let new_prop = interner.intern_string("new");
let class_static = interner.object(vec![PropertyInfo {
name: new_prop,
type_id: constructor,
write_type: constructor,
optional: false,
readonly: true,
is_method: true,
visibility: Visibility::Public,
parent_id: None,
}]);
let cond = ConditionalType {
check_type: t_param,
extends_type: class_static,
true_type: t_param,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
subst.insert(t_name, class_static);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, class_static);
}
#[test]
fn test_module_augmentation_global_interface() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("E");
let infer_e = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let my_method = interner.intern_string("myMethod");
let extends_obj = interner.object(vec![PropertyInfo::method(my_method, infer_e)]);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_obj,
true_type: infer_e,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let input_obj = interner.object(vec![PropertyInfo::method(my_method, TypeId::NUMBER)]);
subst.insert(t_name, input_obj);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::NUMBER);
}
#[test]
fn test_array_covariance_element_extraction() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("E");
let infer_e = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_array = interner.array(infer_e);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_array,
true_type: infer_e,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let input_array = interner.array(TypeId::STRING);
subst.insert(t_name, input_array);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::STRING);
}
#[test]
fn test_array_covariance_union_element() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("E");
let infer_e = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_array = interner.array(infer_e);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_array,
true_type: infer_e,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let union_elem = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let input_array = interner.array(union_elem);
subst.insert(t_name, input_array);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, union_elem);
}
#[test]
fn test_array_covariance_readonly() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("E");
let infer_e = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_array = interner.array(infer_e);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_array,
true_type: infer_e,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let input_array = interner.array(TypeId::NUMBER);
subst.insert(t_name, input_array);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::NUMBER);
}
#[test]
fn test_array_covariance_nested() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("E");
let infer_e = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let inner_array = interner.array(infer_e);
let extends_array = interner.array(inner_array);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_array,
true_type: infer_e,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let string_array = interner.array(TypeId::STRING);
let nested_array = interner.array(string_array);
subst.insert(t_name, nested_array);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::STRING);
}
#[test]
fn test_array_covariance_non_array() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("E");
let infer_e = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_array = interner.array(infer_e);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_array,
true_type: infer_e,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
subst.insert(t_name, TypeId::STRING);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_return_type_generic_function() {
let interner = TypeInterner::new();
let infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_fn = interner.function(FunctionShape {
params: vec![ParamInfo {
name: None,
type_id: interner.array(TypeId::ANY),
optional: false,
rest: true,
}],
this_type: None,
return_type: infer_r,
type_params: Vec::new(),
type_predicate: None,
is_constructor: false,
is_method: false,
});
let u_name = interner.intern_string("U");
let u_param = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: u_name,
constraint: None,
default: None,
is_const: false,
}));
let generic_fn = interner.function(FunctionShape {
type_params: vec![TypeParamInfo {
name: u_name,
constraint: None,
default: None,
is_const: false,
}],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
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 cond = ConditionalType {
check_type: generic_fn,
extends_type: extends_fn,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, u_param);
}
#[test]
fn test_return_type_overloaded_function() {
let interner = TypeInterner::new();
let infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_fn = interner.function(FunctionShape {
params: vec![ParamInfo {
name: None,
type_id: interner.array(TypeId::ANY),
optional: false,
rest: true,
}],
this_type: None,
return_type: infer_r,
type_params: Vec::new(),
type_predicate: None,
is_constructor: false,
is_method: false,
});
let overloaded = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![
CallSignature {
type_params: Vec::new(),
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::new(),
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: TypeId::NUMBER,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::BOOLEAN,
type_predicate: None,
is_method: false,
},
],
construct_signatures: Vec::new(),
properties: Vec::new(),
string_index: None,
number_index: None,
});
let cond = ConditionalType {
check_type: overloaded,
extends_type: extends_fn,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, TypeId::BOOLEAN);
}
#[test]
fn test_return_type_type_predicate_function() {
let interner = TypeInterner::new();
let infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_fn = interner.function(FunctionShape {
params: vec![ParamInfo {
name: None,
type_id: interner.array(TypeId::ANY),
optional: false,
rest: true,
}],
this_type: None,
return_type: infer_r,
type_params: Vec::new(),
type_predicate: None,
is_constructor: false,
is_method: false,
});
let x_name = interner.intern_string("x");
let type_guard_fn = interner.function(FunctionShape {
type_params: Vec::new(),
params: vec![ParamInfo::required(x_name, TypeId::UNKNOWN)],
this_type: None,
return_type: TypeId::BOOLEAN,
type_predicate: Some(TypePredicate {
parameter_index: None,
target: TypePredicateTarget::Identifier(x_name),
type_id: Some(TypeId::STRING),
asserts: false,
}),
is_constructor: false,
is_method: false,
});
let cond = ConditionalType {
check_type: type_guard_fn,
extends_type: extends_fn,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, TypeId::BOOLEAN);
}
#[test]
fn test_parameters_rest_param_function() {
let interner = TypeInterner::new();
let infer_name = interner.intern_string("P");
let infer_p = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_fn = interner.function(FunctionShape {
params: vec![ParamInfo {
name: Some(interner.intern_string("args")),
type_id: infer_p,
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 source_fn = interner.function(FunctionShape {
type_params: Vec::new(),
params: vec![ParamInfo {
name: Some(interner.intern_string("args")),
type_id: interner.array(TypeId::STRING),
optional: false,
rest: true,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let cond = ConditionalType {
check_type: source_fn,
extends_type: extends_fn,
true_type: infer_p,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
let expected = interner.array(TypeId::STRING);
assert_eq!(result, expected);
}
#[test]
fn test_parameters_optional_and_rest_combination() {
let interner = TypeInterner::new();
let infer_name = interner.intern_string("P");
let infer_p = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_fn = interner.function(FunctionShape {
params: vec![ParamInfo {
name: Some(interner.intern_string("args")),
type_id: infer_p,
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 source_fn = interner.function(FunctionShape {
type_params: Vec::new(),
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,
},
ParamInfo {
name: Some(interner.intern_string("rest")),
type_id: interner.array(TypeId::BOOLEAN),
optional: false,
rest: true,
},
],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let cond = ConditionalType {
check_type: source_fn,
extends_type: extends_fn,
true_type: infer_p,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_constructor_parameters_basic() {
let interner = TypeInterner::new();
let infer_name = interner.intern_string("P");
let infer_p = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_ctor = interner.function(FunctionShape {
params: vec![ParamInfo {
name: Some(interner.intern_string("args")),
type_id: infer_p,
optional: false,
rest: true,
}],
this_type: None,
return_type: TypeId::ANY,
type_params: Vec::new(),
type_predicate: None,
is_constructor: true, is_method: false,
});
let source_ctor = interner.function(FunctionShape {
type_params: Vec::new(),
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: TypeId::OBJECT, type_predicate: None,
is_constructor: true,
is_method: false,
});
let cond = ConditionalType {
check_type: source_ctor,
extends_type: extends_ctor,
true_type: infer_p,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_constructor_parameters_callable_construct_signature() {
let interner = TypeInterner::new();
let infer_name = interner.intern_string("P");
let infer_p = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_ctor = interner.function(FunctionShape {
params: vec![ParamInfo {
name: Some(interner.intern_string("args")),
type_id: infer_p,
optional: false,
rest: true,
}],
this_type: None,
return_type: TypeId::ANY,
type_params: Vec::new(),
type_predicate: None,
is_constructor: true,
is_method: false,
});
let callable_with_ctor = interner.callable(CallableShape {
symbol: None,
call_signatures: Vec::new(),
construct_signatures: vec![CallSignature {
type_params: Vec::new(),
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: TypeId::STRING,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::OBJECT,
type_predicate: None,
is_method: false,
}],
properties: Vec::new(),
string_index: None,
number_index: None,
});
let cond = ConditionalType {
check_type: callable_with_ctor,
extends_type: extends_ctor,
true_type: infer_p,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
match interner.lookup(result) {
Some(TypeData::Tuple(elems)) => {
let elems = interner.tuple_list(elems);
assert_eq!(elems.len(), 1);
assert_eq!(elems[0].type_id, TypeId::STRING);
}
_ => panic!("Expected tuple, got {result:?}"),
}
}
#[test]
fn test_return_type_union_distributive() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_fn = interner.function(FunctionShape {
params: vec![ParamInfo {
name: None,
type_id: interner.array(TypeId::ANY),
optional: false,
rest: true,
}],
this_type: None,
return_type: infer_r,
type_params: Vec::new(),
type_predicate: None,
is_constructor: false,
is_method: false,
});
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_fn,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let fn_string = interner.function(FunctionShape {
type_params: Vec::new(),
params: Vec::new(),
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::new(),
params: Vec::new(),
this_type: None,
return_type: TypeId::NUMBER,
type_predicate: None,
is_constructor: false,
is_method: false,
});
subst.insert(t_name, interner.union(vec![fn_string, fn_number]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert_eq!(result, expected);
}
#[test]
fn test_nested_distributive_two_levels() {
let interner = TypeInterner::new();
let lit_a = interner.literal_string("a");
let lit_b = interner.literal_string("b");
let lit_matched = interner.literal_string("matched");
let _lit_unmatched = interner.literal_string("unmatched");
let union_input = interner.union(vec![lit_a, lit_b, TypeId::NUMBER]);
let outer_cond = ConditionalType {
check_type: union_input,
extends_type: TypeId::STRING,
true_type: lit_matched, false_type: TypeId::NEVER,
is_distributive: true,
};
let outer_result = evaluate_conditional(&interner, &outer_cond);
assert_eq!(outer_result, lit_matched);
}
#[test]
fn test_nested_distributive_inner_also_distributes() {
let interner = TypeInterner::new();
let union_input = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let string_array = interner.array(TypeId::STRING);
let _number_array = interner.array(TypeId::NUMBER);
let cond = ConditionalType {
check_type: union_input,
extends_type: TypeId::ANY,
true_type: string_array, false_type: TypeId::NEVER,
is_distributive: true,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, string_array);
}
#[test]
fn test_nested_distributive_three_levels() {
let interner = TypeInterner::new();
let lit_primitive = interner.literal_string("primitive");
let lit_array = interner.literal_string("array");
let lit_function = interner.literal_string("function");
let lit_object = interner.literal_string("object");
let cond_string = ConditionalType {
check_type: TypeId::STRING,
extends_type: TypeId::OBJECT,
true_type: lit_object,
false_type: lit_primitive,
is_distributive: false,
};
let result_string = evaluate_conditional(&interner, &cond_string);
assert_eq!(result_string, lit_primitive);
let cond_number = ConditionalType {
check_type: TypeId::NUMBER,
extends_type: TypeId::OBJECT,
true_type: lit_object,
false_type: lit_primitive,
is_distributive: false,
};
let result_number = evaluate_conditional(&interner, &cond_number);
assert_eq!(result_number, lit_primitive);
let _ = (lit_array, lit_function); }
#[test]
fn test_distribution_over_intersection_basic() {
let interner = TypeInterner::new();
let obj_a = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
TypeId::STRING,
)]);
let obj_b = interner.object(vec![PropertyInfo::new(
interner.intern_string("b"),
TypeId::NUMBER,
)]);
let intersection_ab = interner.intersection(vec![obj_a, obj_b]);
let lit_yes = interner.literal_string("yes");
let lit_no = interner.literal_string("no");
let cond = ConditionalType {
check_type: intersection_ab,
extends_type: TypeId::OBJECT,
true_type: lit_yes,
false_type: lit_no,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, lit_yes);
}
#[test]
fn test_distribution_over_intersection_with_primitives() {
let interner = TypeInterner::new();
let empty_obj = interner.object(vec![]);
let string_inter = interner.intersection(vec![TypeId::STRING, empty_obj]);
let number_inter = interner.intersection(vec![TypeId::NUMBER, empty_obj]);
let union_of_intersections = interner.union(vec![string_inter, number_inter]);
let lit_string_type = interner.literal_string("string-like");
let lit_other = interner.literal_string("other");
let cond = ConditionalType {
check_type: union_of_intersections,
extends_type: TypeId::STRING,
true_type: lit_string_type,
false_type: lit_other,
is_distributive: true,
};
let result = evaluate_conditional(&interner, &cond);
let expected = interner.union(vec![lit_string_type, lit_other]);
assert_eq!(result, expected);
}
#[test]
fn test_infer_tuple_swap_pattern() {
let interner = TypeInterner::new();
let infer_a_name = interner.intern_string("A");
let infer_b_name = interner.intern_string("B");
let infer_a = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_a_name,
constraint: None,
default: None,
is_const: false,
}));
let infer_b = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_b_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = interner.tuple(vec![
TupleElement {
type_id: infer_a,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: infer_b,
name: None,
optional: false,
rest: false,
},
]);
let input = 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 cond = ConditionalType {
check_type: input,
extends_type: pattern,
true_type: infer_a, false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, TypeId::STRING);
}
#[test]
fn test_infer_tuple_swap_second_position() {
let interner = TypeInterner::new();
let infer_a_name = interner.intern_string("A");
let infer_b_name = interner.intern_string("B");
let infer_a = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_a_name,
constraint: None,
default: None,
is_const: false,
}));
let infer_b = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_b_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = interner.tuple(vec![
TupleElement {
type_id: infer_a,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: infer_b,
name: None,
optional: false,
rest: false,
},
]);
let input = 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 cond = ConditionalType {
check_type: input,
extends_type: pattern,
true_type: infer_b,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, TypeId::NUMBER);
}
#[test]
fn test_infer_function_signature_param_and_return() {
let interner = TypeInterner::new();
let infer_p_name = interner.intern_string("P");
let infer_r_name = interner.intern_string("R");
let infer_p = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_p_name,
constraint: None,
default: None,
is_const: false,
}));
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_r_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern_fn = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: infer_p,
optional: false,
rest: false,
}],
this_type: None,
return_type: infer_r,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let input_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::NUMBER,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let cond_p = ConditionalType {
check_type: input_fn,
extends_type: pattern_fn,
true_type: infer_p,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result_p = evaluate_conditional(&interner, &cond_p);
assert_eq!(result_p, TypeId::STRING);
let cond_r = ConditionalType {
check_type: input_fn,
extends_type: pattern_fn,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result_r = evaluate_conditional(&interner, &cond_r);
assert_eq!(result_r, TypeId::NUMBER);
}
#[test]
fn test_infer_function_multiple_params() {
let interner = TypeInterner::new();
let infer_a_name = interner.intern_string("A");
let infer_b_name = interner.intern_string("B");
let infer_a = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_a_name,
constraint: None,
default: None,
is_const: false,
}));
let infer_b = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_b_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern_fn = interner.function(FunctionShape {
type_params: vec![],
params: vec![
ParamInfo {
name: Some(interner.intern_string("a")),
type_id: infer_a,
optional: false,
rest: false,
},
ParamInfo {
name: Some(interner.intern_string("b")),
type_id: infer_b,
optional: false,
rest: false,
},
],
this_type: None,
return_type: TypeId::ANY,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let input_fn = interner.function(FunctionShape {
type_params: vec![],
params: vec![
ParamInfo {
name: Some(interner.intern_string("a")),
type_id: TypeId::BOOLEAN,
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 cond_a = ConditionalType {
check_type: input_fn,
extends_type: pattern_fn,
true_type: infer_a,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result_a = evaluate_conditional(&interner, &cond_a);
assert_eq!(result_a, TypeId::BOOLEAN);
let cond_b = ConditionalType {
check_type: input_fn,
extends_type: pattern_fn,
true_type: infer_b,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result_b = evaluate_conditional(&interner, &cond_b);
assert_eq!(result_b, TypeId::STRING);
}
#[test]
fn test_edge_case_never_distributive_empty() {
let interner = TypeInterner::new();
let lit_yes = interner.literal_string("yes");
let lit_no = interner.literal_string("no");
let cond = ConditionalType {
check_type: TypeId::NEVER,
extends_type: TypeId::STRING,
true_type: lit_yes,
false_type: lit_no,
is_distributive: true,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_edge_case_never_as_extends_target() {
let interner = TypeInterner::new();
let lit_yes = interner.literal_string("yes");
let lit_no = interner.literal_string("no");
let cond_string = ConditionalType {
check_type: TypeId::STRING,
extends_type: TypeId::NEVER,
true_type: lit_yes,
false_type: lit_no,
is_distributive: false,
};
let result_string = evaluate_conditional(&interner, &cond_string);
assert_eq!(result_string, lit_no);
let cond_never = ConditionalType {
check_type: TypeId::NEVER,
extends_type: TypeId::NEVER,
true_type: lit_yes,
false_type: lit_no,
is_distributive: false,
};
let result_never = evaluate_conditional(&interner, &cond_never);
assert_eq!(result_never, lit_yes);
}
#[test]
fn test_edge_case_unknown_multiple_extends() {
let interner = TypeInterner::new();
let lit_yes = interner.literal_string("yes");
let lit_no = interner.literal_string("no");
let cond_string = ConditionalType {
check_type: TypeId::UNKNOWN,
extends_type: TypeId::STRING,
true_type: lit_yes,
false_type: lit_no,
is_distributive: false,
};
let result_string = evaluate_conditional(&interner, &cond_string);
assert_eq!(result_string, lit_no);
let cond_unknown = ConditionalType {
check_type: TypeId::UNKNOWN,
extends_type: TypeId::UNKNOWN,
true_type: lit_yes,
false_type: lit_no,
is_distributive: false,
};
let result_unknown = evaluate_conditional(&interner, &cond_unknown);
assert_eq!(result_unknown, lit_yes);
let cond_any = ConditionalType {
check_type: TypeId::UNKNOWN,
extends_type: TypeId::ANY,
true_type: lit_yes,
false_type: lit_no,
is_distributive: false,
};
let result_any = evaluate_conditional(&interner, &cond_any);
assert_eq!(result_any, lit_yes);
}
#[test]
fn test_edge_case_any_produces_union() {
let interner = TypeInterner::new();
let lit_yes = interner.literal_string("yes");
let lit_no = interner.literal_string("no");
let cond = ConditionalType {
check_type: TypeId::ANY,
extends_type: TypeId::STRING,
true_type: lit_yes,
false_type: lit_no,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
let expected = interner.union(vec![lit_yes, lit_no]);
assert_eq!(result, expected);
}
#[test]
fn test_edge_case_any_as_extends_target() {
let interner = TypeInterner::new();
let lit_yes = interner.literal_string("yes");
let lit_no = interner.literal_string("no");
let cond_string = ConditionalType {
check_type: TypeId::STRING,
extends_type: TypeId::ANY,
true_type: lit_yes,
false_type: lit_no,
is_distributive: false,
};
let result_string = evaluate_conditional(&interner, &cond_string);
assert_eq!(result_string, lit_yes);
let cond_obj = ConditionalType {
check_type: TypeId::OBJECT,
extends_type: TypeId::ANY,
true_type: lit_yes,
false_type: lit_no,
is_distributive: false,
};
let result_obj = evaluate_conditional(&interner, &cond_obj);
assert_eq!(result_obj, lit_yes);
}
#[test]
fn test_infer_contravariant_single_param() {
let interner = TypeInterner::new();
let infer_p_name = interner.intern_string("P");
let infer_p = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_p_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern_fn = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: infer_p,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::ANY,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let param_union = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let input_fn = 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,
});
let cond = ConditionalType {
check_type: input_fn,
extends_type: pattern_fn,
true_type: infer_p,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, param_union);
}
#[test]
fn test_infer_contravariant_intersection_from_multiple_candidates() {
let interner = TypeInterner::new();
let infer_t_name = interner.intern_string("T");
let infer_t = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_t_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern_fn = interner.function(FunctionShape {
type_params: vec![],
params: vec![
ParamInfo {
name: Some(interner.intern_string("a")),
type_id: infer_t,
optional: false,
rest: false,
},
ParamInfo {
name: Some(interner.intern_string("b")),
type_id: infer_t,
optional: false,
rest: false,
},
],
this_type: None,
return_type: TypeId::ANY,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let input_fn = 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 cond = ConditionalType {
check_type: input_fn,
extends_type: pattern_fn,
true_type: infer_t,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, TypeId::STRING);
}
#[test]
fn test_infer_contravariant_callback_param() {
let interner = TypeInterner::new();
let infer_t_name = interner.intern_string("T");
let infer_t = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_t_name,
constraint: None,
default: None,
is_const: false,
}));
let callback_pattern = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: infer_t,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let outer_pattern = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("callback")),
type_id: callback_pattern,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::ANY,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let input_callback = 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 input_fn = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("callback")),
type_id: input_callback,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let cond = ConditionalType {
check_type: input_fn,
extends_type: outer_pattern,
true_type: infer_t,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, TypeId::NUMBER);
}
#[test]
fn test_tuple_spread_infer_first_rest() {
let interner = TypeInterner::new();
let infer_f_name = interner.intern_string("F");
let infer_r_name = interner.intern_string("R");
let infer_f = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_f_name,
constraint: None,
default: None,
is_const: false,
}));
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_r_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = interner.tuple(vec![
TupleElement {
type_id: infer_f,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: infer_r,
name: None,
optional: false,
rest: true,
},
]);
let input = 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 cond = ConditionalType {
check_type: input,
extends_type: pattern,
true_type: infer_f,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result == TypeId::STRING || result == TypeId::NEVER);
}
#[test]
fn test_tuple_spread_concat_pattern() {
let interner = TypeInterner::new();
let concat_result = 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 any_array = interner.array(TypeId::ANY);
let pattern = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: any_array,
name: None,
optional: false,
rest: true,
},
]);
let lit_yes = interner.literal_string("yes");
let lit_no = interner.literal_string("no");
let cond = ConditionalType {
check_type: concat_result,
extends_type: pattern,
true_type: lit_yes,
false_type: lit_no,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, lit_yes);
}
#[test]
fn test_tuple_spread_length_check() {
let interner = TypeInterner::new();
let infer_l_name = interner.intern_string("L");
let infer_l = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_l_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = interner.object(vec![PropertyInfo::new(
interner.intern_string("length"),
infer_l,
)]);
let lit_2 = interner.literal_number(2.0);
let input = interner.object(vec![PropertyInfo::new(
interner.intern_string("length"),
lit_2,
)]);
let cond = ConditionalType {
check_type: input,
extends_type: pattern,
true_type: infer_l,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, lit_2);
}
#[test]
fn test_tuple_spread_push_pattern() {
let interner = TypeInterner::new();
let pushed = 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 any_array = interner.array(TypeId::ANY);
let pattern = interner.tuple(vec![
TupleElement {
type_id: any_array,
name: None,
optional: false,
rest: true,
},
TupleElement {
type_id: TypeId::BOOLEAN,
name: None,
optional: false,
rest: false,
},
]);
let lit_yes = interner.literal_string("yes");
let lit_no = interner.literal_string("no");
let cond = ConditionalType {
check_type: pushed,
extends_type: pattern,
true_type: lit_yes,
false_type: lit_no,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result == lit_yes || result == lit_no);
}
#[test]
fn test_nonnullable_removes_null() {
let interner = TypeInterner::new();
let input = interner.union(vec![TypeId::STRING, TypeId::NULL]);
let null_or_undefined = interner.union(vec![TypeId::NULL, TypeId::UNDEFINED]);
let cond = ConditionalType {
check_type: input,
extends_type: null_or_undefined,
true_type: TypeId::NEVER,
false_type: input, is_distributive: true,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(
result,
TypeId::STRING,
"NonNullable<string | null> should equal string"
);
}
#[test]
fn test_nonnullable_removes_undefined() {
let interner = TypeInterner::new();
let input = interner.union(vec![TypeId::NUMBER, TypeId::UNDEFINED]);
let null_or_undefined = interner.union(vec![TypeId::NULL, TypeId::UNDEFINED]);
let cond = ConditionalType {
check_type: input,
extends_type: null_or_undefined,
true_type: TypeId::NEVER,
false_type: input,
is_distributive: true,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(
result,
TypeId::NUMBER,
"NonNullable<number | undefined> should equal number"
);
}
#[test]
fn test_nonnullable_removes_null_and_undefined() {
let interner = TypeInterner::new();
let input = interner.union(vec![TypeId::STRING, TypeId::NULL, TypeId::UNDEFINED]);
let null_or_undefined = interner.union(vec![TypeId::NULL, TypeId::UNDEFINED]);
let cond = ConditionalType {
check_type: input,
extends_type: null_or_undefined,
true_type: TypeId::NEVER,
false_type: input,
is_distributive: true,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(
result,
TypeId::STRING,
"NonNullable<string | null | undefined> should equal string"
);
}
#[test]
fn test_nonnullable_preserves_non_nullable_members() {
let interner = TypeInterner::new();
let input = interner.union(vec![
TypeId::STRING,
TypeId::NUMBER,
TypeId::NULL,
TypeId::UNDEFINED,
]);
let null_or_undefined = interner.union(vec![TypeId::NULL, TypeId::UNDEFINED]);
let cond = ConditionalType {
check_type: input,
extends_type: null_or_undefined,
true_type: TypeId::NEVER,
false_type: input,
is_distributive: true,
};
let result = evaluate_conditional(&interner, &cond);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert_eq!(
result, expected,
"NonNullable<string | number | null | undefined> should equal string | number"
);
}
#[test]
fn test_nonnullable_all_nullable_becomes_never() {
let interner = TypeInterner::new();
let input = interner.union(vec![TypeId::NULL, TypeId::UNDEFINED]);
let null_or_undefined = interner.union(vec![TypeId::NULL, TypeId::UNDEFINED]);
let cond = ConditionalType {
check_type: input,
extends_type: null_or_undefined,
true_type: TypeId::NEVER,
false_type: input,
is_distributive: true,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_readonly_nested_object_top_level_only() {
let interner = TypeInterner::new();
let inner_obj = interner.object(vec![PropertyInfo::new(
interner.intern_string("b"),
TypeId::STRING,
)]);
let outer_obj = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
inner_obj,
)]);
let keyof_outer = interner.intern(TypeData::KeyOf(outer_obj));
let k_name = interner.intern_string("K");
let k_param = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: k_name,
constraint: None,
default: None,
is_const: false,
}));
let mapped = MappedType {
type_param: TypeParamInfo {
name: k_name,
constraint: None,
default: None,
is_const: false,
},
constraint: keyof_outer,
name_type: None,
template: interner.intern(TypeData::IndexAccess(outer_obj, k_param)),
readonly_modifier: Some(MappedModifier::Add),
optional_modifier: None,
};
let result = evaluate_mapped(&interner, &mapped);
match interner.lookup(result).unwrap() {
TypeData::Object(shape_id) => {
let shape = interner.object_shape(shape_id);
assert_eq!(shape.properties.len(), 1);
assert!(
shape.properties[0].readonly,
"Property 'a' should be readonly"
);
let inner_type = shape.properties[0].type_id;
if let Some(TypeData::Object(inner_shape_id)) = interner.lookup(inner_type) {
let inner_shape = interner.object_shape(inner_shape_id);
assert!(
!inner_shape.properties[0].readonly,
"Nested property 'b' should NOT be readonly (shallow Readonly)"
);
}
}
_ => panic!("Expected Object type from Readonly mapped type"),
}
}
#[test]
fn test_readonly_multiple_properties_nested() {
let interner = TypeInterner::new();
let inner = interner.object(vec![PropertyInfo::new(
interner.intern_string("x"),
TypeId::NUMBER,
)]);
let outer = interner.object(vec![
PropertyInfo::new(interner.intern_string("a"), TypeId::STRING),
PropertyInfo::new(interner.intern_string("b"), inner),
]);
let keyof_outer = interner.intern(TypeData::KeyOf(outer));
let k_name = interner.intern_string("K");
let k_param = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: k_name,
constraint: None,
default: None,
is_const: false,
}));
let mapped = MappedType {
type_param: TypeParamInfo {
name: k_name,
constraint: None,
default: None,
is_const: false,
},
constraint: keyof_outer,
name_type: None,
template: interner.intern(TypeData::IndexAccess(outer, k_param)),
readonly_modifier: Some(MappedModifier::Add),
optional_modifier: None,
};
let result = evaluate_mapped(&interner, &mapped);
match interner.lookup(result).unwrap() {
TypeData::Object(shape_id) => {
let shape = interner.object_shape(shape_id);
assert_eq!(shape.properties.len(), 2);
assert!(
shape.properties[0].readonly,
"Property 'a' should be readonly"
);
assert!(
shape.properties[1].readonly,
"Property 'b' should be readonly"
);
}
_ => panic!("Expected Object type"),
}
}
#[test]
fn test_deep_readonly_pattern_structure() {
let interner = TypeInterner::new();
let simple_obj = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
TypeId::STRING,
)]);
let keyof_obj = interner.intern(TypeData::KeyOf(simple_obj));
let k_name = interner.intern_string("K");
let k_param = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: k_name,
constraint: None,
default: None,
is_const: false,
}));
let mapped = MappedType {
type_param: TypeParamInfo {
name: k_name,
constraint: None,
default: None,
is_const: false,
},
constraint: keyof_obj,
name_type: None,
template: interner.intern(TypeData::IndexAccess(simple_obj, k_param)),
readonly_modifier: Some(MappedModifier::Add),
optional_modifier: None,
};
let result = evaluate_mapped(&interner, &mapped);
match interner.lookup(result).unwrap() {
TypeData::Object(shape_id) => {
let shape = interner.object_shape(shape_id);
assert_eq!(shape.properties.len(), 1);
assert!(shape.properties[0].readonly);
assert_eq!(shape.properties[0].type_id, TypeId::STRING);
}
_ => panic!("Expected Object type"),
}
}
#[test]
fn test_deep_readonly_manual_nested_application() {
let interner = TypeInterner::new();
let inner = interner.object(vec![PropertyInfo::new(
interner.intern_string("b"),
TypeId::STRING,
)]);
let keyof_inner = interner.intern(TypeData::KeyOf(inner));
let k_name = interner.intern_string("K");
let k_param = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: k_name,
constraint: None,
default: None,
is_const: false,
}));
let inner_mapped = MappedType {
type_param: TypeParamInfo {
name: k_name,
constraint: None,
default: None,
is_const: false,
},
constraint: keyof_inner,
name_type: None,
template: interner.intern(TypeData::IndexAccess(inner, k_param)),
readonly_modifier: Some(MappedModifier::Add),
optional_modifier: None,
};
let readonly_inner = evaluate_mapped(&interner, &inner_mapped);
let outer_with_readonly_inner = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
readonly_inner,
)]);
let keyof_outer = interner.intern(TypeData::KeyOf(outer_with_readonly_inner));
let k2_name = interner.intern_string("K2");
let k2_param = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: k2_name,
constraint: None,
default: None,
is_const: false,
}));
let outer_mapped = MappedType {
type_param: TypeParamInfo {
name: k2_name,
constraint: None,
default: None,
is_const: false,
},
constraint: keyof_outer,
name_type: None,
template: interner.intern(TypeData::IndexAccess(outer_with_readonly_inner, k2_param)),
readonly_modifier: Some(MappedModifier::Add),
optional_modifier: None,
};
let result = evaluate_mapped(&interner, &outer_mapped);
match interner.lookup(result).unwrap() {
TypeData::Object(shape_id) => {
let shape = interner.object_shape(shape_id);
assert_eq!(shape.properties.len(), 1);
assert!(shape.properties[0].readonly, "Outer 'a' should be readonly");
let inner_type = shape.properties[0].type_id;
if let Some(TypeData::Object(inner_shape_id)) = interner.lookup(inner_type) {
let inner_shape = interner.object_shape(inner_shape_id);
assert!(
inner_shape.properties[0].readonly,
"Inner 'b' should be readonly (DeepReadonly)"
);
} else {
panic!("Expected inner to be Object type");
}
}
_ => panic!("Expected Object type"),
}
}
#[test]
fn test_deep_readonly_with_array_property() {
let interner = TypeInterner::new();
let string_array = interner.array(TypeId::STRING);
let obj = interner.object(vec![PropertyInfo::new(
interner.intern_string("items"),
string_array,
)]);
let keyof_obj = interner.intern(TypeData::KeyOf(obj));
let k_name = interner.intern_string("K");
let k_param = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: k_name,
constraint: None,
default: None,
is_const: false,
}));
let mapped = MappedType {
type_param: TypeParamInfo {
name: k_name,
constraint: None,
default: None,
is_const: false,
},
constraint: keyof_obj,
name_type: None,
template: interner.intern(TypeData::IndexAccess(obj, k_param)),
readonly_modifier: Some(MappedModifier::Add),
optional_modifier: None,
};
let result = evaluate_mapped(&interner, &mapped);
match interner.lookup(result).unwrap() {
TypeData::Object(shape_id) => {
let shape = interner.object_shape(shape_id);
assert_eq!(shape.properties.len(), 1);
assert!(
shape.properties[0].readonly,
"Property 'items' should be readonly"
);
assert_eq!(shape.properties[0].type_id, string_array);
}
_ => panic!("Expected Object type"),
}
}
#[test]
fn test_awaited_simple_promise() {
let interner = TypeInterner::new();
let then_name = interner.intern_string("then");
let promise_string = interner.object(vec![PropertyInfo::readonly(then_name, TypeId::STRING)]);
let infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = interner.object(vec![PropertyInfo::readonly(then_name, infer_r)]);
let cond = ConditionalType {
check_type: promise_string,
extends_type: pattern,
true_type: infer_r,
false_type: promise_string,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, TypeId::STRING);
}
#[test]
fn test_awaited_nested_promise_one_level() {
let interner = TypeInterner::new();
let then_name = interner.intern_string("then");
let inner_promise = interner.object(vec![PropertyInfo::readonly(then_name, TypeId::NUMBER)]);
let outer_promise = interner.object(vec![PropertyInfo::readonly(then_name, inner_promise)]);
let infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = interner.object(vec![PropertyInfo::readonly(then_name, infer_r)]);
let cond = ConditionalType {
check_type: outer_promise,
extends_type: pattern,
true_type: infer_r,
false_type: outer_promise,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, inner_promise);
let cond2 = ConditionalType {
check_type: result,
extends_type: pattern,
true_type: infer_r,
false_type: result,
is_distributive: false,
};
let final_result = evaluate_conditional(&interner, &cond2);
assert_eq!(final_result, TypeId::NUMBER);
}
#[test]
fn test_awaited_union_of_promises() {
let interner = TypeInterner::new();
let then_name = interner.intern_string("then");
let promise_string = interner.object(vec![PropertyInfo::readonly(then_name, TypeId::STRING)]);
let promise_number = interner.object(vec![PropertyInfo::readonly(then_name, TypeId::NUMBER)]);
let union_promises = interner.union(vec![promise_string, promise_number]);
let infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = interner.object(vec![PropertyInfo::readonly(then_name, infer_r)]);
let cond = ConditionalType {
check_type: union_promises,
extends_type: pattern,
true_type: infer_r,
false_type: union_promises,
is_distributive: true, };
let result = evaluate_conditional(&interner, &cond);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert_eq!(result, expected);
}
#[test]
fn test_awaited_non_promise_passthrough() {
let interner = TypeInterner::new();
let then_name = interner.intern_string("then");
let infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = interner.object(vec![PropertyInfo::readonly(then_name, infer_r)]);
let cond = ConditionalType {
check_type: TypeId::STRING, extends_type: pattern,
true_type: infer_r,
false_type: TypeId::STRING, is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, TypeId::STRING);
}
#[test]
fn test_awaited_mixed_union() {
let interner = TypeInterner::new();
let then_name = interner.intern_string("then");
let promise_boolean = interner.object(vec![PropertyInfo::readonly(then_name, TypeId::BOOLEAN)]);
let mixed_union = interner.union(vec![promise_boolean, TypeId::NUMBER]);
let infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = interner.object(vec![PropertyInfo::readonly(then_name, infer_r)]);
let cond = ConditionalType {
check_type: mixed_union,
extends_type: pattern,
true_type: infer_r,
false_type: mixed_union, is_distributive: true,
};
let result = evaluate_conditional(&interner, &cond);
let expected = interner.union(vec![TypeId::BOOLEAN, TypeId::NUMBER]);
assert_eq!(
result, expected,
"Awaited<Promise<boolean> | number> should equal boolean | number"
);
}
#[test]
fn test_infer_mapped_type_value_extraction() {
let interner = TypeInterner::new();
let infer_v_name = interner.intern_string("V");
let infer_v = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_v_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = interner.object(vec![
PropertyInfo::new(interner.intern_string("x"), infer_v),
PropertyInfo::new(interner.intern_string("y"), infer_v),
]);
let input = interner.object(vec![
PropertyInfo::new(interner.intern_string("x"), TypeId::STRING),
PropertyInfo::new(interner.intern_string("y"), TypeId::STRING),
]);
let cond = ConditionalType {
check_type: input,
extends_type: pattern,
true_type: infer_v,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, TypeId::STRING);
}
#[test]
fn test_infer_mapped_type_mixed_values() {
let interner = TypeInterner::new();
let infer_v_name = interner.intern_string("V");
let infer_v = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_v_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = interner.object(vec![
PropertyInfo::new(interner.intern_string("a"), infer_v),
PropertyInfo::new(interner.intern_string("b"), infer_v),
]);
let input = interner.object(vec![
PropertyInfo::new(interner.intern_string("a"), TypeId::STRING),
PropertyInfo::new(interner.intern_string("b"), TypeId::NUMBER),
]);
let cond = ConditionalType {
check_type: input,
extends_type: pattern,
true_type: infer_v,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert!(
result == expected
|| result == TypeId::STRING
|| result == TypeId::NUMBER
|| result == TypeId::NEVER
);
}
#[test]
fn test_infer_mapped_type_key_and_value() {
let interner = TypeInterner::new();
let infer_v_name = interner.intern_string("V");
let infer_v = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_v_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = interner.object(vec![PropertyInfo::new(
interner.intern_string("key"),
infer_v,
)]);
let input = interner.object(vec![PropertyInfo::new(
interner.intern_string("key"),
TypeId::BOOLEAN,
)]);
let cond = ConditionalType {
check_type: input,
extends_type: pattern,
true_type: infer_v,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, TypeId::BOOLEAN);
}
#[test]
fn test_infer_with_extends_constraint() {
let interner = TypeInterner::new();
let infer_u_name = interner.intern_string("U");
let infer_u = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_u_name,
constraint: Some(TypeId::STRING), default: None,
is_const: false,
}));
let pattern_fn = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: infer_u,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::ANY,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let lit_hello = interner.literal_string("hello");
let input_fn = interner.function(FunctionShape {
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_constructor: false,
is_method: false,
});
let cond = ConditionalType {
check_type: input_fn,
extends_type: pattern_fn,
true_type: infer_u,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, lit_hello);
}
#[test]
fn test_infer_with_constraint_violation() {
let interner = TypeInterner::new();
let infer_u_name = interner.intern_string("U");
let infer_u = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_u_name,
constraint: Some(TypeId::STRING), default: None,
is_const: false,
}));
let pattern_fn = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: infer_u,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::ANY,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let input_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::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let cond = ConditionalType {
check_type: input_fn,
extends_type: pattern_fn,
true_type: infer_u,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result == TypeId::NEVER || result == TypeId::NUMBER);
}
#[test]
fn test_infer_multiple_same_name_covariant() {
let interner = TypeInterner::new();
let infer_r_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_r_name,
constraint: None,
default: None,
is_const: false,
}));
let getter = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: infer_r, type_predicate: None,
is_constructor: false,
is_method: false,
});
let pattern = interner.object(vec![PropertyInfo::method(
interner.intern_string("get"),
getter,
)]);
let string_getter = 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 input = interner.object(vec![PropertyInfo::method(
interner.intern_string("get"),
string_getter,
)]);
let cond = ConditionalType {
check_type: input,
extends_type: pattern,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, TypeId::STRING);
}
#[test]
fn test_infer_template_literal_prefix() {
let interner = TypeInterner::new();
let infer_rest_name = interner.intern_string("Rest");
let infer_rest = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_rest_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("prefix")),
TemplateSpan::Type(infer_rest),
]);
let input = interner.literal_string("prefixSuffix");
let cond = ConditionalType {
check_type: input,
extends_type: pattern,
true_type: infer_rest,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
let expected = interner.literal_string("Suffix");
assert!(result == expected || result == TypeId::STRING || result == TypeId::NEVER);
}
#[test]
fn test_infer_template_literal_suffix() {
let interner = TypeInterner::new();
let infer_prefix_name = interner.intern_string("Prefix");
let infer_prefix = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_prefix_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = interner.template_literal(vec![
TemplateSpan::Type(infer_prefix),
TemplateSpan::Text(interner.intern_string("Suffix")),
]);
let input = interner.literal_string("PrefixSuffix");
let cond = ConditionalType {
check_type: input,
extends_type: pattern,
true_type: infer_prefix,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
let expected = interner.literal_string("Prefix");
assert!(result == expected || result == TypeId::STRING || result == TypeId::NEVER);
}
#[test]
fn test_infer_template_literal_middle() {
let interner = TypeInterner::new();
let infer_middle_name = interner.intern_string("Middle");
let infer_middle = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_middle_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("start")),
TemplateSpan::Type(infer_middle),
TemplateSpan::Text(interner.intern_string("end")),
]);
let input = interner.literal_string("startMIDDLEend");
let cond = ConditionalType {
check_type: input,
extends_type: pattern,
true_type: infer_middle,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
let expected = interner.literal_string("MIDDLE");
assert!(result == expected || result == TypeId::STRING || result == TypeId::NEVER);
}
#[test]
fn test_infer_template_literal_no_match() {
let interner = TypeInterner::new();
let infer_rest_name = interner.intern_string("Rest");
let infer_rest = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_rest_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("prefix")),
TemplateSpan::Type(infer_rest),
]);
let input = interner.literal_string("wrongStart");
let cond = ConditionalType {
check_type: input,
extends_type: pattern,
true_type: infer_rest,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_unique_symbol_type_distinct() {
let interner = TypeInterner::new();
let sym1 = interner.intern(TypeData::UniqueSymbol(SymbolRef(1)));
let sym2 = interner.intern(TypeData::UniqueSymbol(SymbolRef(2)));
assert_ne!(sym1, sym2);
}
#[test]
fn test_unique_symbol_type_same_ref() {
let interner = TypeInterner::new();
let sym1 = interner.intern(TypeData::UniqueSymbol(SymbolRef(42)));
let sym2 = interner.intern(TypeData::UniqueSymbol(SymbolRef(42)));
assert_eq!(sym1, sym2);
}
#[test]
fn test_unique_symbol_not_assignable_to_base_symbol() {
let interner = TypeInterner::new();
let unique_sym = interner.intern(TypeData::UniqueSymbol(SymbolRef(1)));
assert_ne!(unique_sym, TypeId::SYMBOL);
}
#[test]
fn test_symbol_union_with_unique() {
let interner = TypeInterner::new();
let unique_sym = interner.intern(TypeData::UniqueSymbol(SymbolRef(1)));
let union = interner.union(vec![TypeId::SYMBOL, unique_sym]);
assert_ne!(union, TypeId::SYMBOL);
assert_ne!(union, unique_sym);
}
#[test]
fn test_iterator_result_type_done_false() {
let interner = TypeInterner::new();
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::STRING),
PropertyInfo::readonly(done_name, interner.literal_boolean(false)),
]);
match interner.lookup(iter_result) {
Some(TypeData::Object(_)) => {}
_ => panic!("Expected Object type"),
}
}
#[test]
fn test_iterator_result_type_done_true() {
let interner = TypeInterner::new();
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::UNDEFINED),
PropertyInfo::readonly(done_name, interner.literal_boolean(true)),
]);
match interner.lookup(iter_result) {
Some(TypeData::Object(_)) => {}
_ => panic!("Expected Object type"),
}
}
#[test]
fn test_iterator_result_union() {
let interner = TypeInterner::new();
let value_name = interner.intern_string("value");
let done_name = interner.intern_string("done");
let yielding = interner.object(vec![
PropertyInfo::readonly(value_name, TypeId::NUMBER),
PropertyInfo::readonly(done_name, interner.literal_boolean(false)),
]);
let completed = interner.object(vec![
PropertyInfo::readonly(value_name, TypeId::UNDEFINED),
PropertyInfo::readonly(done_name, interner.literal_boolean(true)),
]);
let result_union = interner.union(vec![yielding, completed]);
match interner.lookup(result_union) {
Some(TypeData::Union(_)) => {}
_ => panic!("Expected Union type"),
}
}
#[test]
fn test_iterable_with_symbol_iterator() {
let interner = TypeInterner::new();
let value_name = interner.intern_string("value");
let done_name = interner.intern_string("done");
let next_name = interner.intern_string("next");
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: next_name,
type_id: next_fn,
write_type: next_fn,
optional: false,
readonly: true,
is_method: true,
visibility: Visibility::Public,
parent_id: None,
}]);
match interner.lookup(iterator) {
Some(TypeData::Object(shape_id)) => {
let shape = interner.object_shape(shape_id);
assert_eq!(shape.properties.len(), 1);
assert_eq!(shape.properties[0].name, next_name);
}
_ => panic!("Expected Object type"),
}
}
#[test]
fn test_well_known_symbol_unique_type() {
let interner = TypeInterner::new();
let sym_iterator = interner.intern(TypeData::UniqueSymbol(SymbolRef(100)));
let sym_async_iterator = interner.intern(TypeData::UniqueSymbol(SymbolRef(101)));
let sym_to_string_tag = interner.intern(TypeData::UniqueSymbol(SymbolRef(102)));
let sym_has_instance = interner.intern(TypeData::UniqueSymbol(SymbolRef(103)));
assert_ne!(sym_iterator, sym_async_iterator);
assert_ne!(sym_iterator, sym_to_string_tag);
assert_ne!(sym_iterator, sym_has_instance);
assert_ne!(sym_async_iterator, sym_to_string_tag);
}
#[test]
fn test_symbol_keyed_property() {
let interner = TypeInterner::new();
let sym_iterator = interner.intern(TypeData::UniqueSymbol(SymbolRef(100)));
let iter_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,
});
assert_ne!(sym_iterator, TypeId::SYMBOL);
match interner.lookup(iter_fn) {
Some(TypeData::Function(_)) => {}
_ => panic!("Expected Function type"),
}
}
#[test]
fn test_conditional_with_symbol() {
let interner = TypeInterner::new();
let unique_sym = interner.intern(TypeData::UniqueSymbol(SymbolRef(1)));
let cond = ConditionalType {
check_type: unique_sym,
extends_type: TypeId::SYMBOL,
true_type: interner.literal_boolean(true),
false_type: interner.literal_boolean(false),
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result == interner.literal_boolean(true) || result == interner.literal_boolean(false));
}
#[test]
fn test_keyof_with_symbol_property() {
let interner = TypeInterner::new();
let foo_name = interner.intern_string("foo");
let bar_name = interner.intern_string("bar");
let obj = interner.object(vec![
PropertyInfo::new(foo_name, TypeId::STRING),
PropertyInfo::new(bar_name, TypeId::NUMBER),
]);
let keyof_obj = interner.intern(TypeData::KeyOf(obj));
assert_ne!(keyof_obj, TypeId::NEVER);
}
#[test]
fn test_async_iterator_result() {
let interner = TypeInterner::new();
let value_name = interner.intern_string("value");
let done_name = interner.intern_string("done");
let then_name = interner.intern_string("then");
let iter_result = interner.object(vec![
PropertyInfo::readonly(value_name, TypeId::STRING),
PropertyInfo::readonly(done_name, TypeId::BOOLEAN),
]);
let promise_iter = interner.object(vec![PropertyInfo::readonly(then_name, iter_result)]);
match interner.lookup(promise_iter) {
Some(TypeData::Object(shape_id)) => {
let shape = interner.object_shape(shape_id);
assert_eq!(shape.properties.len(), 1);
assert_eq!(shape.properties[0].name, then_name);
}
_ => panic!("Expected Object type"),
}
}
#[test]
fn test_exclude_basic_union() {
let interner = TypeInterner::new();
let _union = interner.union(vec![TypeId::STRING, TypeId::NUMBER, TypeId::BOOLEAN]);
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 cond = ConditionalType {
check_type: t_param,
extends_type: TypeId::STRING,
true_type: TypeId::NEVER,
false_type: t_param,
is_distributive: true,
};
let result = evaluate_conditional(&interner, &cond);
assert_ne!(result, TypeId::NEVER);
}
#[test]
fn test_exclude_removes_matching_type() {
let interner = TypeInterner::new();
let lit_a = interner.literal_string("a");
let lit_b = interner.literal_string("b");
let _lit_c = interner.literal_string("c");
let cond_a = ConditionalType {
check_type: lit_a,
extends_type: lit_a,
true_type: TypeId::NEVER,
false_type: lit_a,
is_distributive: false,
};
let result_a = evaluate_conditional(&interner, &cond_a);
assert_eq!(result_a, TypeId::NEVER);
let cond_b = ConditionalType {
check_type: lit_b,
extends_type: lit_a,
true_type: TypeId::NEVER,
false_type: lit_b,
is_distributive: false,
};
let result_b = evaluate_conditional(&interner, &cond_b);
assert_eq!(result_b, lit_b); }
#[test]
fn test_extract_basic_union() {
let interner = TypeInterner::new();
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 string_or_number = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let cond = ConditionalType {
check_type: t_param,
extends_type: string_or_number,
true_type: t_param,
false_type: TypeId::NEVER,
is_distributive: true,
};
let result = evaluate_conditional(&interner, &cond);
assert_ne!(result, TypeId::NEVER);
}
#[test]
fn test_extract_filters_to_matching() {
let interner = TypeInterner::new();
let lit_a = interner.literal_string("a");
let lit_1 = interner.literal_number(1.0);
let cond_a = ConditionalType {
check_type: lit_a,
extends_type: TypeId::STRING,
true_type: lit_a,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result_a = evaluate_conditional(&interner, &cond_a);
assert_eq!(result_a, lit_a);
let cond_1 = ConditionalType {
check_type: lit_1,
extends_type: TypeId::STRING,
true_type: lit_1,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result_1 = evaluate_conditional(&interner, &cond_1);
assert_eq!(result_1, TypeId::NEVER); }
#[test]
fn test_exclude_with_object_types() {
let interner = TypeInterner::new();
let a_name = interner.intern_string("a");
let obj_a = interner.object(vec![PropertyInfo::new(a_name, TypeId::STRING)]);
let cond = ConditionalType {
check_type: obj_a,
extends_type: TypeId::OBJECT,
true_type: TypeId::NEVER,
false_type: obj_a,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result == TypeId::NEVER || result == obj_a);
}
#[test]
fn test_extract_function_types() {
let interner = TypeInterner::new();
let void_fn = 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 cond = ConditionalType {
check_type: void_fn,
extends_type: void_fn,
true_type: void_fn,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, void_fn);
}
#[test]
fn test_exclude_null_undefined() {
let interner = TypeInterner::new();
let nullish = interner.union(vec![TypeId::NULL, TypeId::UNDEFINED]);
let cond_null = ConditionalType {
check_type: TypeId::NULL,
extends_type: nullish,
true_type: TypeId::NEVER,
false_type: TypeId::NULL,
is_distributive: false,
};
let result_null = evaluate_conditional(&interner, &cond_null);
assert_eq!(result_null, TypeId::NEVER);
let cond_string = ConditionalType {
check_type: TypeId::STRING,
extends_type: nullish,
true_type: TypeId::NEVER,
false_type: TypeId::STRING,
is_distributive: false,
};
let result_string = evaluate_conditional(&interner, &cond_string);
assert_eq!(result_string, TypeId::STRING);
}
#[test]
fn test_extract_literal_types() {
let interner = TypeInterner::new();
let lit_1 = interner.literal_number(1.0);
let lit_2 = interner.literal_number(2.0);
let cond_1 = ConditionalType {
check_type: lit_1,
extends_type: TypeId::NUMBER,
true_type: lit_1,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result_1 = evaluate_conditional(&interner, &cond_1);
assert_eq!(result_1, lit_1);
let cond_2 = ConditionalType {
check_type: lit_2,
extends_type: TypeId::NUMBER,
true_type: lit_2,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result_2 = evaluate_conditional(&interner, &cond_2);
assert_eq!(result_2, lit_2);
}
#[test]
fn test_distributive_conditional_with_type_param() {
let interner = TypeInterner::new();
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 yes = interner.literal_string("yes");
let no = interner.literal_string("no");
let cond = ConditionalType {
check_type: t_param,
extends_type: TypeId::STRING,
true_type: yes,
false_type: no,
is_distributive: true,
};
let result = evaluate_conditional(&interner, &cond);
assert_ne!(result, TypeId::NEVER);
}
#[test]
fn test_non_distributive_conditional() {
let interner = TypeInterner::new();
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_t = interner.tuple(vec![TupleElement {
type_id: t_param,
name: None,
optional: false,
rest: false,
}]);
let tuple_string = interner.tuple(vec![TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
}]);
let cond = ConditionalType {
check_type: tuple_t,
extends_type: tuple_string,
true_type: interner.literal_boolean(true),
false_type: interner.literal_boolean(false),
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result != TypeId::NEVER);
}
#[test]
fn test_exclude_with_any() {
let interner = TypeInterner::new();
let cond = ConditionalType {
check_type: TypeId::ANY,
extends_type: TypeId::STRING,
true_type: TypeId::NEVER,
false_type: TypeId::ANY,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result == TypeId::ANY || result == TypeId::NEVER);
}
#[test]
fn test_extract_with_never() {
let interner = TypeInterner::new();
let cond = ConditionalType {
check_type: TypeId::NEVER,
extends_type: TypeId::STRING,
true_type: TypeId::NEVER,
false_type: TypeId::NEVER,
is_distributive: true,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_exclude_with_unknown() {
let interner = TypeInterner::new();
let cond = ConditionalType {
check_type: TypeId::UNKNOWN,
extends_type: TypeId::STRING,
true_type: TypeId::NEVER,
false_type: TypeId::UNKNOWN,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, TypeId::UNKNOWN);
}
#[test]
fn test_complex_exclude_chain() {
let interner = TypeInterner::new();
let cond_num_str = ConditionalType {
check_type: TypeId::NUMBER,
extends_type: TypeId::STRING,
true_type: TypeId::NEVER,
false_type: TypeId::NUMBER,
is_distributive: false,
};
let step1_num = evaluate_conditional(&interner, &cond_num_str);
assert_eq!(step1_num, TypeId::NUMBER);
let cond_bool_str = ConditionalType {
check_type: TypeId::BOOLEAN,
extends_type: TypeId::STRING,
true_type: TypeId::NEVER,
false_type: TypeId::BOOLEAN,
is_distributive: false,
};
let step1_bool = evaluate_conditional(&interner, &cond_bool_str);
assert_eq!(step1_bool, TypeId::BOOLEAN);
let cond_num_num = ConditionalType {
check_type: TypeId::NUMBER,
extends_type: TypeId::NUMBER,
true_type: TypeId::NEVER,
false_type: TypeId::NUMBER,
is_distributive: false,
};
let step2_num = evaluate_conditional(&interner, &cond_num_num);
assert_eq!(step2_num, TypeId::NEVER);
let cond_bool_num = ConditionalType {
check_type: TypeId::BOOLEAN,
extends_type: TypeId::NUMBER,
true_type: TypeId::NEVER,
false_type: TypeId::BOOLEAN,
is_distributive: false,
};
let step2_bool = evaluate_conditional(&interner, &cond_bool_num);
assert_eq!(step2_bool, TypeId::BOOLEAN);
}
#[test]
fn test_extract_intersection() {
let interner = TypeInterner::new();
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 cond = ConditionalType {
check_type: intersection,
extends_type: obj_a,
true_type: intersection,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result == intersection || result == TypeId::NEVER);
}
#[test]
fn test_noinfer_identity_behavior() {
use crate::evaluate::evaluate_type;
let interner = TypeInterner::new();
let noinfer_string = interner.intern(TypeData::NoInfer(TypeId::STRING));
let evaluated = evaluate_type(&interner, noinfer_string);
assert_eq!(evaluated, TypeId::STRING);
let noinfer_number = interner.intern(TypeData::NoInfer(TypeId::NUMBER));
let evaluated = evaluate_type(&interner, noinfer_number);
assert_eq!(evaluated, TypeId::NUMBER);
let lit_hello = interner.literal_string("hello");
let noinfer_lit = interner.intern(TypeData::NoInfer(lit_hello));
let evaluated = evaluate_type(&interner, noinfer_lit);
assert_eq!(evaluated, lit_hello); }
#[test]
fn test_noinfer_with_union_type() {
use crate::evaluate::evaluate_type;
let interner = TypeInterner::new();
let union = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let noinfer_union = interner.intern(TypeData::NoInfer(union));
let evaluated = evaluate_type(&interner, noinfer_union);
match interner.lookup(evaluated) {
Some(TypeData::Union(_)) => {} other => panic!("Expected Union type, got {other:?}"),
}
}
#[test]
fn test_noinfer_in_function_param_position() {
use crate::infer::InferenceContext;
use crate::types::InferencePriority;
let interner = TypeInterner::new();
let mut ctx = InferenceContext::new(&interner);
let t_name = interner.intern_string("T");
let var_t = ctx.fresh_type_param(t_name, false);
let t_param = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: t_name,
constraint: None,
default: None,
is_const: false,
}));
let hello_lit = interner.literal_string("hello");
let number_type = TypeId::NUMBER;
ctx.infer_from_types(hello_lit, t_param, InferencePriority::NakedTypeVariable)
.unwrap();
let noinfer_t = interner.intern(TypeData::NoInfer(t_param));
ctx.infer_from_types(number_type, noinfer_t, InferencePriority::NakedTypeVariable)
.unwrap();
let result = ctx.resolve_with_constraints(var_t).unwrap();
assert_eq!(result, TypeId::STRING); }
#[test]
fn test_noinfer_inference_priority() {
use crate::infer::InferenceContext;
use crate::types::InferencePriority;
let interner = TypeInterner::new();
let mut ctx = InferenceContext::new(&interner);
let t_name = interner.intern_string("T");
let var_t = ctx.fresh_type_param(t_name, false);
let t_param = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: t_name,
constraint: None,
default: None,
is_const: false,
}));
let lit_hello = interner.literal_string("hello");
let lit_123 = interner.literal_number(123.0);
ctx.infer_from_types(lit_hello, t_param, InferencePriority::NakedTypeVariable)
.unwrap();
let noinfer_t = interner.intern(TypeData::NoInfer(t_param));
ctx.infer_from_types(lit_123, noinfer_t, InferencePriority::NakedTypeVariable)
.unwrap();
let result = ctx.resolve_with_constraints(var_t).unwrap();
assert_eq!(result, TypeId::STRING); assert_ne!(result, lit_123); }
#[test]
fn test_noinfer_with_conditional_type() {
let interner = TypeInterner::new();
let yes = interner.literal_string("yes");
let no = interner.literal_string("no");
let noinfer_string = interner.intern(TypeData::NoInfer(TypeId::STRING));
let cond = ConditionalType {
check_type: noinfer_string,
extends_type: TypeId::STRING,
true_type: yes,
false_type: no,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, yes);
}
#[test]
fn test_noinfer_nested() {
use crate::evaluate::evaluate_type;
let interner = TypeInterner::new();
let lit_42 = interner.literal_number(42.0);
let noinfer_42 = interner.intern(TypeData::NoInfer(lit_42));
let noinfer_noinfer_42 = interner.intern(TypeData::NoInfer(noinfer_42));
let evaluated = evaluate_type(&interner, noinfer_noinfer_42);
assert_eq!(evaluated, lit_42);
}
#[test]
fn test_noinfer_with_object_property() {
let interner = TypeInterner::new();
let value_name = interner.intern_string("value");
let t_param = TypeId::STRING;
let noinfer_t = interner.intern(TypeData::NoInfer(t_param));
let obj = interner.object(vec![PropertyInfo {
name: value_name,
type_id: noinfer_t,
write_type: noinfer_t,
optional: false,
readonly: false,
is_method: false,
visibility: Visibility::Public,
parent_id: None,
}]);
match interner.lookup(obj) {
Some(TypeData::Object(shape_id)) => {
let shape = interner.object_shape(shape_id);
assert_eq!(shape.properties.len(), 1);
assert_eq!(shape.properties[0].type_id, noinfer_t);
use crate::evaluate::evaluate_type;
let evaluated_prop = evaluate_type(&interner, shape.properties[0].type_id);
assert_eq!(evaluated_prop, t_param);
}
other => panic!("Expected Object type, got {other:?}"),
}
}
#[test]
fn test_noinfer_preserves_constraints() {
let interner = TypeInterner::new();
let t_name = interner.intern_string("T");
let t_constrained = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: t_name,
constraint: Some(TypeId::STRING),
default: None,
is_const: false,
}));
match interner.lookup(t_constrained) {
Some(TypeData::TypeParameter(info)) => {
assert_eq!(info.constraint, Some(TypeId::STRING));
}
_ => panic!("Expected TypeParameter"),
}
}
#[test]
fn test_noinfer_with_array() {
let interner = TypeInterner::new();
let string_array = interner.array(TypeId::STRING);
match interner.lookup(string_array) {
Some(TypeData::Array(elem)) => {
assert_eq!(elem, TypeId::STRING);
}
_ => panic!("Expected Array type"),
}
}
#[test]
fn test_noinfer_with_tuple() {
let interner = TypeInterner::new();
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,
},
]);
match interner.lookup(tuple) {
Some(TypeData::Tuple(list_id)) => {
let elements = interner.tuple_list(list_id);
assert_eq!(elements.len(), 2);
assert_eq!(elements[0].type_id, TypeId::STRING);
assert_eq!(elements[1].type_id, TypeId::NUMBER);
}
_ => panic!("Expected Tuple type"),
}
}
#[test]
fn test_noinfer_default_parameter() {
let interner = TypeInterner::new();
let t_name = interner.intern_string("T");
let x_name = interner.intern_string("x");
let t_with_default = TypeParamInfo {
name: t_name,
constraint: None,
default: Some(TypeId::STRING),
is_const: false,
};
let t_param = interner.intern(TypeData::TypeParameter(t_with_default.clone()));
let func = interner.function(FunctionShape {
type_params: vec![t_with_default],
params: vec![ParamInfo::required(x_name, t_param)],
this_type: None,
return_type: t_param,
type_predicate: None,
is_constructor: false,
is_method: false,
});
match interner.lookup(func) {
Some(TypeData::Function(shape_id)) => {
let shape = interner.function_shape(shape_id);
assert_eq!(shape.type_params[0].default, Some(TypeId::STRING));
}
_ => panic!("Expected Function type"),
}
}
#[test]
fn test_noinfer_multiple_type_params() {
let interner = TypeInterner::new();
let t_name = interner.intern_string("T");
let u_name = interner.intern_string("U");
let t_param = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: t_name,
constraint: None,
default: None,
is_const: false,
}));
let u_param = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: u_name,
constraint: None,
default: None,
is_const: false,
}));
let result_tuple = 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 func = interner.function(FunctionShape {
type_params: vec![
TypeParamInfo {
name: t_name,
constraint: None,
default: None,
is_const: false,
},
TypeParamInfo {
name: u_name,
constraint: None,
default: None,
is_const: false,
},
],
params: vec![
ParamInfo {
name: Some(interner.intern_string("a")),
type_id: t_param,
optional: false,
rest: false,
},
ParamInfo {
name: Some(interner.intern_string("b")),
type_id: u_param, optional: false,
rest: false,
},
],
this_type: None,
return_type: result_tuple,
type_predicate: None,
is_constructor: false,
is_method: false,
});
match interner.lookup(func) {
Some(TypeData::Function(shape_id)) => {
let shape = interner.function_shape(shape_id);
assert_eq!(shape.type_params.len(), 2);
assert_eq!(shape.params.len(), 2);
}
_ => panic!("Expected Function type"),
}
}
#[test]
fn test_noinfer_union_distribution() {
let interner = TypeInterner::new();
let union = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
match interner.lookup(union) {
Some(TypeData::Union(list_id)) => {
let members = interner.type_list(list_id);
assert_eq!(members.len(), 2);
}
_ => panic!("Expected Union type"),
}
}
#[test]
fn test_noinfer_in_return_position() {
let interner = TypeInterner::new();
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 func = interner.function(FunctionShape {
type_params: vec![TypeParamInfo {
name: t_name,
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_constructor: false,
is_method: false,
});
match interner.lookup(func) {
Some(TypeData::Function(shape_id)) => {
let shape = interner.function_shape(shape_id);
assert_eq!(shape.return_type, t_param);
}
_ => panic!("Expected Function type"),
}
}
#[test]
fn test_noinfer_conditional_true_branch() {
let interner = TypeInterner::new();
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 cond = ConditionalType {
check_type: t_param,
extends_type: TypeId::STRING,
true_type: t_param, false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
match interner.lookup(cond_type) {
Some(TypeData::Conditional(_)) => {}
_ => panic!("Expected Conditional type"),
}
}
#[test]
fn test_noinfer_with_infer_keyword() {
let interner = TypeInterner::new();
let u_name = interner.intern_string("U");
let infer_u = interner.intern(TypeData::Infer(TypeParamInfo {
name: u_name,
constraint: None,
default: None,
is_const: false,
}));
let cond = ConditionalType {
check_type: TypeId::STRING,
extends_type: infer_u, true_type: infer_u,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, TypeId::STRING);
}
#[test]
fn test_record_string_keys() {
let interner = TypeInterner::new();
let record = 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,
});
match interner.lookup(record) {
Some(TypeData::ObjectWithIndex(shape_id)) => {
let shape = interner.object_shape(shape_id);
assert!(shape.string_index.is_some());
assert_eq!(
shape.string_index.as_ref().unwrap().value_type,
TypeId::NUMBER
);
}
_ => panic!("Expected ObjectWithIndex type"),
}
}
#[test]
fn test_record_number_keys() {
let interner = TypeInterner::new();
let record = 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,
}),
});
match interner.lookup(record) {
Some(TypeData::ObjectWithIndex(shape_id)) => {
let shape = interner.object_shape(shape_id);
assert!(shape.number_index.is_some());
assert_eq!(
shape.number_index.as_ref().unwrap().value_type,
TypeId::STRING
);
}
_ => panic!("Expected ObjectWithIndex type"),
}
}
#[test]
fn test_record_literal_keys() {
let interner = TypeInterner::new();
let a_name = interner.intern_string("a");
let b_name = interner.intern_string("b");
let record = interner.object(vec![
PropertyInfo::new(a_name, TypeId::NUMBER),
PropertyInfo::new(b_name, TypeId::NUMBER),
]);
match interner.lookup(record) {
Some(TypeData::Object(shape_id)) => {
let shape = interner.object_shape(shape_id);
assert_eq!(shape.properties.len(), 2);
}
_ => panic!("Expected Object type"),
}
}
#[test]
fn test_record_with_object_value() {
let interner = TypeInterner::new();
let name_prop = interner.intern_string("name");
let inner_obj = interner.object(vec![PropertyInfo::new(name_prop, TypeId::STRING)]);
let record = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: inner_obj,
readonly: false,
}),
number_index: None,
});
match interner.lookup(record) {
Some(TypeData::ObjectWithIndex(shape_id)) => {
let shape = interner.object_shape(shape_id);
assert!(shape.string_index.is_some());
let idx = shape.string_index.as_ref().unwrap();
assert_ne!(idx.value_type, TypeId::STRING);
}
_ => panic!("Expected ObjectWithIndex type"),
}
}
#[test]
fn test_partial_simple_object() {
let interner = TypeInterner::new();
let a_name = interner.intern_string("a");
let b_name = interner.intern_string("b");
let partial_obj = interner.object(vec![
PropertyInfo {
name: a_name,
type_id: TypeId::STRING,
write_type: TypeId::STRING,
optional: true, readonly: false,
is_method: false,
visibility: Visibility::Public,
parent_id: None,
},
PropertyInfo {
name: b_name,
type_id: TypeId::NUMBER,
write_type: TypeId::NUMBER,
optional: true, readonly: false,
is_method: false,
visibility: Visibility::Public,
parent_id: None,
},
]);
match interner.lookup(partial_obj) {
Some(TypeData::Object(shape_id)) => {
let shape = interner.object_shape(shape_id);
assert!(shape.properties[0].optional);
assert!(shape.properties[1].optional);
}
_ => panic!("Expected Object type"),
}
}
#[test]
fn test_partial_nested_object() {
let interner = TypeInterner::new();
let value_name = interner.intern_string("value");
let inner_name = interner.intern_string("inner");
let inner_obj = interner.object(vec![PropertyInfo {
name: value_name,
type_id: TypeId::STRING,
write_type: TypeId::STRING,
optional: false, readonly: false,
is_method: false,
visibility: Visibility::Public,
parent_id: None,
}]);
let partial_outer = interner.object(vec![PropertyInfo {
name: inner_name,
type_id: inner_obj,
write_type: inner_obj,
optional: true, readonly: false,
is_method: false,
visibility: Visibility::Public,
parent_id: None,
}]);
match interner.lookup(partial_outer) {
Some(TypeData::Object(shape_id)) => {
let shape = interner.object_shape(shape_id);
assert!(shape.properties[0].optional);
match interner.lookup(shape.properties[0].type_id) {
Some(TypeData::Object(inner_shape_id)) => {
let inner = interner.object_shape(inner_shape_id);
assert!(!inner.properties[0].optional); }
_ => panic!("Expected inner Object type"),
}
}
_ => panic!("Expected Object type"),
}
}
#[test]
fn test_partial_deep_nesting() {
let interner = TypeInterner::new();
let x_name = interner.intern_string("x");
let y_name = interner.intern_string("y");
let point_name = interner.intern_string("point");
let deep_partial_point = interner.object(vec![
PropertyInfo {
name: x_name,
type_id: TypeId::NUMBER,
write_type: TypeId::NUMBER,
optional: true, readonly: false,
is_method: false,
visibility: Visibility::Public,
parent_id: None,
},
PropertyInfo {
name: y_name,
type_id: TypeId::NUMBER,
write_type: TypeId::NUMBER,
optional: true, readonly: false,
is_method: false,
visibility: Visibility::Public,
parent_id: None,
},
]);
let deep_partial_outer =
interner.object(vec![PropertyInfo::opt(point_name, deep_partial_point)]);
match interner.lookup(deep_partial_outer) {
Some(TypeData::Object(shape_id)) => {
let shape = interner.object_shape(shape_id);
assert!(shape.properties[0].optional);
match interner.lookup(shape.properties[0].type_id) {
Some(TypeData::Object(inner_id)) => {
let inner = interner.object_shape(inner_id);
assert!(inner.properties[0].optional);
assert!(inner.properties[1].optional);
}
_ => panic!("Expected nested Object"),
}
}
_ => panic!("Expected Object type"),
}
}
#[test]
fn test_required_simple_object() {
let interner = TypeInterner::new();
let a_name = interner.intern_string("a");
let b_name = interner.intern_string("b");
let required_obj = interner.object(vec![
PropertyInfo {
name: a_name,
type_id: TypeId::STRING,
write_type: TypeId::STRING,
optional: false, readonly: false,
is_method: false,
visibility: Visibility::Public,
parent_id: None,
},
PropertyInfo {
name: b_name,
type_id: TypeId::NUMBER,
write_type: TypeId::NUMBER,
optional: false, readonly: false,
is_method: false,
visibility: Visibility::Public,
parent_id: None,
},
]);
match interner.lookup(required_obj) {
Some(TypeData::Object(shape_id)) => {
let shape = interner.object_shape(shape_id);
assert!(!shape.properties[0].optional);
assert!(!shape.properties[1].optional);
}
_ => panic!("Expected Object type"),
}
}
#[test]
fn test_required_nested_optionals() {
let interner = TypeInterner::new();
let value_name = interner.intern_string("value");
let inner_name = interner.intern_string("inner");
let inner_obj = interner.object(vec![PropertyInfo {
name: value_name,
type_id: TypeId::STRING,
write_type: TypeId::STRING,
optional: true, readonly: false,
is_method: false,
visibility: Visibility::Public,
parent_id: None,
}]);
let required_outer = interner.object(vec![PropertyInfo {
name: inner_name,
type_id: inner_obj,
write_type: inner_obj,
optional: false, readonly: false,
is_method: false,
visibility: Visibility::Public,
parent_id: None,
}]);
match interner.lookup(required_outer) {
Some(TypeData::Object(shape_id)) => {
let shape = interner.object_shape(shape_id);
assert!(!shape.properties[0].optional); match interner.lookup(shape.properties[0].type_id) {
Some(TypeData::Object(inner_id)) => {
let inner = interner.object_shape(inner_id);
assert!(inner.properties[0].optional); }
_ => panic!("Expected inner Object"),
}
}
_ => panic!("Expected Object type"),
}
}
#[test]
fn test_required_mapped_type() {
let interner = TypeInterner::new();
let k_name = interner.intern_string("K");
let mapped = MappedType {
type_param: TypeParamInfo {
name: k_name,
constraint: None,
default: None,
is_const: false,
},
constraint: TypeId::STRING, name_type: None,
template: TypeId::NUMBER, readonly_modifier: None,
optional_modifier: Some(MappedModifier::Remove), };
let mapped_id = interner.mapped(mapped);
match interner.lookup(mapped_id) {
Some(TypeData::Mapped(mapped_id)) => {
let m = interner.mapped_type(mapped_id);
assert_eq!(m.optional_modifier, Some(MappedModifier::Remove));
}
_ => panic!("Expected Mapped type"),
}
}
#[test]
fn test_readonly_simple_object() {
let interner = TypeInterner::new();
let a_name = interner.intern_string("a");
let b_name = interner.intern_string("b");
let readonly_obj = interner.object(vec![
PropertyInfo {
name: a_name,
type_id: TypeId::STRING,
write_type: TypeId::STRING,
optional: false,
readonly: true, is_method: false,
visibility: Visibility::Public,
parent_id: None,
},
PropertyInfo {
name: b_name,
type_id: TypeId::NUMBER,
write_type: TypeId::NUMBER,
optional: false,
readonly: true, is_method: false,
visibility: Visibility::Public,
parent_id: None,
},
]);
match interner.lookup(readonly_obj) {
Some(TypeData::Object(shape_id)) => {
let shape = interner.object_shape(shape_id);
assert!(shape.properties[0].readonly);
assert!(shape.properties[1].readonly);
}
_ => panic!("Expected Object type"),
}
}
#[test]
fn test_readonly_array() {
let interner = TypeInterner::new();
let string_array = interner.array(TypeId::STRING);
let readonly_array = interner.intern(TypeData::ReadonlyType(string_array));
match interner.lookup(readonly_array) {
Some(TypeData::ReadonlyType(inner)) => {
assert_eq!(inner, string_array);
}
_ => panic!("Expected ReadonlyType"),
}
}
#[test]
fn test_readonly_tuple() {
let interner = TypeInterner::new();
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 readonly_tuple = interner.intern(TypeData::ReadonlyType(tuple));
match interner.lookup(readonly_tuple) {
Some(TypeData::ReadonlyType(inner)) => {
assert_eq!(inner, tuple);
match interner.lookup(inner) {
Some(TypeData::Tuple(_)) => {}
_ => panic!("Expected Tuple inside ReadonlyType"),
}
}
_ => panic!("Expected ReadonlyType"),
}
}
#[test]
fn test_readonly_nested() {
let interner = TypeInterner::new();
let items_name = interner.intern_string("items");
let string_array = interner.array(TypeId::STRING);
let readonly_obj = interner.object(vec![PropertyInfo {
name: items_name,
type_id: string_array, write_type: string_array,
optional: false,
readonly: true, is_method: false,
visibility: Visibility::Public,
parent_id: None,
}]);
match interner.lookup(readonly_obj) {
Some(TypeData::Object(shape_id)) => {
let shape = interner.object_shape(shape_id);
assert!(shape.properties[0].readonly);
match interner.lookup(shape.properties[0].type_id) {
Some(TypeData::Array(_)) => {} _ => panic!("Expected Array type"),
}
}
_ => panic!("Expected Object type"),
}
}
#[test]
fn test_readonly_mapped_type() {
let interner = TypeInterner::new();
let k_name = interner.intern_string("K");
let mapped = MappedType {
type_param: TypeParamInfo {
name: k_name,
constraint: None,
default: None,
is_const: false,
},
constraint: TypeId::STRING,
name_type: None,
template: TypeId::NUMBER,
readonly_modifier: Some(MappedModifier::Add), optional_modifier: None,
};
let mapped_id = interner.mapped(mapped);
match interner.lookup(mapped_id) {
Some(TypeData::Mapped(mapped_id)) => {
let m = interner.mapped_type(mapped_id);
assert_eq!(m.readonly_modifier, Some(MappedModifier::Add));
}
_ => panic!("Expected Mapped type"),
}
}
#[test]
fn test_record_with_union_value() {
let interner = TypeInterner::new();
let value_union = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let record = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: value_union,
readonly: false,
}),
number_index: None,
});
match interner.lookup(record) {
Some(TypeData::ObjectWithIndex(shape_id)) => {
let shape = interner.object_shape(shape_id);
let idx = shape.string_index.as_ref().unwrap();
match interner.lookup(idx.value_type) {
Some(TypeData::Union(_)) => {}
_ => panic!("Expected Union value type"),
}
}
_ => panic!("Expected ObjectWithIndex"),
}
}
#[test]
fn test_partial_with_methods() {
let interner = TypeInterner::new();
let greet_name = interner.intern_string("greet");
let method_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 partial_obj = interner.object(vec![PropertyInfo {
name: greet_name,
type_id: method_type,
write_type: method_type,
optional: true, readonly: false,
is_method: true,
visibility: Visibility::Public,
parent_id: None,
}]);
match interner.lookup(partial_obj) {
Some(TypeData::Object(shape_id)) => {
let shape = interner.object_shape(shape_id);
assert!(shape.properties[0].optional);
assert!(shape.properties[0].is_method);
}
_ => panic!("Expected Object type"),
}
}
#[test]
fn test_readonly_with_index_signature() {
let interner = TypeInterner::new();
let readonly_indexed = 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,
});
match interner.lookup(readonly_indexed) {
Some(TypeData::ObjectWithIndex(shape_id)) => {
let shape = interner.object_shape(shape_id);
assert!(shape.string_index.as_ref().unwrap().readonly);
}
_ => panic!("Expected ObjectWithIndex"),
}
}
#[test]
fn test_partial_required_inverse() {
let interner = TypeInterner::new();
let a_name = interner.intern_string("a");
let required_partial = interner.object(vec![PropertyInfo {
name: a_name,
type_id: TypeId::STRING,
write_type: TypeId::STRING,
optional: false, readonly: false,
is_method: false,
visibility: Visibility::Public,
parent_id: None,
}]);
match interner.lookup(required_partial) {
Some(TypeData::Object(shape_id)) => {
let shape = interner.object_shape(shape_id);
assert!(!shape.properties[0].optional);
}
_ => panic!("Expected Object type"),
}
}
#[test]
fn test_readonly_with_optional() {
let interner = TypeInterner::new();
let a_name = interner.intern_string("a");
let 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,
}]);
match interner.lookup(readonly_optional) {
Some(TypeData::Object(shape_id)) => {
let shape = interner.object_shape(shape_id);
assert!(shape.properties[0].optional);
assert!(shape.properties[0].readonly);
}
_ => panic!("Expected Object type"),
}
}
#[test]
fn test_template_infer_prefix_extraction() {
let interner = TypeInterner::new();
let infer_rest_name = interner.intern_string("Rest");
let infer_rest = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_rest_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("prefix")),
TemplateSpan::Type(infer_rest),
]);
let input = interner.literal_string("prefixSuffix");
let cond = ConditionalType {
check_type: input,
extends_type: pattern,
true_type: infer_rest,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
let expected = interner.literal_string("Suffix");
assert!(result == expected || result == TypeId::STRING || result == TypeId::NEVER);
}
#[test]
fn test_template_infer_suffix_extraction() {
let interner = TypeInterner::new();
let infer_start_name = interner.intern_string("Start");
let infer_start = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_start_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = interner.template_literal(vec![
TemplateSpan::Type(infer_start),
TemplateSpan::Text(interner.intern_string("Suffix")),
]);
let input = interner.literal_string("PrefixSuffix");
let cond = ConditionalType {
check_type: input,
extends_type: pattern,
true_type: infer_start,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
let expected = interner.literal_string("Prefix");
assert!(result == expected || result == TypeId::STRING || result == TypeId::NEVER);
}
#[test]
fn test_template_infer_middle_extraction() {
let interner = TypeInterner::new();
let infer_middle_name = interner.intern_string("Middle");
let infer_middle = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_middle_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("start")),
TemplateSpan::Type(infer_middle),
TemplateSpan::Text(interner.intern_string("end")),
]);
let input = interner.literal_string("startMIDDLEend");
let cond = ConditionalType {
check_type: input,
extends_type: pattern,
true_type: infer_middle,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
let expected = interner.literal_string("MIDDLE");
assert!(result == expected || result == TypeId::STRING || result == TypeId::NEVER);
}
#[test]
fn test_template_infer_no_match() {
let interner = TypeInterner::new();
let infer_rest_name = interner.intern_string("Rest");
let infer_rest = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_rest_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("prefix")),
TemplateSpan::Type(infer_rest),
]);
let input = interner.literal_string("wrongStart");
let cond = ConditionalType {
check_type: input,
extends_type: pattern,
true_type: infer_rest,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_template_multiple_infers() {
let interner = TypeInterner::new();
let infer_a_name = interner.intern_string("A");
let infer_a = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_a_name,
constraint: None,
default: None,
is_const: false,
}));
let infer_b_name = interner.intern_string("B");
let infer_b = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_b_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = interner.template_literal(vec![
TemplateSpan::Type(infer_a),
TemplateSpan::Text(interner.intern_string("-")),
TemplateSpan::Type(infer_b),
]);
let input = interner.literal_string("hello-world");
let result_tuple = interner.tuple(vec![
TupleElement {
type_id: infer_a,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: infer_b,
name: None,
optional: false,
rest: false,
},
]);
let cond = ConditionalType {
check_type: input,
extends_type: pattern,
true_type: result_tuple,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result != TypeId::ERROR);
}
#[test]
fn test_template_three_infers() {
let interner = TypeInterner::new();
let infer_a_name = interner.intern_string("A");
let infer_a = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_a_name,
constraint: None,
default: None,
is_const: false,
}));
let infer_b_name = interner.intern_string("B");
let infer_b = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_b_name,
constraint: None,
default: None,
is_const: false,
}));
let infer_c_name = interner.intern_string("C");
let infer_c = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_c_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = interner.template_literal(vec![
TemplateSpan::Type(infer_a),
TemplateSpan::Text(interner.intern_string("/")),
TemplateSpan::Type(infer_b),
TemplateSpan::Text(interner.intern_string("/")),
TemplateSpan::Type(infer_c),
]);
let input = interner.literal_string("x/y/z");
let result_tuple = interner.tuple(vec![
TupleElement {
type_id: infer_a,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: infer_b,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: infer_c,
name: None,
optional: false,
rest: false,
},
]);
let cond = ConditionalType {
check_type: input,
extends_type: pattern,
true_type: result_tuple,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result != TypeId::ERROR);
}
#[test]
fn test_template_union_distribution_simple() {
let interner = TypeInterner::new();
let infer_x_name = interner.intern_string("X");
let infer_x = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_x_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = interner.template_literal(vec![TemplateSpan::Type(infer_x)]);
let lit_a = interner.literal_string("a");
let lit_b = interner.literal_string("b");
let union_input = interner.union(vec![lit_a, lit_b]);
let cond = ConditionalType {
check_type: union_input,
extends_type: pattern,
true_type: infer_x,
false_type: TypeId::NEVER,
is_distributive: true,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result != TypeId::ERROR);
}
#[test]
fn test_template_union_prefix_distribution() {
let interner = TypeInterner::new();
let infer_name = interner.intern_string("Name");
let infer_name_type = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("get")),
TemplateSpan::Type(infer_name_type),
]);
let get_name = interner.literal_string("getName");
let get_value = interner.literal_string("getValue");
let other = interner.literal_string("other");
let union_input = interner.union(vec![get_name, get_value, other]);
let cond = ConditionalType {
check_type: union_input,
extends_type: pattern,
true_type: infer_name_type,
false_type: TypeId::NEVER,
is_distributive: true,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result != TypeId::ERROR);
}
#[test]
fn test_template_union_all_match() {
let interner = TypeInterner::new();
let infer_event_name = interner.intern_string("Event");
let infer_event = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_event_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("on")),
TemplateSpan::Type(infer_event),
]);
let on_click = interner.literal_string("onClick");
let on_hover = interner.literal_string("onHover");
let on_focus = interner.literal_string("onFocus");
let union_input = interner.union(vec![on_click, on_hover, on_focus]);
let cond = ConditionalType {
check_type: union_input,
extends_type: pattern,
true_type: infer_event,
false_type: TypeId::NEVER,
is_distributive: true,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result != TypeId::ERROR && result != TypeId::NEVER);
}
#[test]
fn test_template_constrained_infer_string() {
let interner = TypeInterner::new();
let infer_s_name = interner.intern_string("S");
let infer_s = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_s_name,
constraint: Some(TypeId::STRING),
default: None,
is_const: false,
}));
let pattern = interner.template_literal(vec![TemplateSpan::Type(infer_s)]);
let input = interner.literal_string("test");
let cond = ConditionalType {
check_type: input,
extends_type: pattern,
true_type: infer_s,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
let expected = interner.literal_string("test");
assert!(result == expected || result == TypeId::STRING || result == TypeId::NEVER);
}
#[test]
fn test_template_constrained_infer_literal_union() {
let interner = TypeInterner::new();
let lit_a = interner.literal_string("a");
let lit_b = interner.literal_string("b");
let constraint = interner.union(vec![lit_a, lit_b]);
let infer_s_name = interner.intern_string("S");
let infer_s = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_s_name,
constraint: Some(constraint),
default: None,
is_const: false,
}));
let pattern = interner.template_literal(vec![TemplateSpan::Type(infer_s)]);
let input = interner.literal_string("a");
let cond = ConditionalType {
check_type: input,
extends_type: pattern,
true_type: infer_s,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result == lit_a || result == TypeId::STRING || result == TypeId::NEVER);
}
#[test]
fn test_template_constrained_infer_violation() {
let interner = TypeInterner::new();
let lit_a = interner.literal_string("a");
let lit_b = interner.literal_string("b");
let constraint = interner.union(vec![lit_a, lit_b]);
let infer_s_name = interner.intern_string("S");
let infer_s = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_s_name,
constraint: Some(constraint),
default: None,
is_const: false,
}));
let pattern = interner.template_literal(vec![TemplateSpan::Type(infer_s)]);
let input = interner.literal_string("c");
let cond = ConditionalType {
check_type: input,
extends_type: pattern,
true_type: infer_s,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result == TypeId::NEVER || result != TypeId::ERROR);
}
#[test]
fn test_template_constrained_prefix_infer() {
let interner = TypeInterner::new();
let infer_s_name = interner.intern_string("S");
let infer_s = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_s_name,
constraint: Some(TypeId::STRING),
default: None,
is_const: false,
}));
let pattern = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("prefix")),
TemplateSpan::Type(infer_s),
]);
let input = interner.literal_string("prefixValue");
let cond = ConditionalType {
check_type: input,
extends_type: pattern,
true_type: infer_s,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
let expected = interner.literal_string("Value");
assert!(result == expected || result == TypeId::STRING || result == TypeId::NEVER);
}
#[test]
fn test_omit_this_parameter_basic() {
let interner = TypeInterner::new();
let foo_type = interner.object(vec![]);
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,
});
match interner.lookup(fn_with_this) {
Some(TypeData::Function(shape_id)) => {
let shape = interner.function_shape(shape_id);
assert!(shape.this_type.is_some());
}
_ => panic!("Expected Function type"),
}
match interner.lookup(fn_without_this) {
Some(TypeData::Function(shape_id)) => {
let shape = interner.function_shape(shape_id);
assert!(shape.this_type.is_none());
}
_ => panic!("Expected Function type"),
}
}
#[test]
fn test_omit_this_parameter_no_this() {
let interner = TypeInterner::new();
let fn_no_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,
});
match interner.lookup(fn_no_this) {
Some(TypeData::Function(shape_id)) => {
let shape = interner.function_shape(shape_id);
assert!(shape.this_type.is_none());
assert_eq!(shape.params.len(), 1);
}
_ => panic!("Expected Function type"),
}
}
#[test]
fn test_omit_this_preserves_generics() {
let interner = TypeInterner::new();
let t_name = interner.intern_string("T");
let u_name = interner.intern_string("U");
let u_param = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: u_name,
constraint: None,
default: None,
is_const: false,
}));
let fn_result = interner.function(FunctionShape {
type_params: vec![
TypeParamInfo {
name: t_name,
constraint: None,
default: None,
is_const: false,
},
TypeParamInfo {
name: u_name,
constraint: None,
default: None,
is_const: false,
},
],
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: u_param,
optional: false,
rest: false,
}],
this_type: None, return_type: u_param,
type_predicate: None,
is_constructor: false,
is_method: false,
});
match interner.lookup(fn_result) {
Some(TypeData::Function(shape_id)) => {
let shape = interner.function_shape(shape_id);
assert_eq!(shape.type_params.len(), 2);
assert!(shape.this_type.is_none());
}
_ => panic!("Expected Function type"),
}
}
#[test]
fn test_parameters_simple() {
let interner = TypeInterner::new();
let params_tuple = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: Some(interner.intern_string("a")),
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::NUMBER,
name: Some(interner.intern_string("b")),
optional: false,
rest: false,
},
]);
match interner.lookup(params_tuple) {
Some(TypeData::Tuple(list_id)) => {
let elements = interner.tuple_list(list_id);
assert_eq!(elements.len(), 2);
assert_eq!(elements[0].type_id, TypeId::STRING);
assert_eq!(elements[1].type_id, TypeId::NUMBER);
}
_ => panic!("Expected Tuple type"),
}
}
#[test]
fn test_parameters_with_optional() {
let interner = TypeInterner::new();
let params_tuple = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: Some(interner.intern_string("a")),
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::NUMBER,
name: Some(interner.intern_string("b")),
optional: true, rest: false,
},
]);
match interner.lookup(params_tuple) {
Some(TypeData::Tuple(list_id)) => {
let elements = interner.tuple_list(list_id);
assert!(!elements[0].optional);
assert!(elements[1].optional);
}
_ => panic!("Expected Tuple type"),
}
}
#[test]
fn test_parameters_with_rest() {
let interner = TypeInterner::new();
let number_array = interner.array(TypeId::NUMBER);
let params_tuple = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: Some(interner.intern_string("a")),
optional: false,
rest: false,
},
TupleElement {
type_id: number_array,
name: Some(interner.intern_string("rest")),
optional: false,
rest: true, },
]);
match interner.lookup(params_tuple) {
Some(TypeData::Tuple(list_id)) => {
let elements = interner.tuple_list(list_id);
assert!(!elements[0].rest);
assert!(elements[1].rest);
}
_ => panic!("Expected Tuple type"),
}
}
#[test]
fn test_parameters_empty() {
let interner = TypeInterner::new();
let params_tuple = interner.tuple(vec![]);
match interner.lookup(params_tuple) {
Some(TypeData::Tuple(list_id)) => {
let elements = interner.tuple_list(list_id);
assert_eq!(elements.len(), 0);
}
_ => panic!("Expected Tuple type"),
}
}
#[test]
fn test_parameters_with_overloads() {
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::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,
});
match interner.lookup(callable) {
Some(TypeData::Callable(shape_id)) => {
let shape = interner.callable_shape(shape_id);
assert_eq!(shape.call_signatures.len(), 2);
let last = &shape.call_signatures[1];
assert_eq!(last.params.len(), 2);
}
_ => panic!("Expected Callable type"),
}
}
#[test]
fn test_constructor_parameters_simple() {
let interner = TypeInterner::new();
let foo_type = interner.object(vec![]);
let ctor = 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: foo_type,
type_predicate: None,
is_constructor: true,
is_method: false,
});
match interner.lookup(ctor) {
Some(TypeData::Function(shape_id)) => {
let shape = interner.function_shape(shape_id);
assert!(shape.is_constructor);
assert_eq!(shape.params.len(), 1);
}
_ => panic!("Expected Function type"),
}
let params_tuple = interner.tuple(vec![TupleElement {
type_id: TypeId::STRING,
name: Some(interner.intern_string("a")),
optional: false,
rest: false,
}]);
match interner.lookup(params_tuple) {
Some(TypeData::Tuple(list_id)) => {
let elements = interner.tuple_list(list_id);
assert_eq!(elements.len(), 1);
}
_ => panic!("Expected Tuple type"),
}
}
#[test]
fn test_constructor_parameters_callable() {
let interner = TypeInterner::new();
let instance_type = interner.object(vec![PropertyInfo::new(
interner.intern_string("value"),
TypeId::NUMBER,
)]);
let callable = 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::NUMBER,
optional: false,
rest: false,
},
ParamInfo {
name: Some(interner.intern_string("y")),
type_id: TypeId::STRING,
optional: false,
rest: false,
},
],
this_type: None,
return_type: instance_type,
type_predicate: None,
is_method: false,
}],
properties: vec![],
string_index: None,
number_index: None,
});
match interner.lookup(callable) {
Some(TypeData::Callable(shape_id)) => {
let shape = interner.callable_shape(shape_id);
assert_eq!(shape.construct_signatures.len(), 1);
assert_eq!(shape.construct_signatures[0].params.len(), 2);
}
_ => panic!("Expected Callable type"),
}
}
#[test]
fn test_instance_type_simple() {
let interner = TypeInterner::new();
let foo_type = interner.object(vec![PropertyInfo::new(
interner.intern_string("name"),
TypeId::STRING,
)]);
let ctor = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: foo_type,
type_predicate: None,
is_constructor: true,
is_method: false,
});
match interner.lookup(ctor) {
Some(TypeData::Function(shape_id)) => {
let shape = interner.function_shape(shape_id);
assert!(shape.is_constructor);
assert_eq!(shape.return_type, foo_type);
}
_ => panic!("Expected Function type"),
}
}
#[test]
fn test_instance_type_callable() {
let interner = TypeInterner::new();
let instance = interner.object(vec![PropertyInfo::new(
interner.intern_string("value"),
TypeId::NUMBER,
)]);
let callable = 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,
});
match interner.lookup(callable) {
Some(TypeData::Callable(shape_id)) => {
let shape = interner.callable_shape(shape_id);
let ctor = &shape.construct_signatures[0];
assert_eq!(ctor.return_type, instance);
}
_ => panic!("Expected Callable type"),
}
}
#[test]
fn test_instance_type_with_generics() {
let interner = TypeInterner::new();
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 container = interner.object(vec![PropertyInfo::new(
interner.intern_string("value"),
t_param,
)]);
let ctor = interner.function(FunctionShape {
type_params: vec![TypeParamInfo {
name: t_name,
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: container,
type_predicate: None,
is_constructor: true,
is_method: false,
});
match interner.lookup(ctor) {
Some(TypeData::Function(shape_id)) => {
let shape = interner.function_shape(shape_id);
assert_eq!(shape.type_params.len(), 1);
match interner.lookup(shape.return_type) {
Some(TypeData::Object(obj_id)) => {
let obj = interner.object_shape(obj_id);
assert_eq!(obj.properties[0].type_id, t_param);
}
_ => panic!("Expected Object return type"),
}
}
_ => panic!("Expected Function type"),
}
}
#[test]
fn test_this_parameter_type() {
let interner = TypeInterner::new();
let foo_type = interner.object(vec![PropertyInfo::new(
interner.intern_string("id"),
TypeId::NUMBER,
)]);
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,
});
match interner.lookup(fn_with_this) {
Some(TypeData::Function(shape_id)) => {
let shape = interner.function_shape(shape_id);
assert_eq!(shape.this_type, Some(foo_type));
}
_ => panic!("Expected Function type"),
}
}
#[test]
fn test_return_type_simple() {
let interner = TypeInterner::new();
let func = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::STRING,
type_predicate: None,
is_constructor: false,
is_method: false,
});
match interner.lookup(func) {
Some(TypeData::Function(shape_id)) => {
let shape = interner.function_shape(shape_id);
assert_eq!(shape.return_type, TypeId::STRING);
}
_ => panic!("Expected Function type"),
}
}
#[test]
fn test_return_type_overloads() {
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::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,
});
match interner.lookup(callable) {
Some(TypeData::Callable(shape_id)) => {
let shape = interner.callable_shape(shape_id);
let last = &shape.call_signatures[shape.call_signatures.len() - 1];
assert_eq!(last.return_type, TypeId::NUMBER);
}
_ => panic!("Expected Callable type"),
}
}
#[test]
fn test_distributive_large_union_10_members() {
let interner = TypeInterner::new();
let lit_yes = interner.literal_string("yes");
let lit_no = interner.literal_string("no");
let members: Vec<TypeId> = (0..10)
.map(|i| interner.literal_string(&format!("item{i}")))
.collect();
let large_union = interner.union(members);
let cond = ConditionalType {
check_type: large_union,
extends_type: TypeId::STRING,
true_type: lit_yes,
false_type: lit_no,
is_distributive: true,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, lit_yes);
}
#[test]
fn test_distributive_large_union_15_members() {
let interner = TypeInterner::new();
let members: Vec<TypeId> = (0..15).map(|i| interner.literal_number(i as f64)).collect();
let large_union = interner.union(members);
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 cond = ConditionalType {
check_type: large_union,
extends_type: TypeId::NUMBER,
true_type: large_union, false_type: TypeId::NEVER,
is_distributive: true,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result != TypeId::NEVER && result != TypeId::ERROR);
}
#[test]
fn test_distributive_large_union_mixed_types() {
let interner = TypeInterner::new();
let lit_string = interner.literal_string("string");
let lit_other = interner.literal_string("other");
let mut members: Vec<TypeId> = Vec::new();
for i in 0..6 {
members.push(interner.literal_string(&format!("str{i}")));
}
for i in 0..6 {
members.push(interner.literal_number(i as f64));
}
let mixed_union = interner.union(members);
let cond = ConditionalType {
check_type: mixed_union,
extends_type: TypeId::STRING,
true_type: lit_string,
false_type: lit_other,
is_distributive: true,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result != TypeId::ERROR);
}
#[test]
fn test_distributive_large_union_20_members() {
let interner = TypeInterner::new();
let lit_yes = interner.literal_string("yes");
let members: Vec<TypeId> = (0..20)
.map(|i| interner.literal_string(&format!("value{i}")))
.collect();
let large_union = interner.union(members);
let cond = ConditionalType {
check_type: large_union,
extends_type: TypeId::STRING,
true_type: lit_yes,
false_type: TypeId::NEVER,
is_distributive: true,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, lit_yes);
}
#[test]
fn test_nested_distributive_two_levels_stress() {
let interner = TypeInterner::new();
let lit_a = interner.literal_string("a");
let lit_b = interner.literal_string("b");
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 union_ab = interner.union(vec![lit_a, lit_b]);
let inner_cond_id = interner.conditional(ConditionalType {
check_type: union_ab,
extends_type: lit_a,
true_type: lit_1,
false_type: lit_2,
is_distributive: true,
});
let outer_cond = ConditionalType {
check_type: union_ab,
extends_type: TypeId::STRING,
true_type: inner_cond_id,
false_type: lit_3,
is_distributive: true,
};
let result = evaluate_conditional(&interner, &outer_cond);
assert!(result != TypeId::ERROR);
}
#[test]
fn test_nested_distributive_three_levels_stress() {
let interner = TypeInterner::new();
let lit_a = interner.literal_string("a");
let lit_x = interner.literal_string("X");
let lit_y = interner.literal_string("Y");
let lit_z = interner.literal_string("Z");
let lit_w = interner.literal_string("W");
let inner_cond_id = interner.conditional(ConditionalType {
check_type: lit_a,
extends_type: lit_a,
true_type: lit_x,
false_type: lit_y,
is_distributive: false,
});
let middle_cond_id = interner.conditional(ConditionalType {
check_type: lit_a,
extends_type: TypeId::STRING,
true_type: inner_cond_id,
false_type: lit_z,
is_distributive: false,
});
let outer_cond = ConditionalType {
check_type: lit_a,
extends_type: TypeId::UNKNOWN,
true_type: middle_cond_id,
false_type: lit_w,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &outer_cond);
assert!(result == lit_x || result != TypeId::ERROR);
}
#[test]
fn test_nested_distributive_with_infer() {
let interner = TypeInterner::new();
let infer_a_name = interner.intern_string("A");
let infer_a = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_a_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
infer_a,
)]);
let hello = interner.literal_string("hello");
let input = interner.object(vec![PropertyInfo::new(interner.intern_string("a"), hello)]);
let inner_cond_id = interner.conditional(ConditionalType {
check_type: infer_a,
extends_type: TypeId::STRING,
true_type: infer_a,
false_type: TypeId::NEVER,
is_distributive: false,
});
let outer_cond = ConditionalType {
check_type: input,
extends_type: pattern,
true_type: inner_cond_id,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &outer_cond);
assert!(result == hello || result != TypeId::ERROR);
}
#[test]
fn test_distribution_over_intersection_simple() {
let interner = TypeInterner::new();
let lit_yes = interner.literal_string("yes");
let lit_no = interner.literal_string("no");
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 = interner.intersection(vec![obj_a, obj_b]);
let cond = ConditionalType {
check_type: intersection,
extends_type: TypeId::OBJECT,
true_type: lit_yes,
false_type: lit_no,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result == lit_yes || result == lit_no);
}
#[test]
fn test_distribution_over_intersection_with_union() {
let interner = TypeInterner::new();
let lit_a = interner.literal_string("a");
let lit_b = interner.literal_string("b");
let union_ab = interner.union(vec![lit_a, lit_b]);
let intersection = interner.intersection(vec![TypeId::STRING, union_ab]);
let cond = ConditionalType {
check_type: intersection,
extends_type: TypeId::STRING,
true_type: intersection,
false_type: TypeId::NEVER,
is_distributive: true,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result != TypeId::ERROR);
}
#[test]
fn test_distribution_over_intersection_never() {
let interner = TypeInterner::new();
let intersection = interner.intersection(vec![TypeId::STRING, TypeId::NUMBER]);
let cond = ConditionalType {
check_type: intersection,
extends_type: TypeId::STRING,
true_type: intersection,
false_type: TypeId::NEVER,
is_distributive: true,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result == TypeId::NEVER || result != TypeId::ERROR);
}
#[test]
fn test_distribution_over_intersection_three_types() {
let interner = TypeInterner::new();
let lit_yes = interner.literal_string("yes");
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 obj_c = interner.object(vec![PropertyInfo::new(
interner.intern_string("c"),
TypeId::BOOLEAN,
)]);
let intersection = interner.intersection(vec![obj_a, obj_b, obj_c]);
let cond = ConditionalType {
check_type: intersection,
extends_type: TypeId::OBJECT,
true_type: lit_yes,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result == lit_yes || result != TypeId::ERROR);
}
#[test]
fn test_never_filtering_basic() {
let interner = TypeInterner::new();
let lit_yes = interner.literal_string("yes");
let lit_no = interner.literal_string("no");
let cond = ConditionalType {
check_type: TypeId::NEVER,
extends_type: TypeId::NEVER,
true_type: lit_yes,
false_type: lit_no,
is_distributive: true,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result == TypeId::NEVER || result == lit_yes);
}
#[test]
fn test_never_filtering_in_union() {
let interner = TypeInterner::new();
let union_with_never = interner.union(vec![TypeId::STRING, TypeId::NEVER]);
let cond = ConditionalType {
check_type: union_with_never,
extends_type: TypeId::STRING,
true_type: union_with_never,
false_type: TypeId::NEVER,
is_distributive: true,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result != TypeId::ERROR);
}
#[test]
fn test_never_filtering_exclude_pattern() {
let interner = TypeInterner::new();
let lit_a = interner.literal_string("a");
let lit_b = interner.literal_string("b");
let lit_c = interner.literal_string("c");
let union_abc = interner.union(vec![lit_a, lit_b, lit_c]);
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 cond = ConditionalType {
check_type: union_abc,
extends_type: lit_a,
true_type: TypeId::NEVER,
false_type: union_abc, is_distributive: true,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result != TypeId::ERROR);
}
#[test]
fn test_never_filtering_extract_pattern() {
let interner = TypeInterner::new();
let lit_a = interner.literal_string("a");
let lit_b = interner.literal_string("b");
let lit_1 = interner.literal_number(1.0);
let lit_2 = interner.literal_number(2.0);
let mixed_union = interner.union(vec![lit_a, lit_b, lit_1, lit_2]);
let cond = ConditionalType {
check_type: mixed_union,
extends_type: TypeId::STRING,
true_type: mixed_union,
false_type: TypeId::NEVER,
is_distributive: true,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result != TypeId::ERROR && result != TypeId::NEVER);
}
#[test]
fn test_never_filtering_all_filtered() {
let interner = TypeInterner::new();
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 number_union = interner.union(vec![lit_1, lit_2, lit_3]);
let cond = ConditionalType {
check_type: number_union,
extends_type: TypeId::STRING,
true_type: number_union,
false_type: TypeId::NEVER,
is_distributive: true,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_never_filtering_nonnullable() {
let interner = TypeInterner::new();
let nullable_union = interner.union(vec![TypeId::STRING, TypeId::NULL, TypeId::UNDEFINED]);
let null_or_undefined = interner.union(vec![TypeId::NULL, TypeId::UNDEFINED]);
let cond = ConditionalType {
check_type: nullable_union,
extends_type: null_or_undefined,
true_type: TypeId::NEVER,
false_type: nullable_union,
is_distributive: true,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result != TypeId::ERROR);
}
#[test]
fn test_awaited_basic_promise() {
let interner = TypeInterner::new();
let then_name = interner.intern_string("then");
let promise_string = interner.object(vec![PropertyInfo::readonly(then_name, TypeId::STRING)]);
let infer_name = interner.intern_string("U");
let infer_u = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = interner.object(vec![PropertyInfo::readonly(then_name, infer_u)]);
let cond = ConditionalType {
check_type: promise_string,
extends_type: pattern,
true_type: infer_u,
false_type: promise_string,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, TypeId::STRING);
}
#[test]
fn test_awaited_promise_number() {
let interner = TypeInterner::new();
let then_name = interner.intern_string("then");
let promise_number = interner.object(vec![PropertyInfo::readonly(then_name, TypeId::NUMBER)]);
let infer_name = interner.intern_string("U");
let infer_u = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = interner.object(vec![PropertyInfo::readonly(then_name, infer_u)]);
let cond = ConditionalType {
check_type: promise_number,
extends_type: pattern,
true_type: infer_u,
false_type: promise_number,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, TypeId::NUMBER);
}
#[test]
fn test_awaited_nested_promise() {
let interner = TypeInterner::new();
let then_name = interner.intern_string("then");
let inner_promise = interner.object(vec![PropertyInfo::readonly(then_name, TypeId::STRING)]);
let outer_promise = interner.object(vec![PropertyInfo::readonly(then_name, inner_promise)]);
let infer_name = interner.intern_string("U");
let infer_u = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = interner.object(vec![PropertyInfo::readonly(then_name, infer_u)]);
let cond1 = ConditionalType {
check_type: outer_promise,
extends_type: pattern,
true_type: infer_u,
false_type: outer_promise,
is_distributive: false,
};
let first_unwrap = evaluate_conditional(&interner, &cond1);
assert_eq!(first_unwrap, inner_promise);
let cond2 = ConditionalType {
check_type: first_unwrap,
extends_type: pattern,
true_type: infer_u,
false_type: first_unwrap,
is_distributive: false,
};
let second_unwrap = evaluate_conditional(&interner, &cond2);
assert_eq!(second_unwrap, TypeId::STRING);
}
#[test]
fn test_awaited_string_passthrough() {
let interner = TypeInterner::new();
let then_name = interner.intern_string("then");
let infer_name = interner.intern_string("U");
let infer_u = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = interner.object(vec![PropertyInfo::readonly(then_name, infer_u)]);
let cond = ConditionalType {
check_type: TypeId::STRING,
extends_type: pattern,
true_type: infer_u,
false_type: TypeId::STRING, is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, TypeId::STRING);
}
#[test]
fn test_awaited_number_passthrough() {
let interner = TypeInterner::new();
let then_name = interner.intern_string("then");
let infer_name = interner.intern_string("U");
let infer_u = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = interner.object(vec![PropertyInfo::readonly(then_name, infer_u)]);
let cond = ConditionalType {
check_type: TypeId::NUMBER,
extends_type: pattern,
true_type: infer_u,
false_type: TypeId::NUMBER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, TypeId::NUMBER);
}
#[test]
fn test_awaited_null_undefined_passthrough() {
let interner = TypeInterner::new();
let then_name = interner.intern_string("then");
let infer_name = interner.intern_string("U");
let infer_u = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = interner.object(vec![PropertyInfo::readonly(then_name, infer_u)]);
let cond_null = ConditionalType {
check_type: TypeId::NULL,
extends_type: pattern,
true_type: infer_u,
false_type: TypeId::NULL,
is_distributive: false,
};
let result_null = evaluate_conditional(&interner, &cond_null);
assert_eq!(result_null, TypeId::NULL);
let cond_undef = ConditionalType {
check_type: TypeId::UNDEFINED,
extends_type: pattern,
true_type: infer_u,
false_type: TypeId::UNDEFINED,
is_distributive: false,
};
let result_undef = evaluate_conditional(&interner, &cond_undef);
assert_eq!(result_undef, TypeId::UNDEFINED);
}
#[test]
fn test_awaited_promise_union_distributive() {
let interner = TypeInterner::new();
let then_name = interner.intern_string("then");
let promise_string = interner.object(vec![PropertyInfo::readonly(then_name, TypeId::STRING)]);
let promise_number = interner.object(vec![PropertyInfo::readonly(then_name, TypeId::NUMBER)]);
let infer_name = interner.intern_string("U");
let infer_u = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = interner.object(vec![PropertyInfo::readonly(then_name, infer_u)]);
let cond_string = ConditionalType {
check_type: promise_string,
extends_type: pattern,
true_type: infer_u,
false_type: promise_string,
is_distributive: false,
};
let result_string = evaluate_conditional(&interner, &cond_string);
assert_eq!(result_string, TypeId::STRING);
let cond_number = ConditionalType {
check_type: promise_number,
extends_type: pattern,
true_type: infer_u,
false_type: promise_number,
is_distributive: false,
};
let result_number = evaluate_conditional(&interner, &cond_number);
assert_eq!(result_number, TypeId::NUMBER);
let awaited_union = interner.union(vec![result_string, result_number]);
match interner.lookup(awaited_union) {
Some(TypeData::Union(list_id)) => {
let members = interner.type_list(list_id);
assert_eq!(members.len(), 2);
}
_ => panic!("Expected Union type"),
}
}
#[test]
fn test_awaited_mixed_promise_union() {
let interner = TypeInterner::new();
let then_name = interner.intern_string("then");
let promise_string = interner.object(vec![PropertyInfo::readonly(then_name, TypeId::STRING)]);
let infer_name = interner.intern_string("U");
let infer_u = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = interner.object(vec![PropertyInfo::readonly(then_name, infer_u)]);
let cond_promise = ConditionalType {
check_type: promise_string,
extends_type: pattern,
true_type: infer_u,
false_type: promise_string,
is_distributive: false,
};
let result_promise = evaluate_conditional(&interner, &cond_promise);
assert_eq!(result_promise, TypeId::STRING);
let cond_number = ConditionalType {
check_type: TypeId::NUMBER,
extends_type: pattern,
true_type: infer_u,
false_type: TypeId::NUMBER,
is_distributive: false,
};
let result_number = evaluate_conditional(&interner, &cond_number);
assert_eq!(result_number, TypeId::NUMBER);
let mixed_result = interner.union(vec![result_promise, result_number]);
match interner.lookup(mixed_result) {
Some(TypeData::Union(list_id)) => {
let members = interner.type_list(list_id);
assert_eq!(members.len(), 2);
}
_ => panic!("Expected Union type"),
}
}
#[test]
fn test_awaited_promise_void() {
let interner = TypeInterner::new();
let then_name = interner.intern_string("then");
let promise_void = interner.object(vec![PropertyInfo::readonly(then_name, TypeId::VOID)]);
let infer_name = interner.intern_string("U");
let infer_u = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = interner.object(vec![PropertyInfo::readonly(then_name, infer_u)]);
let cond = ConditionalType {
check_type: promise_void,
extends_type: pattern,
true_type: infer_u,
false_type: promise_void,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, TypeId::VOID);
}
#[test]
fn test_awaited_promise_never() {
let interner = TypeInterner::new();
let then_name = interner.intern_string("then");
let promise_never = interner.object(vec![PropertyInfo::readonly(then_name, TypeId::NEVER)]);
let infer_name = interner.intern_string("U");
let infer_u = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = interner.object(vec![PropertyInfo::readonly(then_name, infer_u)]);
let cond = ConditionalType {
check_type: promise_never,
extends_type: pattern,
true_type: infer_u,
false_type: promise_never,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_awaited_promise_any() {
let interner = TypeInterner::new();
let then_name = interner.intern_string("then");
let promise_any = interner.object(vec![PropertyInfo::readonly(then_name, TypeId::ANY)]);
let infer_name = interner.intern_string("U");
let infer_u = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = interner.object(vec![PropertyInfo::readonly(then_name, infer_u)]);
let cond = ConditionalType {
check_type: promise_any,
extends_type: pattern,
true_type: infer_u,
false_type: promise_any,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, TypeId::ANY);
}
#[test]
fn test_awaited_promise_object() {
let interner = TypeInterner::new();
let then_name = interner.intern_string("then");
let value_name = interner.intern_string("value");
let inner_obj = interner.object(vec![PropertyInfo::new(value_name, TypeId::NUMBER)]);
let promise_obj = interner.object(vec![PropertyInfo::readonly(then_name, inner_obj)]);
let infer_name = interner.intern_string("U");
let infer_u = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = interner.object(vec![PropertyInfo::readonly(then_name, infer_u)]);
let cond = ConditionalType {
check_type: promise_obj,
extends_type: pattern,
true_type: infer_u,
false_type: promise_obj,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, inner_obj);
}
#[test]
fn test_awaited_promise_array() {
let interner = TypeInterner::new();
let then_name = interner.intern_string("then");
let string_array = interner.array(TypeId::STRING);
let promise_array = interner.object(vec![PropertyInfo::readonly(then_name, string_array)]);
let infer_name = interner.intern_string("U");
let infer_u = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = interner.object(vec![PropertyInfo::readonly(then_name, infer_u)]);
let cond = ConditionalType {
check_type: promise_array,
extends_type: pattern,
true_type: infer_u,
false_type: promise_array,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, string_array);
}
#[test]
fn test_awaited_triple_nested() {
let interner = TypeInterner::new();
let then_name = interner.intern_string("then");
let level1 = interner.object(vec![PropertyInfo::readonly(then_name, TypeId::BOOLEAN)]);
let level2 = interner.object(vec![PropertyInfo::readonly(then_name, level1)]);
let level3 = interner.object(vec![PropertyInfo::readonly(then_name, level2)]);
let infer_name = interner.intern_string("U");
let infer_u = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = interner.object(vec![PropertyInfo::readonly(then_name, infer_u)]);
let cond1 = ConditionalType {
check_type: level3,
extends_type: pattern,
true_type: infer_u,
false_type: level3,
is_distributive: false,
};
let unwrap1 = evaluate_conditional(&interner, &cond1);
assert_eq!(unwrap1, level2);
let cond2 = ConditionalType {
check_type: unwrap1,
extends_type: pattern,
true_type: infer_u,
false_type: unwrap1,
is_distributive: false,
};
let unwrap2 = evaluate_conditional(&interner, &cond2);
assert_eq!(unwrap2, level1);
let cond3 = ConditionalType {
check_type: unwrap2,
extends_type: pattern,
true_type: infer_u,
false_type: unwrap2,
is_distributive: false,
};
let unwrap3 = evaluate_conditional(&interner, &cond3);
assert_eq!(unwrap3, TypeId::BOOLEAN);
}
#[test]
fn test_recursive_type_simple_tree() {
use crate::evaluate::TypeEvaluator;
use crate::subtype::TypeEnvironment;
let interner = TypeInterner::new();
let tree_ref = interner.lazy(DefId(1));
let left_name = interner.intern_string("left");
let right_name = interner.intern_string("right");
let value_name = interner.intern_string("value");
let tree_body = interner.object(vec![
PropertyInfo::opt(left_name, tree_ref),
PropertyInfo::opt(right_name, tree_ref),
PropertyInfo::new(value_name, TypeId::NUMBER),
]);
let mut env = TypeEnvironment::new();
env.insert_def(DefId(1), tree_body);
let mut evaluator = TypeEvaluator::with_resolver(&interner, &env);
let result = evaluator.evaluate(tree_ref);
match interner.lookup(result).unwrap() {
TypeData::Object(shape_id) => {
let shape = interner.object_shape(shape_id);
assert_eq!(shape.properties.len(), 3);
let has_number = shape.properties.iter().any(|p| p.type_id == TypeId::NUMBER);
assert!(has_number, "Should have value property with NUMBER type");
}
_ => panic!("Expected Object type"),
}
}
#[test]
fn test_recursive_type_linked_list() {
use crate::evaluate::TypeEvaluator;
use crate::subtype::TypeEnvironment;
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 list_ref = interner.lazy(DefId(1));
let list_t = interner.application(list_ref, vec![t_type]);
let next_type = interner.union(vec![list_t, TypeId::NULL]);
let value_name = interner.intern_string("value");
let next_name = interner.intern_string("next");
let list_body = interner.object(vec![
PropertyInfo::new(value_name, t_type),
PropertyInfo::new(next_name, next_type),
]);
let list_string = interner.application(list_ref, vec![TypeId::STRING]);
let mut env = TypeEnvironment::new();
env.insert_def_with_params(DefId(1), list_body, vec![t_param]);
let mut evaluator = TypeEvaluator::with_resolver(&interner, &env);
let result = evaluator.evaluate(list_string);
match interner.lookup(result).unwrap() {
TypeData::Object(shape_id) => {
let shape = interner.object_shape(shape_id);
assert_eq!(shape.properties.len(), 2);
let has_string = shape.properties.iter().any(|p| p.type_id == TypeId::STRING);
assert!(
has_string,
"Should have value property substituted to STRING"
);
}
_ => panic!("Expected Object type"),
}
}
#[test]
fn test_recursive_type_json_value() {
let interner = TypeInterner::new();
let json_ref = interner.lazy(DefId(1));
let json_array = interner.array(json_ref);
let json_object = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: json_ref,
readonly: false,
}),
number_index: None,
});
let json_body = interner.union(vec![
TypeId::STRING,
TypeId::NUMBER,
TypeId::BOOLEAN,
TypeId::NULL,
json_array,
json_object,
]);
match interner.lookup(json_body).unwrap() {
TypeData::Union(list_id) => {
let members = interner.type_list(list_id);
assert_eq!(members.len(), 6);
}
_ => panic!("Expected Union type"),
}
}
#[test]
fn test_recursive_type_expression_ast() {
let interner = TypeInterner::new();
let expr_ref = interner.lazy(DefId(1));
let literal_kind = interner.literal_string("literal");
let literal_type = interner.object(vec![
PropertyInfo::new(interner.intern_string("kind"), literal_kind),
PropertyInfo::new(interner.intern_string("value"), TypeId::NUMBER),
]);
let binary_kind = interner.literal_string("binary");
let binary_type = interner.object(vec![
PropertyInfo::new(interner.intern_string("kind"), binary_kind),
PropertyInfo::new(interner.intern_string("left"), expr_ref),
PropertyInfo::new(interner.intern_string("right"), expr_ref),
PropertyInfo::new(interner.intern_string("op"), TypeId::STRING),
]);
let expr_body = interner.union(vec![literal_type, binary_type]);
match interner.lookup(expr_body).unwrap() {
TypeData::Union(list_id) => {
let members = interner.type_list(list_id);
assert_eq!(members.len(), 2);
}
_ => panic!("Expected Union type"),
}
}
#[test]
fn test_recursive_type_dom_node() {
let interner = TypeInterner::new();
let node_ref = interner.lazy(DefId(1));
let children_array = interner.array(node_ref);
let attrs_type = 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 node_body = interner.object(vec![
PropertyInfo::new(interner.intern_string("tagName"), TypeId::STRING),
PropertyInfo::new(interner.intern_string("children"), children_array),
PropertyInfo::new(interner.intern_string("attributes"), attrs_type),
]);
match interner.lookup(node_body).unwrap() {
TypeData::Object(shape_id) => {
let shape = interner.object_shape(shape_id);
assert_eq!(shape.properties.len(), 3);
let has_string = shape.properties.iter().any(|p| p.type_id == TypeId::STRING);
assert!(has_string, "Should have tagName property with STRING type");
}
_ => panic!("Expected Object type"),
}
}
#[test]
fn test_mutually_recursive_types_a_b() {
let interner = TypeInterner::new();
let a_ref = interner.lazy(DefId(1));
let b_ref = interner.lazy(DefId(2));
let a_body = interner.object(vec![
PropertyInfo::new(interner.intern_string("value"), TypeId::NUMBER),
PropertyInfo::opt(interner.intern_string("b"), b_ref),
]);
let b_body = interner.object(vec![
PropertyInfo::new(interner.intern_string("value"), TypeId::STRING),
PropertyInfo::opt(interner.intern_string("a"), a_ref),
]);
match interner.lookup(a_body).unwrap() {
TypeData::Object(shape_id) => {
let shape = interner.object_shape(shape_id);
assert_eq!(shape.properties.len(), 2);
let has_number = shape.properties.iter().any(|p| p.type_id == TypeId::NUMBER);
assert!(has_number, "A should have NUMBER property");
}
_ => panic!("Expected Object type for A"),
}
match interner.lookup(b_body).unwrap() {
TypeData::Object(shape_id) => {
let shape = interner.object_shape(shape_id);
assert_eq!(shape.properties.len(), 2);
let has_string = shape.properties.iter().any(|p| p.type_id == TypeId::STRING);
assert!(has_string, "B should have STRING property");
}
_ => panic!("Expected Object type for B"),
}
}
#[test]
fn test_mutually_recursive_types_parent_child() {
let interner = TypeInterner::new();
let parent_ref = interner.lazy(DefId(1));
let child_ref = interner.lazy(DefId(2));
let children_array = interner.array(child_ref);
let parent_body = interner.object(vec![
PropertyInfo::new(interner.intern_string("name"), TypeId::STRING),
PropertyInfo::new(interner.intern_string("children"), children_array),
]);
let child_body = interner.object(vec![
PropertyInfo::new(interner.intern_string("name"), TypeId::STRING),
PropertyInfo::new(interner.intern_string("parent"), parent_ref),
]);
match interner.lookup(parent_body).unwrap() {
TypeData::Object(shape_id) => {
let shape = interner.object_shape(shape_id);
assert_eq!(shape.properties.len(), 2);
}
_ => panic!("Expected Object type for Parent"),
}
match interner.lookup(child_body).unwrap() {
TypeData::Object(shape_id) => {
let shape = interner.object_shape(shape_id);
assert_eq!(shape.properties.len(), 2);
}
_ => panic!("Expected Object type for Child"),
}
}
#[test]
fn test_mutually_recursive_types_three_way() {
let interner = TypeInterner::new();
let x_ref = interner.lazy(DefId(1));
let y_ref = interner.lazy(DefId(2));
let z_ref = interner.lazy(DefId(3));
let x_body = interner.object(vec![PropertyInfo::new(interner.intern_string("y"), y_ref)]);
let y_body = interner.object(vec![PropertyInfo::new(interner.intern_string("z"), z_ref)]);
let z_body = interner.object(vec![PropertyInfo::new(interner.intern_string("x"), x_ref)]);
match interner.lookup(x_body).unwrap() {
TypeData::Object(_) => {}
_ => panic!("Expected Object type for X"),
}
match interner.lookup(y_body).unwrap() {
TypeData::Object(_) => {}
_ => panic!("Expected Object type for Y"),
}
match interner.lookup(z_body).unwrap() {
TypeData::Object(_) => {}
_ => panic!("Expected Object type for Z"),
}
}
#[test]
fn test_mutually_recursive_types_state_machine() {
let interner = TypeInterner::new();
let state_a_ref = interner.lazy(DefId(1));
let state_b_ref = interner.lazy(DefId(2));
let state_c_ref = interner.lazy(DefId(3));
let type_a = interner.literal_string("a");
let type_b = interner.literal_string("b");
let type_c = interner.literal_string("c");
let next_from_a = interner.union(vec![state_b_ref, state_c_ref]);
let state_a_body = interner.object(vec![
PropertyInfo::new(interner.intern_string("type"), type_a),
PropertyInfo::new(interner.intern_string("next"), next_from_a),
]);
let next_from_b = interner.union(vec![state_a_ref, state_c_ref]);
let state_b_body = interner.object(vec![
PropertyInfo::new(interner.intern_string("type"), type_b),
PropertyInfo::new(interner.intern_string("next"), next_from_b),
]);
let next_from_c = interner.union(vec![state_a_ref, state_b_ref]);
let state_c_body = interner.object(vec![
PropertyInfo::new(interner.intern_string("type"), type_c),
PropertyInfo::new(interner.intern_string("next"), next_from_c),
]);
for body in [state_a_body, state_b_body, state_c_body] {
match interner.lookup(body).unwrap() {
TypeData::Object(shape_id) => {
let shape = interner.object_shape(shape_id);
assert_eq!(shape.properties.len(), 2);
}
_ => panic!("Expected Object type"),
}
}
}
#[test]
fn test_mutually_recursive_types_request_response() {
use crate::evaluate::TypeEvaluator;
use crate::subtype::TypeEnvironment;
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 request_ref = interner.lazy(DefId(1));
let response_ref = interner.lazy(DefId(2));
let response_t = interner.application(response_ref, vec![t_type]);
let request_body = interner.object(vec![
PropertyInfo::new(interner.intern_string("id"), TypeId::NUMBER),
PropertyInfo::new(interner.intern_string("response"), response_t),
]);
let request_t = interner.application(request_ref, vec![t_type]);
let response_body = interner.object(vec![
PropertyInfo::new(interner.intern_string("data"), t_type),
PropertyInfo::new(interner.intern_string("request"), request_t),
]);
let mut env = TypeEnvironment::new();
env.insert_def_with_params(DefId(1), request_body, vec![t_param.clone()]);
env.insert_def_with_params(DefId(2), response_body, vec![t_param]);
let request_string = interner.application(request_ref, vec![TypeId::STRING]);
let mut evaluator = TypeEvaluator::with_resolver(&interner, &env);
let result = evaluator.evaluate(request_string);
match interner.lookup(result).unwrap() {
TypeData::Object(shape_id) => {
let shape = interner.object_shape(shape_id);
assert_eq!(shape.properties.len(), 2);
let has_number = shape.properties.iter().any(|p| p.type_id == TypeId::NUMBER);
assert!(
has_number,
"Request should have id property with NUMBER type"
);
}
_ => panic!("Expected Object type"),
}
}
#[test]
fn test_recursive_conditional_type_flatten() {
let interner = TypeInterner::new();
let number_array = interner.array(TypeId::NUMBER);
let cond = ConditionalType {
check_type: number_array,
extends_type: interner.array(TypeId::ANY),
true_type: TypeId::NUMBER, false_type: number_array,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, TypeId::NUMBER);
}
#[test]
fn test_recursive_conditional_type_unwrap_promise() {
let interner = TypeInterner::new();
let _promise_number = interner.object(vec![PropertyInfo {
name: interner.intern_string("then"),
type_id: TypeId::NUMBER, write_type: TypeId::NUMBER,
optional: false,
readonly: false,
is_method: true,
visibility: Visibility::Public,
parent_id: None,
}]);
let cond = ConditionalType {
check_type: TypeId::NUMBER,
extends_type: TypeId::OBJECT,
true_type: TypeId::NUMBER,
false_type: TypeId::NUMBER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, TypeId::NUMBER);
}
#[test]
fn test_recursive_conditional_type_deep_partial() {
let interner = TypeInterner::new();
let cond_primitive = ConditionalType {
check_type: TypeId::STRING,
extends_type: TypeId::OBJECT,
true_type: TypeId::OBJECT, false_type: TypeId::STRING,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond_primitive);
assert_eq!(result, TypeId::STRING);
}
#[test]
fn test_recursive_conditional_type_nested_array() {
let interner = TypeInterner::new();
let cond_string = ConditionalType {
check_type: TypeId::STRING,
extends_type: interner.array(TypeId::ANY),
true_type: interner.array(TypeId::STRING), false_type: TypeId::STRING,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond_string);
assert_eq!(result, TypeId::STRING);
let string_array = interner.array(TypeId::STRING);
let cond_array = ConditionalType {
check_type: string_array,
extends_type: interner.array(TypeId::ANY),
true_type: interner.array(TypeId::STRING), false_type: string_array,
is_distributive: false,
};
let result_array = evaluate_conditional(&interner, &cond_array);
assert_eq!(result_array, interner.array(TypeId::STRING));
}
#[test]
fn test_recursive_conditional_type_deep_readonly() {
let interner = TypeInterner::new();
let obj = interner.object(vec![PropertyInfo::new(
interner.intern_string("x"),
TypeId::NUMBER,
)]);
let readonly_obj = interner.object(vec![PropertyInfo::readonly(
interner.intern_string("x"),
TypeId::NUMBER,
)]);
let cond = ConditionalType {
check_type: obj,
extends_type: TypeId::OBJECT,
true_type: readonly_obj,
false_type: obj,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
match interner.lookup(result).unwrap() {
TypeData::Object(shape_id) => {
let shape = interner.object_shape(shape_id);
assert!(shape.properties[0].readonly);
}
_ => panic!("Expected Object type"),
}
}
#[test]
fn test_depth_limited_recursion_level_1() {
use crate::evaluate::TypeEvaluator;
use crate::subtype::TypeEnvironment;
let interner = TypeInterner::new();
let node_ref = interner.lazy(DefId(1));
let node_body = interner.object(vec![
PropertyInfo::new(interner.intern_string("value"), TypeId::NUMBER),
PropertyInfo::opt(interner.intern_string("child"), node_ref),
]);
let mut env = TypeEnvironment::new();
env.insert_def(DefId(1), node_body);
let mut evaluator = TypeEvaluator::with_resolver(&interner, &env);
let result = evaluator.evaluate(node_ref);
let value_atom = interner.intern_string("value");
match interner.lookup(result).unwrap() {
TypeData::Object(shape_id) => {
let shape = interner.object_shape(shape_id);
assert_eq!(shape.properties.len(), 2);
let value_prop = shape
.properties
.iter()
.find(|p| p.name == value_atom)
.expect("Should have 'value' property");
assert_eq!(value_prop.type_id, TypeId::NUMBER);
}
_ => panic!("Expected Object type"),
}
}
#[test]
fn test_depth_limited_recursion_generic() {
let interner = TypeInterner::new();
let zero = interner.literal_number(0.0);
let cond_depth_0 = ConditionalType {
check_type: zero,
extends_type: zero,
true_type: TypeId::STRING,
false_type: interner.array(TypeId::STRING),
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond_depth_0);
assert_eq!(result, TypeId::STRING);
let one = interner.literal_number(1.0);
let cond_depth_1 = ConditionalType {
check_type: one,
extends_type: zero,
true_type: TypeId::STRING,
false_type: interner.array(TypeId::STRING),
is_distributive: false,
};
let result_1 = evaluate_conditional(&interner, &cond_depth_1);
assert_eq!(result_1, interner.array(TypeId::STRING));
}
#[test]
fn test_depth_limited_recursion_tuple_builder() {
let interner = TypeInterner::new();
let level_0 = interner.tuple(vec![]);
let level_1 = interner.tuple(vec![TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
}]);
let level_2 = 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,
},
]);
match interner.lookup(level_0).unwrap() {
TypeData::Tuple(list_id) => {
let elems = interner.tuple_list(list_id);
assert_eq!(elems.len(), 0);
}
_ => panic!("Expected Tuple type"),
}
match interner.lookup(level_1).unwrap() {
TypeData::Tuple(list_id) => {
let elems = interner.tuple_list(list_id);
assert_eq!(elems.len(), 1);
}
_ => panic!("Expected Tuple type"),
}
match interner.lookup(level_2).unwrap() {
TypeData::Tuple(list_id) => {
let elems = interner.tuple_list(list_id);
assert_eq!(elems.len(), 2);
}
_ => panic!("Expected Tuple type"),
}
}
#[test]
fn test_depth_limited_recursion_max_expansion() {
use crate::evaluate::TypeEvaluator;
use crate::subtype::TypeEnvironment;
let interner = TypeInterner::new();
let infinite_ref = interner.lazy(DefId(1));
let infinite_body = interner.object(vec![PropertyInfo::new(
interner.intern_string("next"),
infinite_ref,
)]);
let mut env = TypeEnvironment::new();
env.insert_def(DefId(1), infinite_body);
let mut evaluator = TypeEvaluator::with_resolver(&interner, &env);
let result = evaluator.evaluate(infinite_ref);
match interner.lookup(result).unwrap() {
TypeData::Object(shape_id) => {
let shape = interner.object_shape(shape_id);
assert_eq!(shape.properties.len(), 1);
}
_ => panic!("Expected Object type"),
}
}
#[test]
fn test_depth_limited_recursion_path_tracking() {
use crate::evaluate::TypeEvaluator;
use crate::subtype::TypeEnvironment;
let interner = TypeInterner::new();
let a_ref = interner.lazy(DefId(1));
let b_ref = interner.lazy(DefId(2));
let a_body = interner.object(vec![PropertyInfo::new(interner.intern_string("b"), b_ref)]);
let b_body = interner.object(vec![PropertyInfo::new(interner.intern_string("a"), a_ref)]);
let mut env = TypeEnvironment::new();
env.insert_def(DefId(1), a_body);
env.insert_def(DefId(2), b_body);
let mut evaluator = TypeEvaluator::with_resolver(&interner, &env);
let result = evaluator.evaluate(a_ref);
match interner.lookup(result).unwrap() {
TypeData::Object(shape_id) => {
let shape = interner.object_shape(shape_id);
assert_eq!(shape.properties.len(), 1);
}
_ => panic!("Expected Object type"),
}
}
#[test]
fn test_infer_optional_property_present() {
let interner = TypeInterner::new();
let infer_p_name = interner.intern_string("P");
let infer_p = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_p_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = interner.object(vec![PropertyInfo::opt(
interner.intern_string("prop"),
infer_p,
)]);
let input = interner.object(vec![PropertyInfo::new(
interner.intern_string("prop"),
TypeId::STRING,
)]);
let cond = ConditionalType {
check_type: input,
extends_type: pattern,
true_type: infer_p,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result == TypeId::STRING || result == TypeId::NEVER || result != TypeId::ERROR);
}
#[test]
fn test_infer_optional_property_missing() {
let interner = TypeInterner::new();
let infer_p_name = interner.intern_string("P");
let infer_p = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_p_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = interner.object(vec![PropertyInfo::opt(
interner.intern_string("prop"),
infer_p,
)]);
let input = interner.object(vec![]);
let cond = ConditionalType {
check_type: input,
extends_type: pattern,
true_type: infer_p,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result != TypeId::ERROR);
}
#[test]
fn test_infer_optional_property_with_undefined() {
let interner = TypeInterner::new();
let infer_p_name = interner.intern_string("P");
let infer_p = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_p_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = interner.object(vec![PropertyInfo::opt(
interner.intern_string("prop"),
infer_p,
)]);
let string_or_undefined = interner.union(vec![TypeId::STRING, TypeId::UNDEFINED]);
let input = interner.object(vec![PropertyInfo::new(
interner.intern_string("prop"),
string_or_undefined,
)]);
let cond = ConditionalType {
check_type: input,
extends_type: pattern,
true_type: infer_p,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result != TypeId::ERROR);
}
#[test]
fn test_infer_with_default_type_used() {
let interner = TypeInterner::new();
let infer_p_name = interner.intern_string("P");
let infer_p = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_p_name,
constraint: None,
default: Some(TypeId::STRING),
is_const: false,
}));
let pattern = interner.object(vec![PropertyInfo::new(
interner.intern_string("prop"),
infer_p,
)]);
let input = interner.object(vec![PropertyInfo::new(
interner.intern_string("prop"),
TypeId::NUMBER,
)]);
let cond = ConditionalType {
check_type: input,
extends_type: pattern,
true_type: infer_p,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result == TypeId::NUMBER || result != TypeId::ERROR);
}
#[test]
fn test_infer_with_default_type_fallback() {
let interner = TypeInterner::new();
let infer_p_name = interner.intern_string("P");
let infer_p = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_p_name,
constraint: None,
default: Some(TypeId::STRING),
is_const: false,
}));
let pattern = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
infer_p,
)]);
let input = interner.object(vec![PropertyInfo::new(
interner.intern_string("b"),
TypeId::NUMBER,
)]);
let cond = ConditionalType {
check_type: input,
extends_type: pattern,
true_type: infer_p,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_infer_with_default_and_constraint() {
let interner = TypeInterner::new();
let empty_object = interner.object(vec![]);
let infer_p_name = interner.intern_string("P");
let infer_p = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_p_name,
constraint: Some(TypeId::OBJECT),
default: Some(empty_object),
is_const: false,
}));
let pattern = interner.object(vec![PropertyInfo::new(
interner.intern_string("prop"),
infer_p,
)]);
let inner_obj = interner.object(vec![PropertyInfo::new(
interner.intern_string("x"),
TypeId::NUMBER,
)]);
let input = interner.object(vec![PropertyInfo::new(
interner.intern_string("prop"),
inner_obj,
)]);
let cond = ConditionalType {
check_type: input,
extends_type: pattern,
true_type: infer_p,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result == inner_obj || result != TypeId::ERROR);
}
#[test]
fn test_infer_discriminated_union_kind() {
let interner = TypeInterner::new();
let infer_k_name = interner.intern_string("K");
let infer_k = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_k_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = interner.object(vec![PropertyInfo::new(
interner.intern_string("kind"),
infer_k,
)]);
let circle = interner.literal_string("circle");
let circle_obj = interner.object(vec![PropertyInfo::new(
interner.intern_string("kind"),
circle,
)]);
let square = interner.literal_string("square");
let square_obj = interner.object(vec![PropertyInfo::new(
interner.intern_string("kind"),
square,
)]);
let union_input = interner.union(vec![circle_obj, square_obj]);
let cond = ConditionalType {
check_type: union_input,
extends_type: pattern,
true_type: infer_k,
false_type: TypeId::NEVER,
is_distributive: true,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result != TypeId::ERROR && result != TypeId::NEVER);
}
#[test]
fn test_infer_discriminated_union_with_extra_props() {
let interner = TypeInterner::new();
let infer_t_name = interner.intern_string("T");
let infer_t = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_t_name,
constraint: None,
default: None,
is_const: false,
}));
let infer_d_name = interner.intern_string("D");
let infer_d = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_d_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = interner.object(vec![
PropertyInfo::new(interner.intern_string("type"), infer_t),
PropertyInfo::new(interner.intern_string("data"), infer_d),
]);
let success = interner.literal_string("success");
let input = interner.object(vec![
PropertyInfo::new(interner.intern_string("type"), success),
PropertyInfo::new(interner.intern_string("data"), TypeId::NUMBER),
]);
let result_tuple = interner.tuple(vec![
TupleElement {
type_id: infer_t,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: infer_d,
name: None,
optional: false,
rest: false,
},
]);
let cond = ConditionalType {
check_type: input,
extends_type: pattern,
true_type: result_tuple,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result != TypeId::ERROR);
}
#[test]
fn test_infer_discriminated_union_filter() {
let interner = TypeInterner::new();
let circle = interner.literal_string("circle");
let square = interner.literal_string("square");
let pattern = interner.object(vec![PropertyInfo::new(
interner.intern_string("kind"),
circle,
)]);
let circle_obj = interner.object(vec![
PropertyInfo::new(interner.intern_string("kind"), circle),
PropertyInfo::new(interner.intern_string("radius"), TypeId::NUMBER),
]);
let square_obj = interner.object(vec![
PropertyInfo::new(interner.intern_string("kind"), square),
PropertyInfo::new(interner.intern_string("side"), TypeId::NUMBER),
]);
let union_input = interner.union(vec![circle_obj, square_obj]);
let cond = ConditionalType {
check_type: union_input,
extends_type: pattern,
true_type: union_input,
false_type: TypeId::NEVER,
is_distributive: true,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result != TypeId::ERROR);
}
#[test]
fn test_multiple_infers_both_constrained() {
let interner = TypeInterner::new();
let infer_a_name = interner.intern_string("A");
let infer_a = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_a_name,
constraint: Some(TypeId::STRING),
default: None,
is_const: false,
}));
let infer_b_name = interner.intern_string("B");
let infer_b = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_b_name,
constraint: Some(TypeId::NUMBER),
default: None,
is_const: false,
}));
let pattern = interner.function(FunctionShape {
type_params: vec![],
params: vec![
ParamInfo {
name: Some(interner.intern_string("a")),
type_id: infer_a,
optional: false,
rest: false,
},
ParamInfo {
name: Some(interner.intern_string("b")),
type_id: infer_b,
optional: false,
rest: false,
},
],
this_type: None,
return_type: TypeId::ANY,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let hello = interner.literal_string("hello");
let lit_42 = interner.literal_number(42.0);
let input = interner.function(FunctionShape {
type_params: vec![],
params: vec![
ParamInfo {
name: Some(interner.intern_string("a")),
type_id: hello,
optional: false,
rest: false,
},
ParamInfo {
name: Some(interner.intern_string("b")),
type_id: lit_42,
optional: false,
rest: false,
},
],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let result_tuple = interner.tuple(vec![
TupleElement {
type_id: infer_a,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: infer_b,
name: None,
optional: false,
rest: false,
},
]);
let cond = ConditionalType {
check_type: input,
extends_type: pattern,
true_type: result_tuple,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result != TypeId::ERROR);
}
#[test]
fn test_multiple_infers_constraint_violation() {
let interner = TypeInterner::new();
let infer_a_name = interner.intern_string("A");
let infer_a = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_a_name,
constraint: Some(TypeId::STRING),
default: None,
is_const: false,
}));
let infer_b_name = interner.intern_string("B");
let infer_b = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_b_name,
constraint: Some(TypeId::STRING), default: None,
is_const: false,
}));
let pattern = interner.function(FunctionShape {
type_params: vec![],
params: vec![
ParamInfo {
name: Some(interner.intern_string("a")),
type_id: infer_a,
optional: false,
rest: false,
},
ParamInfo {
name: Some(interner.intern_string("b")),
type_id: infer_b,
optional: false,
rest: false,
},
],
this_type: None,
return_type: TypeId::ANY,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let hello = interner.literal_string("hello");
let lit_42 = interner.literal_number(42.0);
let input = interner.function(FunctionShape {
type_params: vec![],
params: vec![
ParamInfo {
name: Some(interner.intern_string("a")),
type_id: hello,
optional: false,
rest: false,
},
ParamInfo {
name: Some(interner.intern_string("b")),
type_id: lit_42, optional: false,
rest: false,
},
],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let result_tuple = interner.tuple(vec![
TupleElement {
type_id: infer_a,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: infer_b,
name: None,
optional: false,
rest: false,
},
]);
let cond = ConditionalType {
check_type: input,
extends_type: pattern,
true_type: result_tuple,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result != TypeId::ERROR);
}
#[test]
fn test_multiple_infers_same_constraint() {
let interner = TypeInterner::new();
let infer_x_name = interner.intern_string("X");
let infer_x = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_x_name,
constraint: Some(TypeId::STRING),
default: None,
is_const: false,
}));
let infer_y_name = interner.intern_string("Y");
let infer_y = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_y_name,
constraint: Some(TypeId::STRING),
default: None,
is_const: false,
}));
let pattern = interner.object(vec![
PropertyInfo::new(interner.intern_string("a"), infer_x),
PropertyInfo::new(interner.intern_string("b"), infer_y),
]);
let foo = interner.literal_string("foo");
let bar = interner.literal_string("bar");
let input = interner.object(vec![
PropertyInfo::new(interner.intern_string("a"), foo),
PropertyInfo::new(interner.intern_string("b"), bar),
]);
let result_tuple = interner.tuple(vec![
TupleElement {
type_id: infer_x,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: infer_y,
name: None,
optional: false,
rest: false,
},
]);
let cond = ConditionalType {
check_type: input,
extends_type: pattern,
true_type: result_tuple,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result != TypeId::ERROR);
}
#[test]
fn test_multiple_infers_different_constraints() {
let interner = TypeInterner::new();
let infer_s_name = interner.intern_string("S");
let infer_s = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_s_name,
constraint: Some(TypeId::STRING),
default: None,
is_const: false,
}));
let infer_n_name = interner.intern_string("N");
let infer_n = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_n_name,
constraint: Some(TypeId::NUMBER),
default: None,
is_const: false,
}));
let infer_b_name = interner.intern_string("B");
let infer_b = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_b_name,
constraint: Some(TypeId::BOOLEAN),
default: None,
is_const: false,
}));
let pattern = interner.object(vec![
PropertyInfo::new(interner.intern_string("str"), infer_s),
PropertyInfo::new(interner.intern_string("num"), infer_n),
PropertyInfo::new(interner.intern_string("bool"), infer_b),
]);
let test_str = interner.literal_string("test");
let lit_123 = interner.literal_number(123.0);
let lit_true = interner.literal_boolean(true);
let input = interner.object(vec![
PropertyInfo::new(interner.intern_string("str"), test_str),
PropertyInfo::new(interner.intern_string("num"), lit_123),
PropertyInfo::new(interner.intern_string("bool"), lit_true),
]);
let result_tuple = interner.tuple(vec![
TupleElement {
type_id: infer_s,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: infer_n,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: infer_b,
name: None,
optional: false,
rest: false,
},
]);
let cond = ConditionalType {
check_type: input,
extends_type: pattern,
true_type: result_tuple,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result != TypeId::ERROR);
}
#[test]
fn test_typeof_variable_reference_basic() {
use crate::{SymbolRef, TypeEnvironment};
let interner = TypeInterner::new();
let mut env = TypeEnvironment::new();
let sym = SymbolRef(1);
env.insert(sym, TypeId::NUMBER);
let type_query = interner.intern(TypeData::TypeQuery(sym));
let mut evaluator = TypeEvaluator::with_resolver(&interner, &env);
let result = evaluator.evaluate(type_query);
assert_eq!(result, TypeId::NUMBER);
}
#[test]
fn test_typeof_variable_reference_object_type() {
use crate::{SymbolRef, TypeEnvironment};
let interner = TypeInterner::new();
let mut env = TypeEnvironment::new();
let obj = interner.object(vec![
PropertyInfo::new(interner.intern_string("a"), TypeId::STRING),
PropertyInfo::new(interner.intern_string("b"), TypeId::NUMBER),
]);
let sym = SymbolRef(1);
env.insert(sym, obj);
let type_query = interner.intern(TypeData::TypeQuery(sym));
let mut evaluator = TypeEvaluator::with_resolver(&interner, &env);
let result = evaluator.evaluate(type_query);
assert_eq!(result, obj);
}
#[test]
fn test_typeof_variable_reference_array_type() {
use crate::{SymbolRef, TypeEnvironment};
let interner = TypeInterner::new();
let mut env = TypeEnvironment::new();
let string_array = interner.array(TypeId::STRING);
let sym = SymbolRef(1);
env.insert(sym, string_array);
let type_query = interner.intern(TypeData::TypeQuery(sym));
let mut evaluator = TypeEvaluator::with_resolver(&interner, &env);
let result = evaluator.evaluate(type_query);
assert_eq!(result, string_array);
}
#[test]
fn test_typeof_imported_value_basic() {
use crate::{SymbolRef, TypeEnvironment};
let interner = TypeInterner::new();
let mut env = TypeEnvironment::new();
let imported_sym = SymbolRef(100);
env.insert(imported_sym, TypeId::BOOLEAN);
let type_query = interner.intern(TypeData::TypeQuery(imported_sym));
let mut evaluator = TypeEvaluator::with_resolver(&interner, &env);
let result = evaluator.evaluate(type_query);
assert_eq!(result, TypeId::BOOLEAN);
}
#[test]
fn test_typeof_imported_value_complex() {
use crate::{SymbolRef, TypeEnvironment};
let interner = TypeInterner::new();
let mut env = TypeEnvironment::new();
let config_type = interner.object(vec![
PropertyInfo::new(interner.intern_string("port"), TypeId::NUMBER),
PropertyInfo::new(interner.intern_string("host"), TypeId::STRING),
]);
let imported_sym = SymbolRef(200);
env.insert(imported_sym, config_type);
let type_query = interner.intern(TypeData::TypeQuery(imported_sym));
let mut evaluator = TypeEvaluator::with_resolver(&interner, &env);
let result = evaluator.evaluate(type_query);
assert_eq!(result, config_type);
}
#[test]
fn test_typeof_function_type() {
use crate::{SymbolRef, TypeEnvironment};
let interner = TypeInterner::new();
let mut env = TypeEnvironment::new();
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,
});
let sym = SymbolRef(1);
env.insert(sym, fn_type);
let type_query = interner.intern(TypeData::TypeQuery(sym));
let mut evaluator = TypeEvaluator::with_resolver(&interner, &env);
let result = evaluator.evaluate(type_query);
assert_eq!(result, fn_type);
}
#[test]
fn test_typeof_function_multiple_params() {
use crate::{SymbolRef, TypeEnvironment};
let interner = TypeInterner::new();
let mut env = TypeEnvironment::new();
let fn_type = 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::BOOLEAN,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let sym = SymbolRef(1);
env.insert(sym, fn_type);
let type_query = interner.intern(TypeData::TypeQuery(sym));
let mut evaluator = TypeEvaluator::with_resolver(&interner, &env);
let result = evaluator.evaluate(type_query);
assert_eq!(result, fn_type);
}
#[test]
fn test_typeof_const_string_literal() {
use crate::{SymbolRef, TypeEnvironment};
let interner = TypeInterner::new();
let mut env = TypeEnvironment::new();
let hello_literal = interner.literal_string("hello");
let sym = SymbolRef(1);
env.insert(sym, hello_literal);
let type_query = interner.intern(TypeData::TypeQuery(sym));
let mut evaluator = TypeEvaluator::with_resolver(&interner, &env);
let result = evaluator.evaluate(type_query);
assert_eq!(result, hello_literal);
}
#[test]
fn test_typeof_const_number_literal() {
use crate::{SymbolRef, TypeEnvironment};
let interner = TypeInterner::new();
let mut env = TypeEnvironment::new();
let num_literal = interner.literal_number(42.0);
let sym = SymbolRef(1);
env.insert(sym, num_literal);
let type_query = interner.intern(TypeData::TypeQuery(sym));
let mut evaluator = TypeEvaluator::with_resolver(&interner, &env);
let result = evaluator.evaluate(type_query);
assert_eq!(result, num_literal);
}
#[test]
fn test_typeof_const_tuple_readonly() {
use crate::{SymbolRef, TypeEnvironment};
let interner = TypeInterner::new();
let mut env = TypeEnvironment::new();
let one = interner.literal_number(1.0);
let two = interner.literal_number(2.0);
let three = interner.literal_number(3.0);
let tuple = 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,
},
]);
let readonly_tuple = interner.intern(TypeData::ReadonlyType(tuple));
let sym = SymbolRef(1);
env.insert(sym, readonly_tuple);
let type_query = interner.intern(TypeData::TypeQuery(sym));
let mut evaluator = TypeEvaluator::with_resolver(&interner, &env);
let result = evaluator.evaluate(type_query);
assert_eq!(result, readonly_tuple);
}
#[test]
fn test_typeof_const_object_readonly() {
use crate::{SymbolRef, TypeEnvironment};
let interner = TypeInterner::new();
let mut env = TypeEnvironment::new();
let one = interner.literal_number(1.0);
let hello = interner.literal_string("hello");
let readonly_obj = interner.object(vec![
PropertyInfo::readonly(interner.intern_string("a"), one),
PropertyInfo::readonly(interner.intern_string("b"), hello),
]);
let sym = SymbolRef(1);
env.insert(sym, readonly_obj);
let type_query = interner.intern(TypeData::TypeQuery(sym));
let mut evaluator = TypeEvaluator::with_resolver(&interner, &env);
let result = evaluator.evaluate(type_query);
assert_eq!(result, readonly_obj);
}
#[test]
fn test_typeof_unresolved_passes_through() {
use crate::{SymbolRef, TypeEnvironment};
let interner = TypeInterner::new();
let env = TypeEnvironment::new();
let unknown_sym = SymbolRef(999);
let type_query = interner.intern(TypeData::TypeQuery(unknown_sym));
let mut evaluator = TypeEvaluator::with_resolver(&interner, &env);
let result = evaluator.evaluate(type_query);
assert_eq!(result, type_query);
}
#[test]
fn test_typeof_in_union() {
use crate::{SymbolRef, TypeEnvironment};
let interner = TypeInterner::new();
let mut env = TypeEnvironment::new();
let sym_x = SymbolRef(1);
let sym_y = SymbolRef(2);
env.insert(sym_x, TypeId::STRING);
env.insert(sym_y, TypeId::NUMBER);
let query_x = interner.intern(TypeData::TypeQuery(sym_x));
let query_y = interner.intern(TypeData::TypeQuery(sym_y));
let union = interner.union(vec![query_x, query_y]);
let mut evaluator = TypeEvaluator::with_resolver(&interner, &env);
let result = evaluator.evaluate(union);
match interner.lookup(result) {
Some(TypeData::Union(members)) => {
let members = interner.type_list(members);
assert_eq!(members.len(), 2);
}
Some(key) => panic!("Expected Union type, got {key:?}"),
None => panic!("Expected a valid type"),
}
}
#[test]
fn test_typeof_in_keyof() {
use crate::{SymbolRef, TypeEnvironment};
let interner = TypeInterner::new();
let mut env = TypeEnvironment::new();
let obj = interner.object(vec![
PropertyInfo::new(interner.intern_string("a"), TypeId::STRING),
PropertyInfo::new(interner.intern_string("b"), TypeId::NUMBER),
]);
let sym = SymbolRef(1);
env.insert(sym, obj);
let type_query = interner.intern(TypeData::TypeQuery(sym));
let keyof = interner.intern(TypeData::KeyOf(type_query));
let mut evaluator = TypeEvaluator::with_resolver(&interner, &env);
let result = evaluator.evaluate(keyof);
let key_a = interner.literal_string("a");
let key_b = interner.literal_string("b");
let expected = interner.union(vec![key_a, key_b]);
assert_eq!(result, expected);
}
#[test]
fn test_typeof_indexed_access() {
use crate::{SymbolRef, TypeEnvironment};
let interner = TypeInterner::new();
let mut env = TypeEnvironment::new();
let obj = interner.object(vec![
PropertyInfo::new(interner.intern_string("a"), TypeId::NUMBER),
PropertyInfo::new(interner.intern_string("b"), TypeId::STRING),
]);
let sym = SymbolRef(1);
env.insert(sym, obj);
let type_query = interner.intern(TypeData::TypeQuery(sym));
let key_a = interner.literal_string("a");
let mut evaluator = TypeEvaluator::with_resolver(&interner, &env);
let result = evaluator.evaluate_index_access(type_query, key_a);
assert_eq!(result, TypeId::NUMBER);
}
#[test]
fn test_uppercase_single_literal() {
let interner = TypeInterner::new();
let input = interner.literal_string("hello");
let output = interner.literal_string("HELLO");
let cond = ConditionalType {
check_type: input,
extends_type: input,
true_type: output,
false_type: input,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, output);
}
#[test]
fn test_uppercase_union_distributive() {
let interner = TypeInterner::new();
let lit_a = interner.literal_string("a");
let lit_b = interner.literal_string("b");
let lit_a_upper = interner.literal_string("A");
let lit_b_upper = interner.literal_string("B");
let cond_a = ConditionalType {
check_type: lit_a,
extends_type: lit_a,
true_type: lit_a_upper,
false_type: lit_a,
is_distributive: false,
};
let result_a = evaluate_conditional(&interner, &cond_a);
assert_eq!(result_a, lit_a_upper);
let cond_b = ConditionalType {
check_type: lit_b,
extends_type: lit_b,
true_type: lit_b_upper,
false_type: lit_b,
is_distributive: false,
};
let result_b = evaluate_conditional(&interner, &cond_b);
assert_eq!(result_b, lit_b_upper);
let result_union = interner.union(vec![result_a, result_b]);
match interner.lookup(result_union) {
Some(TypeData::Union(list_id)) => {
let members = interner.type_list(list_id);
assert_eq!(members.len(), 2);
}
_ => panic!("Expected union"),
}
}
#[test]
fn test_lowercase_single_literal() {
let interner = TypeInterner::new();
let input = interner.literal_string("HELLO");
let output = interner.literal_string("hello");
let cond = ConditionalType {
check_type: input,
extends_type: input,
true_type: output,
false_type: input,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, output);
}
#[test]
fn test_lowercase_union_distributive() {
let interner = TypeInterner::new();
let lit_abc_upper = interner.literal_string("ABC");
let lit_def_upper = interner.literal_string("DEF");
let lit_abc = interner.literal_string("abc");
let lit_def = interner.literal_string("def");
let cond_abc = ConditionalType {
check_type: lit_abc_upper,
extends_type: lit_abc_upper,
true_type: lit_abc,
false_type: lit_abc_upper,
is_distributive: false,
};
let result_abc = evaluate_conditional(&interner, &cond_abc);
assert_eq!(result_abc, lit_abc);
let cond_def = ConditionalType {
check_type: lit_def_upper,
extends_type: lit_def_upper,
true_type: lit_def,
false_type: lit_def_upper,
is_distributive: false,
};
let result_def = evaluate_conditional(&interner, &cond_def);
assert_eq!(result_def, lit_def);
let result_union = interner.union(vec![result_abc, result_def]);
match interner.lookup(result_union) {
Some(TypeData::Union(list_id)) => {
let members = interner.type_list(list_id);
assert_eq!(members.len(), 2);
}
_ => panic!("Expected union"),
}
}
#[test]
fn test_capitalize_single_literal() {
let interner = TypeInterner::new();
let input = interner.literal_string("hello");
let output = interner.literal_string("Hello");
let cond = ConditionalType {
check_type: input,
extends_type: input,
true_type: output,
false_type: input,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, output);
}
#[test]
fn test_capitalize_union_distributive() {
let interner = TypeInterner::new();
let lit_name = interner.literal_string("name");
let lit_value = interner.literal_string("value");
let lit_name_upper = interner.literal_string("Name");
let lit_value_upper = interner.literal_string("Value");
let cond_name = ConditionalType {
check_type: lit_name,
extends_type: lit_name,
true_type: lit_name_upper,
false_type: lit_name,
is_distributive: false,
};
let result_name = evaluate_conditional(&interner, &cond_name);
assert_eq!(result_name, lit_name_upper);
let cond_value = ConditionalType {
check_type: lit_value,
extends_type: lit_value,
true_type: lit_value_upper,
false_type: lit_value,
is_distributive: false,
};
let result_value = evaluate_conditional(&interner, &cond_value);
assert_eq!(result_value, lit_value_upper);
let result_union = interner.union(vec![result_name, result_value]);
match interner.lookup(result_union) {
Some(TypeData::Union(list_id)) => {
let members = interner.type_list(list_id);
assert_eq!(members.len(), 2);
}
_ => panic!("Expected union"),
}
}
#[test]
fn test_uncapitalize_single_literal() {
let interner = TypeInterner::new();
let input = interner.literal_string("Hello");
let output = interner.literal_string("hello");
let cond = ConditionalType {
check_type: input,
extends_type: input,
true_type: output,
false_type: input,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, output);
}
#[test]
fn test_uncapitalize_union_distributive() {
let interner = TypeInterner::new();
let lit_name_upper = interner.literal_string("Name");
let lit_value_upper = interner.literal_string("Value");
let lit_name = interner.literal_string("name");
let lit_value = interner.literal_string("value");
let cond_name = ConditionalType {
check_type: lit_name_upper,
extends_type: lit_name_upper,
true_type: lit_name,
false_type: lit_name_upper,
is_distributive: false,
};
let result_name = evaluate_conditional(&interner, &cond_name);
assert_eq!(result_name, lit_name);
let cond_value = ConditionalType {
check_type: lit_value_upper,
extends_type: lit_value_upper,
true_type: lit_value,
false_type: lit_value_upper,
is_distributive: false,
};
let result_value = evaluate_conditional(&interner, &cond_value);
assert_eq!(result_value, lit_value);
let result_union = interner.union(vec![result_name, result_value]);
match interner.lookup(result_union) {
Some(TypeData::Union(list_id)) => {
let members = interner.type_list(list_id);
assert_eq!(members.len(), 2);
}
_ => panic!("Expected union"),
}
}
#[test]
fn test_uppercase_string_passthrough() {
let interner = TypeInterner::new();
let cond = ConditionalType {
check_type: TypeId::STRING,
extends_type: TypeId::STRING,
true_type: TypeId::STRING,
false_type: TypeId::STRING,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, TypeId::STRING);
}
#[test]
fn test_uppercase_empty_string() {
let interner = TypeInterner::new();
let empty = interner.literal_string("");
let cond = ConditionalType {
check_type: empty,
extends_type: empty,
true_type: empty, false_type: empty,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, empty);
}
#[test]
fn test_string_template_infer_prefix_pattern() {
let interner = TypeInterner::new();
let input = interner.literal_string("prefix-value");
let infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("prefix-")),
TemplateSpan::Type(infer_r),
]);
let cond = ConditionalType {
check_type: input,
extends_type: pattern,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
let expected = interner.literal_string("value");
assert_eq!(result, expected);
}
#[test]
fn test_string_template_infer_suffix_pattern() {
let interner = TypeInterner::new();
let input = interner.literal_string("value-suffix");
let infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = interner.template_literal(vec![
TemplateSpan::Type(infer_r),
TemplateSpan::Text(interner.intern_string("-suffix")),
]);
let cond = ConditionalType {
check_type: input,
extends_type: pattern,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
let expected = interner.literal_string("value");
assert_eq!(result, expected);
}
#[test]
fn test_string_template_infer_middle_pattern() {
let interner = TypeInterner::new();
let input = interner.literal_string("start-middle-end");
let infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("start-")),
TemplateSpan::Type(infer_r),
TemplateSpan::Text(interner.intern_string("-end")),
]);
let cond = ConditionalType {
check_type: input,
extends_type: pattern,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
let expected = interner.literal_string("middle");
assert_eq!(result, expected);
}
#[test]
fn test_string_template_infer_no_match_pattern() {
let interner = TypeInterner::new();
let input = interner.literal_string("different");
let infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("prefix-")),
TemplateSpan::Type(infer_r),
]);
let cond = ConditionalType {
check_type: input,
extends_type: pattern,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_template_infer_two_parts() {
let interner = TypeInterner::new();
let input = interner.literal_string("first_second");
let infer_a_name = interner.intern_string("A");
let infer_a = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_a_name,
constraint: None,
default: None,
is_const: false,
}));
let infer_b_name = interner.intern_string("B");
let infer_b = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_b_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = interner.template_literal(vec![
TemplateSpan::Type(infer_a),
TemplateSpan::Text(interner.intern_string("_")),
TemplateSpan::Type(infer_b),
]);
let cond_first = ConditionalType {
check_type: input,
extends_type: pattern,
true_type: infer_a,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result_first = evaluate_conditional(&interner, &cond_first);
let expected_first = interner.literal_string("first");
assert_eq!(result_first, expected_first);
let cond_second = ConditionalType {
check_type: input,
extends_type: pattern,
true_type: infer_b,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result_second = evaluate_conditional(&interner, &cond_second);
let expected_second = interner.literal_string("second");
assert_eq!(result_second, expected_second);
}
#[test]
fn test_template_infer_union_distributive() {
let interner = TypeInterner::new();
let input_a = interner.literal_string("get-foo");
let input_b = interner.literal_string("get-bar");
let infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("get-")),
TemplateSpan::Type(infer_r),
]);
let cond_a = ConditionalType {
check_type: input_a,
extends_type: pattern,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result_a = evaluate_conditional(&interner, &cond_a);
let expected_a = interner.literal_string("foo");
assert_eq!(result_a, expected_a);
let cond_b = ConditionalType {
check_type: input_b,
extends_type: pattern,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result_b = evaluate_conditional(&interner, &cond_b);
let expected_b = interner.literal_string("bar");
assert_eq!(result_b, expected_b);
let result_union = interner.union(vec![result_a, result_b]);
match interner.lookup(result_union) {
Some(TypeData::Union(list_id)) => {
let members = interner.type_list(list_id);
assert_eq!(members.len(), 2);
}
_ => panic!("Expected union"),
}
}
#[test]
fn test_template_multi_segment_extraction() {
let interner = TypeInterner::new();
let input = interner.literal_string("item-123-end");
let infer_name = interner.intern_string("N");
let infer_n = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("item-")),
TemplateSpan::Type(infer_n),
TemplateSpan::Text(interner.intern_string("-end")),
]);
let cond = ConditionalType {
check_type: input,
extends_type: pattern,
true_type: infer_n,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
let expected = interner.literal_string("123");
assert_eq!(result, expected);
}
#[test]
fn test_string_literal_extends_literal() {
let interner = TypeInterner::new();
let hello = interner.literal_string("hello");
let world = interner.literal_string("world");
let cond_same = ConditionalType {
check_type: hello,
extends_type: hello,
true_type: interner.literal_boolean(true),
false_type: interner.literal_boolean(false),
is_distributive: false,
};
let result_same = evaluate_conditional(&interner, &cond_same);
assert_eq!(result_same, interner.literal_boolean(true));
let cond_diff = ConditionalType {
check_type: hello,
extends_type: world,
true_type: interner.literal_boolean(true),
false_type: interner.literal_boolean(false),
is_distributive: false,
};
let result_diff = evaluate_conditional(&interner, &cond_diff);
assert_eq!(result_diff, interner.literal_boolean(false));
}
#[test]
fn test_string_literal_extends_string() {
let interner = TypeInterner::new();
let hello = interner.literal_string("hello");
let cond = ConditionalType {
check_type: hello,
extends_type: TypeId::STRING,
true_type: interner.literal_boolean(true),
false_type: interner.literal_boolean(false),
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, interner.literal_boolean(true));
}
#[test]
fn test_string_not_extends_literal() {
let interner = TypeInterner::new();
let hello = interner.literal_string("hello");
let cond = ConditionalType {
check_type: TypeId::STRING,
extends_type: hello,
true_type: interner.literal_boolean(true),
false_type: interner.literal_boolean(false),
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, interner.literal_boolean(false));
}
#[test]
fn test_string_union_narrowing() {
let interner = TypeInterner::new();
let lit_a = interner.literal_string("a");
let lit_b = interner.literal_string("b");
let lit_c = interner.literal_string("c");
let _union_abc = interner.union(vec![lit_a, lit_b, lit_c]);
let target_ab = interner.union(vec![lit_a, lit_b]);
let cond_a = ConditionalType {
check_type: lit_a,
extends_type: target_ab,
true_type: lit_a,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result_a = evaluate_conditional(&interner, &cond_a);
assert_eq!(result_a, lit_a);
let cond_b = ConditionalType {
check_type: lit_b,
extends_type: target_ab,
true_type: lit_b,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result_b = evaluate_conditional(&interner, &cond_b);
assert_eq!(result_b, lit_b);
let cond_c = ConditionalType {
check_type: lit_c,
extends_type: target_ab,
true_type: lit_c,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result_c = evaluate_conditional(&interner, &cond_c);
assert_eq!(result_c, TypeId::NEVER);
}
#[test]
fn test_template_literal_extends_string() {
let interner = TypeInterner::new();
let template = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("prefix")),
TemplateSpan::Type(TypeId::STRING),
]);
let cond = ConditionalType {
check_type: template,
extends_type: TypeId::STRING,
true_type: interner.literal_boolean(true),
false_type: interner.literal_boolean(false),
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, interner.literal_boolean(true));
}
#[test]
fn test_literal_matches_template_via_infer() {
let interner = TypeInterner::new();
let literal = interner.literal_string("prefix-value");
let infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let template = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("prefix-")),
TemplateSpan::Type(infer_r),
]);
let cond = ConditionalType {
check_type: literal,
extends_type: template,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
let expected = interner.literal_string("value");
assert_eq!(result, expected);
}
#[test]
fn test_literal_not_matching_template_pattern() {
let interner = TypeInterner::new();
let literal = interner.literal_string("other-value");
let template = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("prefix-")),
TemplateSpan::Type(TypeId::STRING),
]);
let cond = ConditionalType {
check_type: literal,
extends_type: template,
true_type: interner.literal_boolean(true),
false_type: interner.literal_boolean(false),
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, interner.literal_boolean(false));
}
#[test]
fn test_string_literal_special_chars() {
let interner = TypeInterner::new();
let special = interner.literal_string("hello\nworld");
let pattern = interner.literal_string("hello\nworld");
let cond = ConditionalType {
check_type: special,
extends_type: pattern,
true_type: interner.literal_boolean(true),
false_type: interner.literal_boolean(false),
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, interner.literal_boolean(true));
}
#[test]
fn test_mapped_type_uppercase_keys() {
let interner = TypeInterner::new();
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_upper_a = interner.literal_string("A");
let key_upper_b = interner.literal_string("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 inner_cond = interner.conditional(ConditionalType {
check_type: key_param_id,
extends_type: key_b,
true_type: key_upper_b,
false_type: TypeId::NEVER,
is_distributive: false,
});
let name_type = interner.conditional(ConditionalType {
check_type: key_param_id,
extends_type: key_a,
true_type: key_upper_a,
false_type: inner_cond,
is_distributive: false,
});
let mapped = MappedType {
type_param: key_param,
constraint: keys,
name_type: Some(name_type),
template: TypeId::NUMBER,
readonly_modifier: None,
optional_modifier: None,
};
let result = evaluate_mapped(&interner, &mapped);
let a_name = interner.intern_string("A");
let b_name = interner.intern_string("B");
let expected = interner.object(vec![
PropertyInfo::new(a_name, TypeId::NUMBER),
PropertyInfo::new(b_name, TypeId::NUMBER),
]);
assert_eq!(result, expected);
}
#[test]
fn test_mapped_type_template_literal_keys() {
let interner = TypeInterner::new();
let key_click = interner.literal_string("click");
let key_focus = interner.literal_string("focus");
let keys = interner.union(vec![key_click, key_focus]);
let on_click = interner.literal_string("onclick");
let on_focus = interner.literal_string("onfocus");
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 inner_cond = interner.conditional(ConditionalType {
check_type: key_param_id,
extends_type: key_focus,
true_type: on_focus,
false_type: TypeId::NEVER,
is_distributive: false,
});
let name_type = interner.conditional(ConditionalType {
check_type: key_param_id,
extends_type: key_click,
true_type: on_click,
false_type: inner_cond,
is_distributive: false,
});
let handler = 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 mapped = MappedType {
type_param: key_param,
constraint: keys,
name_type: Some(name_type),
template: handler,
readonly_modifier: None,
optional_modifier: None,
};
let result = evaluate_mapped(&interner, &mapped);
let onclick_name = interner.intern_string("onclick");
let onfocus_name = interner.intern_string("onfocus");
let expected = interner.object(vec![
PropertyInfo::new(onclick_name, handler),
PropertyInfo::new(onfocus_name, handler),
]);
assert_eq!(result, expected);
}
#[test]
fn test_satisfies_basic_literal_string() {
use crate::SubtypeChecker;
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let hello = interner.literal_string("hello");
assert!(checker.is_subtype_of(hello, TypeId::STRING));
assert_ne!(hello, TypeId::STRING);
}
#[test]
fn test_satisfies_basic_literal_number() {
use crate::SubtypeChecker;
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::NUMBER));
assert_ne!(forty_two, TypeId::NUMBER);
}
#[test]
fn test_satisfies_basic_object_type() {
use crate::SubtypeChecker;
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let one = interner.literal_number(1.0);
let hello = interner.literal_string("hello");
let inferred = interner.object(vec![
PropertyInfo::new(interner.intern_string("a"), one),
PropertyInfo::new(interner.intern_string("b"), hello),
]);
let constraint = interner.object(vec![
PropertyInfo::new(interner.intern_string("a"), TypeId::NUMBER),
PropertyInfo::new(interner.intern_string("b"), TypeId::STRING),
]);
assert!(checker.is_subtype_of(inferred, constraint));
assert_ne!(inferred, constraint);
}
#[test]
fn test_satisfies_constraint_failure() {
use crate::SubtypeChecker;
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let hello = interner.literal_string("hello");
assert!(!checker.is_subtype_of(hello, TypeId::NUMBER));
}
#[test]
fn test_satisfies_literal_widening_preserved_string() {
use crate::{LiteralValue, SubtypeChecker};
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let hello = interner.literal_string("hello");
assert!(checker.is_subtype_of(hello, TypeId::STRING));
match interner.lookup(hello) {
Some(TypeData::Literal(LiteralValue::String(_))) => {} other => panic!("Expected Literal(String), got {other:?}"),
}
}
#[test]
fn test_satisfies_literal_widening_preserved_number() {
use crate::{LiteralValue, SubtypeChecker};
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::NUMBER));
match interner.lookup(forty_two) {
Some(TypeData::Literal(LiteralValue::Number(_))) => {} other => panic!("Expected Literal(Number), got {other:?}"),
}
}
#[test]
fn test_satisfies_literal_widening_preserved_boolean() {
use crate::{LiteralValue, SubtypeChecker};
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let lit_true = interner.literal_boolean(true);
assert!(checker.is_subtype_of(lit_true, TypeId::BOOLEAN));
match interner.lookup(lit_true) {
Some(TypeData::Literal(LiteralValue::Boolean(true))) => {} other => panic!("Expected Literal(Boolean(true)), got {other:?}"),
}
}
#[test]
fn test_satisfies_excess_property_check_fails() {
use crate::SubtypeChecker;
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let source = interner.object(vec![
PropertyInfo::new(interner.intern_string("a"), TypeId::NUMBER),
PropertyInfo::new(interner.intern_string("b"), TypeId::NUMBER),
PropertyInfo::new(interner.intern_string("c"), TypeId::NUMBER),
]);
let target = interner.object(vec![
PropertyInfo::new(interner.intern_string("a"), TypeId::NUMBER),
PropertyInfo::new(interner.intern_string("b"), TypeId::NUMBER),
]);
assert!(checker.is_subtype_of(source, target));
}
#[test]
fn test_satisfies_missing_property_fails() {
use crate::SubtypeChecker;
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let source = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
TypeId::NUMBER,
)]);
let target = interner.object(vec![
PropertyInfo::new(interner.intern_string("a"), TypeId::NUMBER),
PropertyInfo::new(interner.intern_string("b"), TypeId::NUMBER),
]);
assert!(!checker.is_subtype_of(source, target));
}
#[test]
fn test_satisfies_optional_property_satisfied() {
use crate::SubtypeChecker;
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let source = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
TypeId::NUMBER,
)]);
let target = interner.object(vec![
PropertyInfo::new(interner.intern_string("a"), TypeId::NUMBER),
PropertyInfo {
name: interner.intern_string("b"),
type_id: TypeId::NUMBER,
write_type: TypeId::NUMBER,
optional: true, readonly: false,
is_method: false,
visibility: Visibility::Public,
parent_id: None,
},
]);
assert!(checker.is_subtype_of(source, target));
}
#[test]
fn test_satisfies_vs_annotation_literal_preservation() {
use crate::SubtypeChecker;
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let hello = interner.literal_string("hello");
let satisfies_type = hello;
let annotation_type = TypeId::STRING;
assert!(checker.is_subtype_of(satisfies_type, TypeId::STRING));
assert!(checker.is_subtype_of(annotation_type, TypeId::STRING));
assert!(checker.is_subtype_of(satisfies_type, annotation_type));
assert!(!checker.is_subtype_of(annotation_type, satisfies_type));
}
#[test]
fn test_satisfies_vs_annotation_object_properties() {
use crate::SubtypeChecker;
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let success = interner.literal_string("success");
let satisfies_obj = interner.object(vec![PropertyInfo::new(
interner.intern_string("status"),
success,
)]);
let annotation_obj = interner.object(vec![PropertyInfo::new(
interner.intern_string("status"),
TypeId::STRING,
)]);
assert!(checker.is_subtype_of(satisfies_obj, annotation_obj));
assert!(!checker.is_subtype_of(annotation_obj, satisfies_obj));
}
#[test]
fn test_satisfies_union_constraint() {
use crate::SubtypeChecker;
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let lit_a = interner.literal_string("a");
let lit_b = interner.literal_string("b");
let lit_c = interner.literal_string("c");
let union = interner.union(vec![lit_a, lit_b, lit_c]);
assert!(checker.is_subtype_of(lit_a, union));
assert_ne!(lit_a, union);
}
#[test]
fn test_satisfies_array_type() {
use crate::SubtypeChecker;
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 tuple = 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,
},
]);
let number_array = interner.array(TypeId::NUMBER);
assert!(checker.is_subtype_of(tuple, number_array));
}
#[test]
fn test_satisfies_record_type() {
use crate::SubtypeChecker;
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let source = interner.object(vec![
PropertyInfo::new(interner.intern_string("bar"), TypeId::NUMBER),
PropertyInfo::new(interner.intern_string("foo"), TypeId::NUMBER),
]);
let record = 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(source, record));
}
#[test]
fn test_satisfies_with_generic_function() {
use crate::SubtypeChecker;
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let t_name = interner.intern_string("T");
let x_name = interner.intern_string("x");
let t_param = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: t_name,
constraint: None,
default: None,
is_const: false,
}));
let source_func = interner.function(FunctionShape {
type_params: vec![TypeParamInfo {
name: t_name,
constraint: None,
default: None,
is_const: false,
}],
params: vec![ParamInfo::required(x_name, t_param)],
this_type: None,
return_type: t_param,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let target_func = interner.function(FunctionShape {
type_params: vec![TypeParamInfo {
name: t_name,
constraint: None,
default: None,
is_const: false,
}],
params: vec![ParamInfo::required(x_name, t_param)],
this_type: None,
return_type: t_param,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(checker.is_subtype_of(source_func, target_func));
}
#[test]
fn test_satisfies_preserves_narrower_type() {
use crate::SubtypeChecker;
use crate::types::LiteralValue;
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let hello_lit = interner.literal_string("hello");
assert!(checker.is_subtype_of(hello_lit, TypeId::STRING));
match interner.lookup(hello_lit) {
Some(TypeData::Literal(LiteralValue::String(_))) => {} other => panic!("Expected Literal(String), got {other:?}"),
}
}
#[test]
fn test_satisfies_with_union_literals() {
use crate::SubtypeChecker;
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let a_lit = interner.literal_string("a");
let b_lit = interner.literal_string("b");
let union = interner.union(vec![a_lit, b_lit]);
assert!(checker.is_subtype_of(union, TypeId::STRING));
}
#[test]
fn test_satisfies_with_intersection() {
use crate::SubtypeChecker;
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 obj_a = interner.object(vec![PropertyInfo::new(interner.intern_string("a"), one)]);
let obj_b = interner.object(vec![PropertyInfo::new(interner.intern_string("b"), two)]);
let intersection = interner.intersection(vec![obj_a, obj_b]);
let target = interner.object(vec![
PropertyInfo::new(interner.intern_string("a"), TypeId::NUMBER),
PropertyInfo::new(interner.intern_string("b"), TypeId::NUMBER),
]);
assert!(checker.is_subtype_of(intersection, target));
}
#[test]
fn test_noinfer_blocks_inference_in_target() {
use crate::infer::InferenceContext;
use crate::types::InferencePriority;
let interner = TypeInterner::new();
let mut ctx = InferenceContext::new(&interner);
let t_name = interner.intern_string("T");
let var_t = ctx.fresh_type_param(t_name, false);
let t_param = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: t_name,
constraint: None,
default: None,
is_const: false,
}));
let hello_lit = interner.literal_string("hello");
let noinfer_t = interner.intern(TypeData::NoInfer(t_param));
ctx.infer_from_types(hello_lit, noinfer_t, InferencePriority::NakedTypeVariable)
.unwrap();
assert!(ctx.probe(var_t).is_none());
}
#[test]
fn test_noinfer_in_union_distribution() {
use crate::evaluate::evaluate_type;
let interner = TypeInterner::new();
let union = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let noinfer_union = interner.intern(TypeData::NoInfer(union));
let evaluated = evaluate_type(&interner, noinfer_union);
match interner.lookup(evaluated) {
Some(TypeData::Union(_)) => {} other => panic!("Expected Union, got {other:?}"),
}
}
#[test]
fn test_noinfer_with_array_elements() {
use crate::evaluate::evaluate_type;
let interner = TypeInterner::new();
let string_array = interner.array(TypeId::STRING);
let noinfer_array = interner.intern(TypeData::NoInfer(string_array));
let evaluated = evaluate_type(&interner, noinfer_array);
match interner.lookup(evaluated) {
Some(TypeData::Array(elem)) => {
assert_eq!(elem, TypeId::STRING);
}
other => panic!("Expected Array, got {other:?}"),
}
}
#[test]
fn test_noinfer_visitor_traversal() {
use crate::visitor::TypeVisitor;
struct TestVisitor {
visited_noinfer: bool,
}
impl TypeVisitor for TestVisitor {
type Output = ();
fn visit_no_infer(&mut self, _inner: TypeId) -> Self::Output {
self.visited_noinfer = true;
}
fn visit_intrinsic(&mut self, _kind: IntrinsicKind) -> Self::Output {}
fn visit_literal(&mut self, _value: &LiteralValue) -> Self::Output {}
fn default_output() -> Self::Output {}
}
let interner = TypeInterner::new();
let noinfer_string = interner.intern(TypeData::NoInfer(TypeId::STRING));
let mut visitor = TestVisitor {
visited_noinfer: false,
};
visitor.visit_type(&interner, noinfer_string);
assert!(visitor.visited_noinfer, "NoInfer should be visited");
}
#[test]
fn test_noinfer_contains_type_param() {
use crate::visitor::TypeVisitor;
use tsz_common::interner::Atom;
struct CollectParams<'a> {
params: Vec<Atom>,
interner: &'a TypeInterner,
}
impl<'a> TypeVisitor for CollectParams<'a> {
type Output = ();
fn visit_type_parameter(&mut self, info: &TypeParamInfo) -> Self::Output {
self.params.push(info.name);
}
fn visit_no_infer(&mut self, inner: TypeId) -> Self::Output {
self.visit_type(self.interner, inner)
}
fn visit_intrinsic(&mut self, _kind: IntrinsicKind) -> Self::Output {}
fn visit_literal(&mut self, _value: &LiteralValue) -> Self::Output {}
fn default_output() -> Self::Output {}
}
let interner = TypeInterner::new();
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 noinfer_t = interner.intern(TypeData::NoInfer(t_param));
let mut collector = CollectParams {
params: Vec::new(),
interner: &interner,
};
collector.visit_type(&interner, noinfer_t);
assert!(collector.params.contains(&t_name));
}
#[test]
fn test_bigint_literal_creation() {
let interner = TypeInterner::new();
let bigint_42 = interner.literal_bigint("42");
let bigint_42_dup = interner.literal_bigint("42");
assert_eq!(bigint_42, bigint_42_dup);
let bigint_100 = interner.literal_bigint("100");
assert_ne!(bigint_42, bigint_100);
}
#[test]
fn test_bigint_literal_extends_bigint() {
let interner = TypeInterner::new();
let bigint_42 = interner.literal_bigint("42");
let cond = ConditionalType {
check_type: bigint_42,
extends_type: TypeId::BIGINT,
true_type: interner.literal_boolean(true),
false_type: interner.literal_boolean(false),
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, interner.literal_boolean(true));
}
#[test]
fn test_bigint_not_extends_number() {
let interner = TypeInterner::new();
let bigint_42 = interner.literal_bigint("42");
let cond = ConditionalType {
check_type: bigint_42,
extends_type: TypeId::NUMBER,
true_type: interner.literal_boolean(true),
false_type: interner.literal_boolean(false),
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, interner.literal_boolean(false));
}
#[test]
fn test_bigint_literal_union() {
let interner = TypeInterner::new();
let bigint_1 = interner.literal_bigint("1");
let bigint_2 = interner.literal_bigint("2");
let bigint_3 = interner.literal_bigint("3");
let union = interner.union(vec![bigint_1, bigint_2, bigint_3]);
match interner.lookup(union) {
Some(TypeData::Union(list_id)) => {
let members = interner.type_list(list_id);
assert_eq!(members.len(), 3);
}
_ => panic!("Expected union"),
}
}
#[test]
fn test_bigint_negative_literal() {
let interner = TypeInterner::new();
let neg_bigint = interner.literal_bigint_with_sign(true, "42");
let pos_bigint = interner.literal_bigint("42");
assert_ne!(neg_bigint, pos_bigint);
let cond = ConditionalType {
check_type: neg_bigint,
extends_type: TypeId::BIGINT,
true_type: interner.literal_boolean(true),
false_type: interner.literal_boolean(false),
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, interner.literal_boolean(true));
}
#[test]
fn test_symbol_not_extends_string() {
let interner = TypeInterner::new();
let cond = ConditionalType {
check_type: TypeId::SYMBOL,
extends_type: TypeId::STRING,
true_type: interner.literal_boolean(true),
false_type: interner.literal_boolean(false),
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, interner.literal_boolean(false));
}
#[test]
fn test_symbol_extends_symbol() {
let interner = TypeInterner::new();
let cond = ConditionalType {
check_type: TypeId::SYMBOL,
extends_type: TypeId::SYMBOL,
true_type: interner.literal_boolean(true),
false_type: interner.literal_boolean(false),
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, interner.literal_boolean(true));
}
#[test]
fn test_unique_symbol_extends_symbol() {
let interner = TypeInterner::new();
let unique_sym = interner.intern(TypeData::UniqueSymbol(SymbolRef(42)));
let cond = ConditionalType {
check_type: unique_sym,
extends_type: TypeId::SYMBOL,
true_type: interner.literal_boolean(true),
false_type: interner.literal_boolean(false),
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, interner.literal_boolean(true));
}
#[test]
fn test_unique_symbol_distinct_refs() {
let interner = TypeInterner::new();
let sym_a = interner.intern(TypeData::UniqueSymbol(SymbolRef(1)));
let sym_b = interner.intern(TypeData::UniqueSymbol(SymbolRef(2)));
assert_ne!(sym_a, sym_b);
let sym_a_dup = interner.intern(TypeData::UniqueSymbol(SymbolRef(1)));
assert_eq!(sym_a, sym_a_dup);
}
#[test]
fn test_unique_symbol_union_with_symbol() {
let interner = TypeInterner::new();
let unique_sym = interner.intern(TypeData::UniqueSymbol(SymbolRef(1)));
let union = interner.union(vec![unique_sym, TypeId::SYMBOL]);
match interner.lookup(union) {
Some(TypeData::Union(list_id)) => {
let members = interner.type_list(list_id);
assert_eq!(members.len(), 2);
}
_ => panic!("Expected union"),
}
}
#[test]
fn test_unique_symbol_in_union() {
let interner = TypeInterner::new();
let sym1 = interner.intern(TypeData::UniqueSymbol(SymbolRef(100)));
let sym2 = interner.intern(TypeData::UniqueSymbol(SymbolRef(101)));
let sym3 = interner.intern(TypeData::UniqueSymbol(SymbolRef(102)));
let union = interner.union(vec![sym1, sym2, sym3]);
match interner.lookup(union) {
Some(TypeData::Union(list_id)) => {
let members = interner.type_list(list_id);
assert_eq!(members.len(), 3);
}
_ => panic!("Expected union"),
}
}
#[test]
fn test_number_literal_creation() {
let interner = TypeInterner::new();
let num_42 = interner.literal_number(42.0);
let num_42_dup = interner.literal_number(42.0);
assert_eq!(num_42, num_42_dup);
let num_100 = interner.literal_number(100.0);
assert_ne!(num_42, num_100);
}
#[test]
fn test_number_literal_extends_number() {
let interner = TypeInterner::new();
let num_42 = interner.literal_number(42.0);
let cond = ConditionalType {
check_type: num_42,
extends_type: TypeId::NUMBER,
true_type: interner.literal_boolean(true),
false_type: interner.literal_boolean(false),
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, interner.literal_boolean(true));
}
#[test]
fn test_number_literal_not_extends_different() {
let interner = TypeInterner::new();
let num_42 = interner.literal_number(42.0);
let num_100 = interner.literal_number(100.0);
let cond = ConditionalType {
check_type: num_42,
extends_type: num_100,
true_type: interner.literal_boolean(true),
false_type: interner.literal_boolean(false),
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, interner.literal_boolean(false));
}
#[test]
fn test_number_literal_union() {
let interner = TypeInterner::new();
let num_1 = interner.literal_number(1.0);
let num_2 = interner.literal_number(2.0);
let num_3 = interner.literal_number(3.0);
let union = interner.union(vec![num_1, num_2, num_3]);
match interner.lookup(union) {
Some(TypeData::Union(list_id)) => {
let members = interner.type_list(list_id);
assert_eq!(members.len(), 3);
}
_ => panic!("Expected union"),
}
}
#[test]
fn test_string_literal_comparison() {
let interner = TypeInterner::new();
let str_hello = interner.literal_string("hello");
let str_hello_dup = interner.literal_string("hello");
assert_eq!(str_hello, str_hello_dup);
let str_world = interner.literal_string("world");
assert_ne!(str_hello, str_world);
}
#[test]
fn test_string_literal_union_conditional() {
let interner = TypeInterner::new();
let lit_a = interner.literal_string("a");
let lit_b = interner.literal_string("b");
let lit_c = interner.literal_string("c");
let union_ab = interner.union(vec![lit_a, lit_b]);
let cond_a = ConditionalType {
check_type: lit_a,
extends_type: union_ab,
true_type: interner.literal_boolean(true),
false_type: interner.literal_boolean(false),
is_distributive: false,
};
let result_a = evaluate_conditional(&interner, &cond_a);
assert_eq!(result_a, interner.literal_boolean(true));
let cond_c = ConditionalType {
check_type: lit_c,
extends_type: union_ab,
true_type: interner.literal_boolean(true),
false_type: interner.literal_boolean(false),
is_distributive: false,
};
let result_c = evaluate_conditional(&interner, &cond_c);
assert_eq!(result_c, interner.literal_boolean(false));
}
#[test]
fn test_mixed_numeric_literal_types() {
let interner = TypeInterner::new();
let num_42 = interner.literal_number(42.0);
let bigint_42 = interner.literal_bigint("42");
assert_ne!(num_42, bigint_42);
let union = interner.union(vec![num_42, bigint_42]);
match interner.lookup(union) {
Some(TypeData::Union(list_id)) => {
let members = interner.type_list(list_id);
assert_eq!(members.len(), 2);
}
_ => panic!("Expected union"),
}
}
#[test]
fn test_float_number_literal() {
let interner = TypeInterner::new();
const APPROX_PI: f64 = 3.15;
const APPROX_E: f64 = 2.72;
let float_pi = interner.literal_number(APPROX_PI);
let float_e = interner.literal_number(APPROX_E);
assert_ne!(float_pi, float_e);
let cond = ConditionalType {
check_type: float_pi,
extends_type: TypeId::NUMBER,
true_type: interner.literal_boolean(true),
false_type: interner.literal_boolean(false),
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, interner.literal_boolean(true));
}
#[test]
fn test_negative_number_literal() {
let interner = TypeInterner::new();
let neg_42 = interner.literal_number(-42.0);
let pos_42 = interner.literal_number(42.0);
assert_ne!(neg_42, pos_42);
let cond = ConditionalType {
check_type: neg_42,
extends_type: TypeId::NUMBER,
true_type: interner.literal_boolean(true),
false_type: interner.literal_boolean(false),
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, interner.literal_boolean(true));
}
#[test]
fn test_zero_number_literal() {
let interner = TypeInterner::new();
let zero = interner.literal_number(0.0);
let neg_zero = interner.literal_number(-0.0);
let cond_zero = ConditionalType {
check_type: zero,
extends_type: TypeId::NUMBER,
true_type: interner.literal_boolean(true),
false_type: interner.literal_boolean(false),
is_distributive: false,
};
assert_eq!(
evaluate_conditional(&interner, &cond_zero),
interner.literal_boolean(true)
);
let cond_neg = ConditionalType {
check_type: neg_zero,
extends_type: TypeId::NUMBER,
true_type: interner.literal_boolean(true),
false_type: interner.literal_boolean(false),
is_distributive: false,
};
assert_eq!(
evaluate_conditional(&interner, &cond_neg),
interner.literal_boolean(true)
);
}
#[test]
fn test_boolean_literal_operations() {
let interner = TypeInterner::new();
let lit_true = interner.literal_boolean(true);
let lit_false = interner.literal_boolean(false);
assert_ne!(lit_true, lit_false);
let cond_true = ConditionalType {
check_type: lit_true,
extends_type: TypeId::BOOLEAN,
true_type: interner.literal_boolean(true),
false_type: interner.literal_boolean(false),
is_distributive: false,
};
assert_eq!(evaluate_conditional(&interner, &cond_true), lit_true);
let cond_false = ConditionalType {
check_type: lit_false,
extends_type: TypeId::BOOLEAN,
true_type: interner.literal_boolean(true),
false_type: interner.literal_boolean(false),
is_distributive: false,
};
assert_eq!(evaluate_conditional(&interner, &cond_false), lit_true);
}
#[test]
fn test_boolean_literal_union() {
let interner = TypeInterner::new();
let lit_true = interner.literal_boolean(true);
let lit_false = interner.literal_boolean(false);
let union = interner.union(vec![lit_true, lit_false]);
match interner.lookup(union) {
Some(TypeData::Union(list_id)) => {
let members = interner.type_list(list_id);
assert!(members.len() == 2);
}
Some(TypeData::Intrinsic(IntrinsicKind::Boolean)) => {
}
_ => panic!("Expected union or boolean"),
}
}
#[test]
fn test_intrinsic_types_distinct() {
assert_ne!(TypeId::STRING, TypeId::NUMBER);
assert_ne!(TypeId::NUMBER, TypeId::BOOLEAN);
assert_ne!(TypeId::BOOLEAN, TypeId::BIGINT);
assert_ne!(TypeId::BIGINT, TypeId::SYMBOL);
assert_ne!(TypeId::SYMBOL, TypeId::NULL);
assert_ne!(TypeId::NULL, TypeId::UNDEFINED);
assert_ne!(TypeId::UNDEFINED, TypeId::VOID);
assert_ne!(TypeId::VOID, TypeId::NEVER);
assert_ne!(TypeId::NEVER, TypeId::ANY);
assert_ne!(TypeId::ANY, TypeId::UNKNOWN);
assert_ne!(TypeId::UNKNOWN, TypeId::OBJECT);
}
#[test]
fn test_null_undefined_extends() {
let interner = TypeInterner::new();
let cond_null = ConditionalType {
check_type: TypeId::NULL,
extends_type: TypeId::NULL,
true_type: interner.literal_boolean(true),
false_type: interner.literal_boolean(false),
is_distributive: false,
};
assert_eq!(
evaluate_conditional(&interner, &cond_null),
interner.literal_boolean(true)
);
let cond_undef = ConditionalType {
check_type: TypeId::UNDEFINED,
extends_type: TypeId::UNDEFINED,
true_type: interner.literal_boolean(true),
false_type: interner.literal_boolean(false),
is_distributive: false,
};
assert_eq!(
evaluate_conditional(&interner, &cond_undef),
interner.literal_boolean(true)
);
let cond_null_undef = ConditionalType {
check_type: TypeId::NULL,
extends_type: TypeId::UNDEFINED,
true_type: interner.literal_boolean(true),
false_type: interner.literal_boolean(false),
is_distributive: false,
};
assert_eq!(
evaluate_conditional(&interner, &cond_null_undef),
interner.literal_boolean(false)
);
}
#[test]
fn test_void_undefined_relationship() {
let interner = TypeInterner::new();
let cond = ConditionalType {
check_type: TypeId::UNDEFINED,
extends_type: TypeId::VOID,
true_type: interner.literal_boolean(true),
false_type: interner.literal_boolean(false),
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, interner.literal_boolean(true));
}
#[test]
fn test_never_bottom_type() {
let interner = TypeInterner::new();
let cond_string = ConditionalType {
check_type: TypeId::NEVER,
extends_type: TypeId::STRING,
true_type: interner.literal_boolean(true),
false_type: interner.literal_boolean(false),
is_distributive: false,
};
assert_eq!(
evaluate_conditional(&interner, &cond_string),
interner.literal_boolean(true)
);
let cond_number = ConditionalType {
check_type: TypeId::NEVER,
extends_type: TypeId::NUMBER,
true_type: interner.literal_boolean(true),
false_type: interner.literal_boolean(false),
is_distributive: false,
};
assert_eq!(
evaluate_conditional(&interner, &cond_number),
interner.literal_boolean(true)
);
}
#[test]
fn test_any_unknown_top_types() {
let interner = TypeInterner::new();
let cond_any = ConditionalType {
check_type: TypeId::STRING,
extends_type: TypeId::ANY,
true_type: interner.literal_boolean(true),
false_type: interner.literal_boolean(false),
is_distributive: false,
};
assert_eq!(
evaluate_conditional(&interner, &cond_any),
interner.literal_boolean(true)
);
let cond_unknown = ConditionalType {
check_type: TypeId::STRING,
extends_type: TypeId::UNKNOWN,
true_type: interner.literal_boolean(true),
false_type: interner.literal_boolean(false),
is_distributive: false,
};
assert_eq!(
evaluate_conditional(&interner, &cond_unknown),
interner.literal_boolean(true)
);
}
#[test]
fn test_const_object_literal_readonly_properties() {
let interner = TypeInterner::new();
let one = interner.literal_number(1.0);
let hello = interner.literal_string("hello");
let const_obj = interner.object(vec![
PropertyInfo {
name: interner.intern_string("a"),
type_id: one,
write_type: one,
optional: false,
readonly: true, is_method: false,
visibility: Visibility::Public,
parent_id: None,
},
PropertyInfo::readonly(interner.intern_string("b"), hello),
]);
match interner.lookup(const_obj) {
Some(TypeData::Object(shape_id)) => {
let shape = interner.object_shape(shape_id);
assert_eq!(shape.properties.len(), 2);
for prop in &shape.properties {
assert!(prop.readonly);
}
}
other => panic!("Expected Object type, got {other:?}"),
}
}
#[test]
fn test_const_object_literal_nested() {
let interner = TypeInterner::new();
let forty_two = interner.literal_number(42.0);
let inner = interner.object(vec![PropertyInfo::readonly(
interner.intern_string("inner"),
forty_two,
)]);
let outer = interner.object(vec![PropertyInfo::readonly(
interner.intern_string("outer"),
inner,
)]);
match interner.lookup(outer) {
Some(TypeData::Object(shape_id)) => {
let shape = interner.object_shape(shape_id);
assert_eq!(shape.properties.len(), 1);
assert!(shape.properties[0].readonly);
let inner_type = shape.properties[0].type_id;
match interner.lookup(inner_type) {
Some(TypeData::Object(inner_shape_id)) => {
let inner_shape = interner.object_shape(inner_shape_id);
assert!(inner_shape.properties[0].readonly);
}
other => panic!("Expected inner Object, got {other:?}"),
}
}
other => panic!("Expected Object type, got {other:?}"),
}
}
#[test]
fn test_const_object_literal_vs_mutable() {
use crate::SubtypeChecker;
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let one = interner.literal_number(1.0);
let const_obj = interner.object(vec![PropertyInfo::readonly(
interner.intern_string("a"),
one,
)]);
let widened_readonly = interner.object(vec![PropertyInfo::readonly(
interner.intern_string("a"),
TypeId::NUMBER,
)]);
assert!(checker.is_subtype_of(const_obj, widened_readonly));
assert!(!checker.is_subtype_of(widened_readonly, const_obj));
}
#[test]
fn test_const_array_literal_tuple() {
let interner = TypeInterner::new();
let one = interner.literal_number(1.0);
let two = interner.literal_number(2.0);
let three = interner.literal_number(3.0);
let tuple = 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,
},
]);
let readonly_tuple = interner.intern(TypeData::ReadonlyType(tuple));
match interner.lookup(readonly_tuple) {
Some(TypeData::ReadonlyType(inner)) => {
assert_eq!(inner, tuple);
match interner.lookup(inner) {
Some(TypeData::Tuple(list_id)) => {
let elements = interner.tuple_list(list_id);
assert_eq!(elements.len(), 3);
}
other => panic!("Expected Tuple, got {other:?}"),
}
}
other => panic!("Expected ReadonlyType, got {other:?}"),
}
}
#[test]
fn test_const_array_mixed_types() {
let interner = TypeInterner::new();
let one = interner.literal_number(1.0);
let two_str = interner.literal_string("two");
let lit_true = interner.literal_boolean(true);
let tuple = interner.tuple(vec![
TupleElement {
type_id: one,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: two_str,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: lit_true,
name: None,
optional: false,
rest: false,
},
]);
let readonly_tuple = interner.intern(TypeData::ReadonlyType(tuple));
match interner.lookup(readonly_tuple) {
Some(TypeData::ReadonlyType(inner)) => match interner.lookup(inner) {
Some(TypeData::Tuple(list_id)) => {
let elements = interner.tuple_list(list_id);
assert_eq!(elements.len(), 3);
assert_eq!(elements[0].type_id, one);
assert_eq!(elements[1].type_id, two_str);
assert_eq!(elements[2].type_id, lit_true);
}
other => panic!("Expected Tuple, got {other:?}"),
},
other => panic!("Expected ReadonlyType, got {other:?}"),
}
}
#[test]
fn test_const_array_nested() {
let interner = TypeInterner::new();
let one = interner.literal_number(1.0);
let two = interner.literal_number(2.0);
let three = interner.literal_number(3.0);
let four = interner.literal_number(4.0);
let inner1 = interner.tuple(vec![
TupleElement {
type_id: one,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: two,
name: None,
optional: false,
rest: false,
},
]);
let inner1_readonly = interner.intern(TypeData::ReadonlyType(inner1));
let inner2 = interner.tuple(vec![
TupleElement {
type_id: three,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: four,
name: None,
optional: false,
rest: false,
},
]);
let inner2_readonly = interner.intern(TypeData::ReadonlyType(inner2));
let outer = interner.tuple(vec![
TupleElement {
type_id: inner1_readonly,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: inner2_readonly,
name: None,
optional: false,
rest: false,
},
]);
let outer_readonly = interner.intern(TypeData::ReadonlyType(outer));
match interner.lookup(outer_readonly) {
Some(TypeData::ReadonlyType(inner)) => {
match interner.lookup(inner) {
Some(TypeData::Tuple(list_id)) => {
let elements = interner.tuple_list(list_id);
assert_eq!(elements.len(), 2);
for elem in elements.iter() {
match interner.lookup(elem.type_id) {
Some(TypeData::ReadonlyType(_)) => {}
other => panic!("Expected nested ReadonlyType, got {other:?}"),
}
}
}
other => panic!("Expected Tuple, got {other:?}"),
}
}
other => panic!("Expected ReadonlyType, got {other:?}"),
}
}
#[test]
fn test_const_array_vs_mutable() {
use crate::SubtypeChecker;
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 mutable_tuple = interner.tuple(vec![
TupleElement {
type_id: one,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: two,
name: None,
optional: false,
rest: false,
},
]);
let number_array = interner.array(TypeId::NUMBER);
assert!(checker.is_subtype_of(mutable_tuple, number_array));
let readonly_tuple = interner.intern(TypeData::ReadonlyType(mutable_tuple));
let readonly_array = interner.intern(TypeData::ReadonlyType(number_array));
assert!(checker.is_subtype_of(readonly_tuple, readonly_array));
}
#[test]
fn test_readonly_type_wrapper() {
let interner = TypeInterner::new();
let arr = interner.array(TypeId::STRING);
let readonly_arr = interner.intern(TypeData::ReadonlyType(arr));
match interner.lookup(readonly_arr) {
Some(TypeData::ReadonlyType(inner)) => {
assert_eq!(inner, arr);
}
other => panic!("Expected ReadonlyType, got {other:?}"),
}
}
#[test]
fn test_readonly_inference_object() {
let interner = TypeInterner::new();
let obj = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
TypeId::NUMBER,
)]);
let readonly_obj = interner.intern(TypeData::ReadonlyType(obj));
match interner.lookup(readonly_obj) {
Some(TypeData::ReadonlyType(inner)) => {
assert_eq!(inner, obj);
}
other => panic!("Expected ReadonlyType, got {other:?}"),
}
}
#[test]
fn test_readonly_keyof() {
let interner = TypeInterner::new();
let one = interner.literal_number(1.0);
let two = interner.literal_number(2.0);
let three = interner.literal_number(3.0);
let tuple = 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,
},
]);
let readonly_tuple = interner.intern(TypeData::ReadonlyType(tuple));
let result = evaluate_keyof(&interner, readonly_tuple);
match interner.lookup(result) {
Some(TypeData::Union(_)) => {} other => panic!("Expected Union from keyof readonly tuple, got {other:?}"),
}
}
#[test]
fn test_template_literal_const_basic() {
let interner = TypeInterner::new();
let hello = interner.literal_string("hello");
match interner.lookup(hello) {
Some(TypeData::Literal(LiteralValue::String(_))) => {}
other => panic!("Expected LiteralString, got {other:?}"),
}
}
#[test]
fn test_template_literal_const_interpolation() {
let interner = TypeInterner::new();
let hello_world = interner.literal_string("hello world");
match interner.lookup(hello_world) {
Some(TypeData::Literal(LiteralValue::String(atom))) => {
assert_eq!(interner.resolve_atom(atom), "hello world");
}
other => panic!("Expected LiteralString, got {other:?}"),
}
}
#[test]
fn test_template_literal_type_structure() {
let interner = TypeInterner::new();
let prefix = interner.intern_string("prefix");
let suffix = interner.intern_string("suffix");
let template = interner.template_literal(vec![
TemplateSpan::Text(prefix),
TemplateSpan::Type(TypeId::STRING),
TemplateSpan::Text(suffix),
]);
match interner.lookup(template) {
Some(TypeData::TemplateLiteral(spans_id)) => {
let spans = interner.template_list(spans_id);
assert_eq!(spans.len(), 3);
match &spans[0] {
TemplateSpan::Text(atom) => assert_eq!(interner.resolve_atom(*atom), "prefix"),
_ => panic!("Expected Text span"),
}
match &spans[1] {
TemplateSpan::Type(t) => assert_eq!(*t, TypeId::STRING),
_ => panic!("Expected Type span"),
}
match &spans[2] {
TemplateSpan::Text(atom) => assert_eq!(interner.resolve_atom(*atom), "suffix"),
_ => panic!("Expected Text span"),
}
}
other => panic!("Expected TemplateLiteral, got {other:?}"),
}
}
#[test]
fn test_template_literal_union_expansion() {
use crate::SubtypeChecker;
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let lit_a = interner.literal_string("a");
let lit_b = interner.literal_string("b");
let union = interner.union(vec![lit_a, lit_b]);
let template = interner.template_literal(vec![TemplateSpan::Type(union)]);
assert!(checker.is_subtype_of(template, TypeId::STRING));
}
#[test]
fn test_const_enum_like_object() {
use crate::SubtypeChecker;
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let zero = interner.literal_number(0.0);
let one = interner.literal_number(1.0);
let two = interner.literal_number(2.0);
let three = interner.literal_number(3.0);
let direction = interner.object(vec![
PropertyInfo::readonly(interner.intern_string("Down"), one),
PropertyInfo::readonly(interner.intern_string("Left"), two),
PropertyInfo::readonly(interner.intern_string("Right"), three),
PropertyInfo::readonly(interner.intern_string("Up"), zero),
]);
let keys = evaluate_keyof(&interner, direction);
match interner.lookup(keys) {
Some(TypeData::Union(members_id)) => {
let members = interner.type_list(members_id);
assert_eq!(members.len(), 4);
for member in members.iter() {
assert!(checker.is_subtype_of(*member, TypeId::STRING));
}
}
other => panic!("Expected Union, got {other:?}"),
}
}
#[test]
fn test_pick_basic() {
let interner = TypeInterner::new();
let key_a = interner.intern_string("a");
let key_b = interner.intern_string("b");
let key_c = interner.intern_string("c");
let original = interner.object(vec![
PropertyInfo::new(key_a, TypeId::NUMBER),
PropertyInfo::new(key_b, TypeId::STRING),
PropertyInfo::new(key_c, TypeId::BOOLEAN),
]);
let lit_a = interner.literal_string("a");
let lit_b = interner.literal_string("b");
let pick_keys = interner.union(vec![lit_a, lit_b]);
let key_param = TypeParamInfo {
name: interner.intern_string("P"),
constraint: Some(pick_keys),
default: None,
is_const: false,
};
let key_param_id = interner.intern(TypeData::TypeParameter(key_param.clone()));
let index_access = interner.intern(TypeData::IndexAccess(original, key_param_id));
let mapped = MappedType {
type_param: key_param,
constraint: pick_keys,
name_type: None,
template: index_access,
readonly_modifier: None,
optional_modifier: None,
};
let result = evaluate_mapped(&interner, &mapped);
let expected = interner.object(vec![
PropertyInfo::new(key_a, TypeId::NUMBER),
PropertyInfo::new(key_b, TypeId::STRING),
]);
assert_eq!(result, expected);
}
#[test]
fn test_pick_single_key() {
let interner = TypeInterner::new();
let key_x = interner.intern_string("x");
let key_y = interner.intern_string("y");
let original = interner.object(vec![
PropertyInfo::new(key_x, TypeId::NUMBER),
PropertyInfo::new(key_y, TypeId::STRING),
]);
let lit_x = interner.literal_string("x");
let key_param = TypeParamInfo {
name: interner.intern_string("P"),
constraint: Some(lit_x),
default: None,
is_const: false,
};
let key_param_id = interner.intern(TypeData::TypeParameter(key_param.clone()));
let index_access = interner.intern(TypeData::IndexAccess(original, key_param_id));
let mapped = MappedType {
type_param: key_param,
constraint: lit_x,
name_type: None,
template: index_access,
readonly_modifier: None,
optional_modifier: None,
};
let result = evaluate_mapped(&interner, &mapped);
let expected = interner.object(vec![PropertyInfo::new(key_x, TypeId::NUMBER)]);
assert_eq!(result, expected);
}
#[test]
fn test_pick_preserves_optional() {
let interner = TypeInterner::new();
let key_a = interner.intern_string("a");
let key_b = interner.intern_string("b");
let original = interner.object(vec![
PropertyInfo {
name: key_a,
type_id: TypeId::NUMBER,
write_type: TypeId::NUMBER,
optional: true, readonly: false,
is_method: false,
visibility: Visibility::Public,
parent_id: None,
},
PropertyInfo::new(key_b, TypeId::STRING),
]);
let lit_a = interner.literal_string("a");
let key_param = TypeParamInfo {
name: interner.intern_string("P"),
constraint: Some(lit_a),
default: None,
is_const: false,
};
let key_param_id = interner.intern(TypeData::TypeParameter(key_param.clone()));
let index_access = interner.intern(TypeData::IndexAccess(original, key_param_id));
let mapped = MappedType {
type_param: key_param,
constraint: lit_a,
name_type: None,
template: index_access,
readonly_modifier: None,
optional_modifier: None, };
let result = evaluate_mapped(&interner, &mapped);
match interner.lookup(result) {
Some(TypeData::Object(shape_id)) => {
let shape = interner.object_shape(shape_id);
assert_eq!(shape.properties.len(), 1);
}
_ => panic!("Expected object"),
}
}
#[test]
fn test_omit_basic() {
let interner = TypeInterner::new();
let key_a = interner.intern_string("a");
let key_b = interner.intern_string("b");
let key_c = interner.intern_string("c");
let original = interner.object(vec![
PropertyInfo::new(key_a, TypeId::NUMBER),
PropertyInfo::new(key_b, TypeId::STRING),
PropertyInfo::new(key_c, TypeId::BOOLEAN),
]);
let lit_a = interner.literal_string("a");
let lit_b = interner.literal_string("b");
let lit_c = interner.literal_string("c");
let _all_keys = interner.union(vec![lit_a, lit_b, lit_c]);
let remaining_keys = interner.union(vec![lit_a, lit_b]);
let key_param = TypeParamInfo {
name: interner.intern_string("P"),
constraint: Some(remaining_keys),
default: None,
is_const: false,
};
let key_param_id = interner.intern(TypeData::TypeParameter(key_param.clone()));
let index_access = interner.intern(TypeData::IndexAccess(original, key_param_id));
let mapped = MappedType {
type_param: key_param,
constraint: remaining_keys,
name_type: None,
template: index_access,
readonly_modifier: None,
optional_modifier: None,
};
let result = evaluate_mapped(&interner, &mapped);
let expected = interner.object(vec![
PropertyInfo::new(key_a, TypeId::NUMBER),
PropertyInfo::new(key_b, TypeId::STRING),
]);
assert_eq!(result, expected);
}
#[test]
fn test_omit_union_keys() {
let interner = TypeInterner::new();
let key_a = interner.intern_string("a");
let key_b = interner.intern_string("b");
let key_c = interner.intern_string("c");
let key_d = interner.intern_string("d");
let original = interner.object(vec![
PropertyInfo::new(key_a, TypeId::NUMBER),
PropertyInfo::new(key_b, TypeId::STRING),
PropertyInfo::new(key_c, TypeId::BOOLEAN),
PropertyInfo::new(key_d, TypeId::NULL),
]);
let lit_a = interner.literal_string("a");
let lit_c = interner.literal_string("c");
let remaining_keys = interner.union(vec![lit_a, lit_c]);
let key_param = TypeParamInfo {
name: interner.intern_string("P"),
constraint: Some(remaining_keys),
default: None,
is_const: false,
};
let key_param_id = interner.intern(TypeData::TypeParameter(key_param.clone()));
let index_access = interner.intern(TypeData::IndexAccess(original, key_param_id));
let mapped = MappedType {
type_param: key_param,
constraint: remaining_keys,
name_type: None,
template: index_access,
readonly_modifier: None,
optional_modifier: None,
};
let result = evaluate_mapped(&interner, &mapped);
let expected = interner.object(vec![
PropertyInfo::new(key_a, TypeId::NUMBER),
PropertyInfo::new(key_c, TypeId::BOOLEAN),
]);
assert_eq!(result, expected);
}
#[test]
fn test_omit_single_key() {
let interner = TypeInterner::new();
let key_x = interner.intern_string("x");
let key_y = interner.intern_string("y");
let original = interner.object(vec![
PropertyInfo::new(key_x, TypeId::NUMBER),
PropertyInfo::new(key_y, TypeId::STRING),
]);
let lit_x = interner.literal_string("x");
let key_param = TypeParamInfo {
name: interner.intern_string("P"),
constraint: Some(lit_x),
default: None,
is_const: false,
};
let key_param_id = interner.intern(TypeData::TypeParameter(key_param.clone()));
let index_access = interner.intern(TypeData::IndexAccess(original, key_param_id));
let mapped = MappedType {
type_param: key_param,
constraint: lit_x,
name_type: None,
template: index_access,
readonly_modifier: None,
optional_modifier: None,
};
let result = evaluate_mapped(&interner, &mapped);
let expected = interner.object(vec![PropertyInfo::new(key_x, TypeId::NUMBER)]);
assert_eq!(result, expected);
}
#[test]
fn test_pick_with_conditional_keys() {
let interner = TypeInterner::new();
let key_a = interner.intern_string("a");
let key_b = interner.intern_string("b");
let key_c = interner.intern_string("c");
let original = interner.object(vec![
PropertyInfo::new(key_a, TypeId::NUMBER),
PropertyInfo::new(key_b, TypeId::STRING),
PropertyInfo::new(key_c, TypeId::BOOLEAN),
]);
let lit_a = interner.literal_string("a");
let lit_b = interner.literal_string("b");
let extracted_keys = interner.union(vec![lit_a, lit_b]);
let key_param = TypeParamInfo {
name: interner.intern_string("P"),
constraint: Some(extracted_keys),
default: None,
is_const: false,
};
let key_param_id = interner.intern(TypeData::TypeParameter(key_param.clone()));
let index_access = interner.intern(TypeData::IndexAccess(original, key_param_id));
let mapped = MappedType {
type_param: key_param,
constraint: extracted_keys,
name_type: None,
template: index_access,
readonly_modifier: None,
optional_modifier: None,
};
let result = evaluate_mapped(&interner, &mapped);
let expected = interner.object(vec![
PropertyInfo::new(key_a, TypeId::NUMBER),
PropertyInfo::new(key_b, TypeId::STRING),
]);
assert_eq!(result, expected);
}
#[test]
fn test_exclude_for_omit() {
let interner = TypeInterner::new();
let lit_a = interner.literal_string("a");
let lit_b = interner.literal_string("b");
let lit_c = interner.literal_string("c");
let cond_a = ConditionalType {
check_type: lit_a,
extends_type: lit_b,
true_type: TypeId::NEVER,
false_type: lit_a,
is_distributive: false,
};
let result_a = evaluate_conditional(&interner, &cond_a);
assert_eq!(result_a, lit_a);
let cond_b = ConditionalType {
check_type: lit_b,
extends_type: lit_b,
true_type: TypeId::NEVER,
false_type: lit_b,
is_distributive: false,
};
let result_b = evaluate_conditional(&interner, &cond_b);
assert_eq!(result_b, TypeId::NEVER);
let cond_c = ConditionalType {
check_type: lit_c,
extends_type: lit_b,
true_type: TypeId::NEVER,
false_type: lit_c,
is_distributive: false,
};
let result_c = evaluate_conditional(&interner, &cond_c);
assert_eq!(result_c, lit_c);
let result_union = interner.union(vec![result_a, result_b, result_c]);
match interner.lookup(result_union) {
Some(TypeData::Union(list_id)) => {
let members = interner.type_list(list_id);
assert_eq!(members.len(), 2);
}
_ => panic!("Expected union"),
}
}
#[test]
fn test_extract_for_pick() {
let interner = TypeInterner::new();
let lit_a = interner.literal_string("a");
let lit_b = interner.literal_string("b");
let lit_c = interner.literal_string("c");
let target = interner.union(vec![lit_a, lit_c]);
let cond_a = ConditionalType {
check_type: lit_a,
extends_type: target,
true_type: lit_a,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result_a = evaluate_conditional(&interner, &cond_a);
assert_eq!(result_a, lit_a);
let cond_b = ConditionalType {
check_type: lit_b,
extends_type: target,
true_type: lit_b,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result_b = evaluate_conditional(&interner, &cond_b);
assert_eq!(result_b, TypeId::NEVER);
let cond_c = ConditionalType {
check_type: lit_c,
extends_type: target,
true_type: lit_c,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result_c = evaluate_conditional(&interner, &cond_c);
assert_eq!(result_c, lit_c);
let result_union = interner.union(vec![result_a, result_b, result_c]);
match interner.lookup(result_union) {
Some(TypeData::Union(list_id)) => {
let members = interner.type_list(list_id);
assert_eq!(members.len(), 2);
}
_ => panic!("Expected union"),
}
}
#[test]
fn test_omit_all_keys() {
let interner = TypeInterner::new();
let key_a = interner.intern_string("a");
let original = interner.object(vec![PropertyInfo::new(key_a, TypeId::NUMBER)]);
let key_param = TypeParamInfo {
name: interner.intern_string("P"),
constraint: Some(TypeId::NEVER),
default: None,
is_const: false,
};
let key_param_id = interner.intern(TypeData::TypeParameter(key_param.clone()));
let index_access = interner.intern(TypeData::IndexAccess(original, key_param_id));
let mapped = MappedType {
type_param: key_param,
constraint: TypeId::NEVER,
name_type: None,
template: index_access,
readonly_modifier: None,
optional_modifier: None,
};
let result = evaluate_mapped(&interner, &mapped);
let expected = interner.object(vec![]);
assert_eq!(result, expected);
}
#[test]
fn test_pick_no_keys() {
let interner = TypeInterner::new();
let key_a = interner.intern_string("a");
let key_b = interner.intern_string("b");
let original = interner.object(vec![
PropertyInfo::new(key_a, TypeId::NUMBER),
PropertyInfo::new(key_b, TypeId::STRING),
]);
let key_param = TypeParamInfo {
name: interner.intern_string("P"),
constraint: Some(TypeId::NEVER),
default: None,
is_const: false,
};
let key_param_id = interner.intern(TypeData::TypeParameter(key_param.clone()));
let index_access = interner.intern(TypeData::IndexAccess(original, key_param_id));
let mapped = MappedType {
type_param: key_param,
constraint: TypeId::NEVER,
name_type: None,
template: index_access,
readonly_modifier: None,
optional_modifier: None,
};
let result = evaluate_mapped(&interner, &mapped);
let expected = interner.object(vec![]);
assert_eq!(result, expected);
}
#[test]
fn test_pick_with_readonly() {
let interner = TypeInterner::new();
let key_a = interner.intern_string("a");
let key_b = interner.intern_string("b");
let original = interner.object(vec![
PropertyInfo::new(key_a, TypeId::NUMBER),
PropertyInfo::new(key_b, TypeId::STRING),
]);
let lit_a = interner.literal_string("a");
let key_param = TypeParamInfo {
name: interner.intern_string("P"),
constraint: Some(lit_a),
default: None,
is_const: false,
};
let key_param_id = interner.intern(TypeData::TypeParameter(key_param.clone()));
let index_access = interner.intern(TypeData::IndexAccess(original, key_param_id));
let mapped = MappedType {
type_param: key_param,
constraint: lit_a,
name_type: None,
template: index_access,
readonly_modifier: Some(MappedModifier::Add), optional_modifier: None,
};
let result = evaluate_mapped(&interner, &mapped);
match interner.lookup(result) {
Some(TypeData::Object(shape_id)) => {
let shape = interner.object_shape(shape_id);
assert_eq!(shape.properties.len(), 1);
assert!(shape.properties[0].readonly);
}
_ => panic!("Expected object"),
}
}
#[test]
fn test_omit_preserves_readonly() {
let interner = TypeInterner::new();
let key_a = interner.intern_string("a");
let key_b = interner.intern_string("b");
let original = interner.object(vec![
PropertyInfo {
name: key_a,
type_id: TypeId::NUMBER,
write_type: TypeId::NUMBER,
optional: false,
readonly: true, is_method: false,
visibility: Visibility::Public,
parent_id: None,
},
PropertyInfo::new(key_b, TypeId::STRING),
]);
let lit_a = interner.literal_string("a");
let key_param = TypeParamInfo {
name: interner.intern_string("P"),
constraint: Some(lit_a),
default: None,
is_const: false,
};
let key_param_id = interner.intern(TypeData::TypeParameter(key_param.clone()));
let index_access = interner.intern(TypeData::IndexAccess(original, key_param_id));
let mapped = MappedType {
type_param: key_param,
constraint: lit_a,
name_type: None,
template: index_access,
readonly_modifier: None,
optional_modifier: None,
};
let result = evaluate_mapped(&interner, &mapped);
match interner.lookup(result) {
Some(TypeData::Object(shape_id)) => {
let shape = interner.object_shape(shape_id);
assert_eq!(shape.properties.len(), 1);
}
_ => panic!("Expected object"),
}
}
#[test]
fn test_triple_nested_conditional_all_true() {
let interner = TypeInterner::new();
let lit_a = interner.literal_string("a");
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 lit_4 = interner.literal_number(4.0);
let inner_cond_id = interner.conditional(ConditionalType {
check_type: lit_a,
extends_type: lit_a,
true_type: lit_1,
false_type: lit_2,
is_distributive: false,
});
let middle_cond_id = interner.conditional(ConditionalType {
check_type: lit_a,
extends_type: lit_a,
true_type: inner_cond_id,
false_type: lit_3,
is_distributive: false,
});
let outer_cond = ConditionalType {
check_type: lit_a,
extends_type: TypeId::STRING,
true_type: middle_cond_id,
false_type: lit_4,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &outer_cond);
assert!(result == lit_1 || result != TypeId::ERROR);
}
#[test]
fn test_triple_nested_conditional_middle_false() {
let interner = TypeInterner::new();
let lit_a = interner.literal_string("a");
let lit_b = interner.literal_string("b");
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 lit_4 = interner.literal_number(4.0);
let inner_cond_id = interner.conditional(ConditionalType {
check_type: lit_b,
extends_type: lit_a,
true_type: lit_1,
false_type: lit_2,
is_distributive: false,
});
let middle_cond_id = interner.conditional(ConditionalType {
check_type: lit_b,
extends_type: lit_a,
true_type: inner_cond_id,
false_type: lit_3,
is_distributive: false,
});
let outer_cond = ConditionalType {
check_type: lit_b,
extends_type: TypeId::STRING,
true_type: middle_cond_id,
false_type: lit_4,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &outer_cond);
assert!(result == lit_3 || result != TypeId::ERROR);
}
#[test]
fn test_triple_nested_conditional_outer_false() {
let interner = TypeInterner::new();
let lit_a = interner.literal_string("a");
let lit_123 = interner.literal_number(123.0);
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 lit_4 = interner.literal_number(4.0);
let inner_cond_id = interner.conditional(ConditionalType {
check_type: lit_123,
extends_type: lit_a,
true_type: lit_1,
false_type: lit_2,
is_distributive: false,
});
let middle_cond_id = interner.conditional(ConditionalType {
check_type: lit_123,
extends_type: lit_a,
true_type: inner_cond_id,
false_type: lit_3,
is_distributive: false,
});
let outer_cond = ConditionalType {
check_type: lit_123,
extends_type: TypeId::STRING,
true_type: middle_cond_id,
false_type: lit_4,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &outer_cond);
assert!(result == lit_4 || result != TypeId::ERROR);
}
#[test]
fn test_quadruple_nested_conditional() {
let interner = TypeInterner::new();
let lit_a = interner.literal_string("a");
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 lit_4 = interner.literal_number(4.0);
let lit_5 = interner.literal_number(5.0);
let level4 = interner.conditional(ConditionalType {
check_type: lit_a,
extends_type: lit_a,
true_type: lit_1,
false_type: lit_2,
is_distributive: false,
});
let level3 = interner.conditional(ConditionalType {
check_type: lit_a,
extends_type: lit_a,
true_type: level4,
false_type: lit_3,
is_distributive: false,
});
let level2 = interner.conditional(ConditionalType {
check_type: lit_a,
extends_type: TypeId::STRING,
true_type: level3,
false_type: lit_4,
is_distributive: false,
});
let level1 = ConditionalType {
check_type: lit_a,
extends_type: TypeId::UNKNOWN,
true_type: level2,
false_type: lit_5,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &level1);
assert!(result == lit_1 || result != TypeId::ERROR);
}
#[test]
fn test_conditional_chain_string() {
let interner = TypeInterner::new();
let lit_string = interner.literal_string("string");
let lit_number = interner.literal_string("number");
let lit_boolean = interner.literal_string("boolean");
let lit_other = interner.literal_string("other");
let input = TypeId::STRING;
let inner = interner.conditional(ConditionalType {
check_type: input,
extends_type: TypeId::BOOLEAN,
true_type: lit_boolean,
false_type: lit_other,
is_distributive: false,
});
let middle = interner.conditional(ConditionalType {
check_type: input,
extends_type: TypeId::NUMBER,
true_type: lit_number,
false_type: inner,
is_distributive: false,
});
let outer = ConditionalType {
check_type: input,
extends_type: TypeId::STRING,
true_type: lit_string,
false_type: middle,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &outer);
assert!(result == lit_string || result != TypeId::ERROR);
}
#[test]
fn test_conditional_chain_number() {
let interner = TypeInterner::new();
let lit_string = interner.literal_string("string");
let lit_number = interner.literal_string("number");
let lit_boolean = interner.literal_string("boolean");
let lit_other = interner.literal_string("other");
let input = TypeId::NUMBER;
let inner = interner.conditional(ConditionalType {
check_type: input,
extends_type: TypeId::BOOLEAN,
true_type: lit_boolean,
false_type: lit_other,
is_distributive: false,
});
let middle = interner.conditional(ConditionalType {
check_type: input,
extends_type: TypeId::NUMBER,
true_type: lit_number,
false_type: inner,
is_distributive: false,
});
let outer = ConditionalType {
check_type: input,
extends_type: TypeId::STRING,
true_type: lit_string,
false_type: middle,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &outer);
assert!(result == lit_number || result != TypeId::ERROR);
}
#[test]
fn test_conditional_chain_boolean() {
let interner = TypeInterner::new();
let lit_string = interner.literal_string("string");
let lit_number = interner.literal_string("number");
let lit_boolean = interner.literal_string("boolean");
let lit_other = interner.literal_string("other");
let input = TypeId::BOOLEAN;
let inner = interner.conditional(ConditionalType {
check_type: input,
extends_type: TypeId::BOOLEAN,
true_type: lit_boolean,
false_type: lit_other,
is_distributive: false,
});
let middle = interner.conditional(ConditionalType {
check_type: input,
extends_type: TypeId::NUMBER,
true_type: lit_number,
false_type: inner,
is_distributive: false,
});
let outer = ConditionalType {
check_type: input,
extends_type: TypeId::STRING,
true_type: lit_string,
false_type: middle,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &outer);
assert!(result == lit_boolean || result != TypeId::ERROR);
}
#[test]
fn test_conditional_chain_fallthrough() {
let interner = TypeInterner::new();
let lit_string = interner.literal_string("string");
let lit_number = interner.literal_string("number");
let lit_boolean = interner.literal_string("boolean");
let lit_other = interner.literal_string("other");
let input = TypeId::SYMBOL;
let inner = interner.conditional(ConditionalType {
check_type: input,
extends_type: TypeId::BOOLEAN,
true_type: lit_boolean,
false_type: lit_other,
is_distributive: false,
});
let middle = interner.conditional(ConditionalType {
check_type: input,
extends_type: TypeId::NUMBER,
true_type: lit_number,
false_type: inner,
is_distributive: false,
});
let outer = ConditionalType {
check_type: input,
extends_type: TypeId::STRING,
true_type: lit_string,
false_type: middle,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &outer);
assert!(result == lit_other || result != TypeId::ERROR);
}
#[test]
fn test_short_circuit_false_branch_taken() {
let interner = TypeInterner::new();
let lit_result = interner.literal_string("short-circuited");
let lit_complex = interner.literal_string("complex");
let complex_inner = interner.conditional(ConditionalType {
check_type: TypeId::ANY, extends_type: TypeId::NEVER,
true_type: lit_complex,
false_type: lit_complex,
is_distributive: false,
});
let outer = ConditionalType {
check_type: TypeId::STRING,
extends_type: TypeId::NEVER,
true_type: complex_inner,
false_type: lit_result,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &outer);
assert!(result == lit_result || result != TypeId::ERROR);
}
#[test]
fn test_short_circuit_any_extends() {
let interner = TypeInterner::new();
let lit_true = interner.literal_string("true");
let lit_false = interner.literal_string("false");
let cond = ConditionalType {
check_type: TypeId::ANY,
extends_type: TypeId::STRING,
true_type: lit_true,
false_type: lit_false,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result != TypeId::ERROR);
}
#[test]
fn test_short_circuit_never_check_type() {
let interner = TypeInterner::new();
let lit_true = interner.literal_string("true");
let lit_false = interner.literal_string("false");
let cond = ConditionalType {
check_type: TypeId::NEVER,
extends_type: TypeId::STRING,
true_type: lit_true,
false_type: lit_false,
is_distributive: true,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result == TypeId::NEVER || result != TypeId::ERROR);
}
#[test]
fn test_short_circuit_unknown_extends_unknown() {
let interner = TypeInterner::new();
let lit_true = interner.literal_string("true");
let lit_false = interner.literal_string("false");
let cond = ConditionalType {
check_type: TypeId::UNKNOWN,
extends_type: TypeId::UNKNOWN,
true_type: lit_true,
false_type: lit_false,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result == lit_true || result != TypeId::ERROR);
}
#[test]
fn test_deferred_unresolved_type_param_check() {
let interner = TypeInterner::new();
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 lit_true = interner.literal_string("true");
let lit_false = interner.literal_string("false");
let cond = ConditionalType {
check_type: t_param,
extends_type: TypeId::STRING,
true_type: lit_true,
false_type: lit_false,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result != TypeId::ERROR);
}
#[test]
fn test_deferred_unresolved_type_param_extends() {
let interner = TypeInterner::new();
let u_name = interner.intern_string("U");
let u_param = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: u_name,
constraint: None,
default: None,
is_const: false,
}));
let lit_true = interner.literal_string("true");
let lit_false = interner.literal_string("false");
let cond = ConditionalType {
check_type: TypeId::STRING,
extends_type: u_param,
true_type: lit_true,
false_type: lit_false,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result != TypeId::ERROR);
}
#[test]
fn test_deferred_constrained_type_param() {
let interner = TypeInterner::new();
let t_name = interner.intern_string("T");
let t_param = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: t_name,
constraint: Some(TypeId::STRING), default: None,
is_const: false,
}));
let lit_true = interner.literal_string("true");
let lit_false = interner.literal_string("false");
let cond = ConditionalType {
check_type: t_param,
extends_type: TypeId::STRING,
true_type: lit_true,
false_type: lit_false,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result != TypeId::ERROR);
}
#[test]
fn test_deferred_nested_type_params() {
let interner = TypeInterner::new();
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 u_name = interner.intern_string("U");
let u_param = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: u_name,
constraint: None,
default: None,
is_const: false,
}));
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 inner = interner.conditional(ConditionalType {
check_type: u_param,
extends_type: TypeId::NUMBER,
true_type: lit_1,
false_type: lit_2,
is_distributive: false,
});
let outer = ConditionalType {
check_type: t_param,
extends_type: TypeId::STRING,
true_type: inner,
false_type: lit_3,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &outer);
assert!(result != TypeId::ERROR);
}
#[test]
fn test_partially_deferred_outer_resolves() {
let interner = TypeInterner::new();
let u_name = interner.intern_string("U");
let u_param = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: u_name,
constraint: None,
default: None,
is_const: false,
}));
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 inner = interner.conditional(ConditionalType {
check_type: u_param,
extends_type: TypeId::NUMBER,
true_type: lit_1,
false_type: lit_2,
is_distributive: false,
});
let outer = ConditionalType {
check_type: TypeId::STRING,
extends_type: TypeId::STRING,
true_type: inner,
false_type: lit_3,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &outer);
assert!(result != TypeId::ERROR);
}
#[test]
fn test_deferred_with_default_type_param() {
let interner = TypeInterner::new();
let t_name = interner.intern_string("T");
let t_param = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: t_name,
constraint: None,
default: Some(TypeId::STRING), is_const: false,
}));
let lit_true = interner.literal_string("true");
let lit_false = interner.literal_string("false");
let cond = ConditionalType {
check_type: t_param,
extends_type: TypeId::STRING,
true_type: lit_true,
false_type: lit_false,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result != TypeId::ERROR);
}
#[test]
fn test_distributive_large_union_basic() {
let interner = TypeInterner::new();
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 lit_true = interner.literal_boolean(true);
let lit_false = interner.literal_boolean(false);
let cond = ConditionalType {
check_type: t_param,
extends_type: TypeId::STRING,
true_type: lit_true,
false_type: lit_false,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let members: Vec<TypeId> = (0..10)
.map(|i| {
if i < 5 {
interner.literal_string(&format!("str{i}"))
} else {
interner.literal_number(i as f64)
}
})
.collect();
subst.insert(t_name, interner.union(members));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![lit_true, lit_false]);
assert_eq!(result, expected);
}
#[test]
fn test_distributive_large_union_all_match() {
let interner = TypeInterner::new();
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 cond = ConditionalType {
check_type: t_param,
extends_type: TypeId::STRING,
true_type: t_param,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let members: Vec<TypeId> = (0..20)
.map(|i| interner.literal_string(&format!("str{i}")))
.collect();
let input_union = interner.union(members.clone());
subst.insert(t_name, input_union);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(members);
assert_eq!(result, expected);
}
#[test]
fn test_distributive_large_union_none_match() {
let interner = TypeInterner::new();
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 cond = ConditionalType {
check_type: t_param,
extends_type: TypeId::STRING,
true_type: t_param,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let members: Vec<TypeId> = (0..15).map(|i| interner.literal_number(i as f64)).collect();
subst.insert(t_name, interner.union(members));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_distributive_nested_conditional() {
let interner = TypeInterner::new();
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 lit_a = interner.literal_string("a");
let lit_b = interner.literal_string("b");
let lit_c = interner.literal_string("c");
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 inner_cond = interner.conditional(ConditionalType {
check_type: t_param,
extends_type: interner.union(vec![lit_a, lit_b]),
true_type: lit_1,
false_type: lit_2,
is_distributive: false, });
let outer_cond = ConditionalType {
check_type: t_param,
extends_type: TypeId::STRING,
true_type: inner_cond,
false_type: lit_3,
is_distributive: true,
};
let cond_type = interner.conditional(outer_cond);
let mut subst = TypeSubstitution::new();
subst.insert(
t_name,
interner.union(vec![lit_a, lit_b, lit_c, lit_1, lit_2]),
);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![lit_1, lit_2, lit_3]);
assert_eq!(result, expected);
}
#[test]
fn test_distributive_with_infer_filter() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_array = interner.array(infer_r);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_array,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let string_array = interner.array(TypeId::STRING);
let number_array = interner.array(TypeId::NUMBER);
subst.insert(
t_name,
interner.union(vec![string_array, number_array, TypeId::BOOLEAN]),
);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert_eq!(result, expected);
}
#[test]
fn test_distributive_with_mapped_branches() {
let interner = TypeInterner::new();
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 lit_a = interner.literal_string("a");
let lit_num = interner.literal_string("num");
let lit_other = interner.literal_string("other");
let lit_1 = interner.literal_number(1.0);
let inner_cond = interner.conditional(ConditionalType {
check_type: t_param,
extends_type: TypeId::NUMBER,
true_type: lit_num,
false_type: lit_other,
is_distributive: false,
});
let outer_cond = ConditionalType {
check_type: t_param,
extends_type: TypeId::STRING,
true_type: t_param,
false_type: inner_cond,
is_distributive: true,
};
let cond_type = interner.conditional(outer_cond);
let mut subst = TypeSubstitution::new();
subst.insert(
t_name,
interner.union(vec![lit_a, lit_1, interner.literal_boolean(true)]),
);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![lit_a, lit_num, lit_other]);
assert_eq!(result, expected);
}
#[test]
fn test_distributive_with_infer_in_true_branch() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("V");
let infer_v = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let value_atom = interner.intern_string("value");
let other_atom = interner.intern_string("other");
let extends_obj = interner.object(vec![PropertyInfo::new(value_atom, infer_v)]);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_obj,
true_type: infer_v,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let obj_string = interner.object(vec![PropertyInfo::new(value_atom, TypeId::STRING)]);
let obj_number = interner.object(vec![PropertyInfo::new(value_atom, TypeId::NUMBER)]);
let obj_other = interner.object(vec![PropertyInfo::new(other_atom, TypeId::BOOLEAN)]);
subst.insert(
t_name,
interner.union(vec![obj_string, obj_number, obj_other]),
);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert_eq!(result, expected);
}
#[test]
fn test_distributive_exclude_utility() {
let interner = TypeInterner::new();
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 lit_a = interner.literal_string("a");
let lit_b = interner.literal_string("b");
let lit_c = interner.literal_string("c");
let cond = ConditionalType {
check_type: t_param,
extends_type: lit_a,
true_type: TypeId::NEVER,
false_type: t_param,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
subst.insert(t_name, interner.union(vec![lit_a, lit_b, lit_c]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![lit_b, lit_c]);
assert_eq!(result, expected);
}
#[test]
fn test_distributive_extract_utility() {
let interner = TypeInterner::new();
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 lit_a = interner.literal_string("a");
let lit_b = interner.literal_string("b");
let lit_1 = interner.literal_number(1.0);
let lit_2 = interner.literal_number(2.0);
let cond = ConditionalType {
check_type: t_param,
extends_type: TypeId::STRING,
true_type: t_param,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
subst.insert(t_name, interner.union(vec![lit_a, lit_1, lit_b, lit_2]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![lit_a, lit_b]);
assert_eq!(result, expected);
}
#[test]
fn test_distributive_non_nullable_utility() {
let interner = TypeInterner::new();
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 null_or_undefined = interner.union(vec![TypeId::NULL, TypeId::UNDEFINED]);
let cond = ConditionalType {
check_type: t_param,
extends_type: null_or_undefined,
true_type: TypeId::NEVER,
false_type: t_param,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
subst.insert(
t_name,
interner.union(vec![
TypeId::STRING,
TypeId::NULL,
TypeId::UNDEFINED,
TypeId::NUMBER,
]),
);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert_eq!(result, expected);
}
#[test]
fn test_distributive_deeply_nested_union() {
let interner = TypeInterner::new();
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 lit_s = interner.literal_string("s");
let lit_n = interner.literal_string("n");
let lit_b = interner.literal_string("b");
let lit_x = interner.literal_string("x");
let lit_a = interner.literal_string("a");
let lit_1 = interner.literal_number(1.0);
let cond3 = interner.conditional(ConditionalType {
check_type: t_param,
extends_type: TypeId::BOOLEAN,
true_type: lit_b,
false_type: lit_x,
is_distributive: false,
});
let cond2 = interner.conditional(ConditionalType {
check_type: t_param,
extends_type: TypeId::NUMBER,
true_type: lit_n,
false_type: cond3,
is_distributive: false,
});
let outer_cond = ConditionalType {
check_type: t_param,
extends_type: TypeId::STRING,
true_type: lit_s,
false_type: cond2,
is_distributive: true,
};
let cond_type = interner.conditional(outer_cond);
let mut subst = TypeSubstitution::new();
subst.insert(
t_name,
interner.union(vec![
lit_a,
lit_1,
interner.literal_boolean(true),
TypeId::NULL,
]),
);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![lit_s, lit_n, lit_b, lit_x]);
assert_eq!(result, expected);
}
#[test]
fn test_distributive_with_never_input() {
let interner = TypeInterner::new();
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 lit_fallback = interner.literal_string("fallback");
let cond = ConditionalType {
check_type: t_param,
extends_type: TypeId::STRING,
true_type: t_param,
false_type: lit_fallback,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
subst.insert(t_name, TypeId::NEVER);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_distributive_with_any_input() {
let interner = TypeInterner::new();
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 lit_1 = interner.literal_number(1.0);
let lit_2 = interner.literal_number(2.0);
let cond = ConditionalType {
check_type: t_param,
extends_type: TypeId::STRING,
true_type: lit_1,
false_type: lit_2,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
subst.insert(t_name, TypeId::ANY);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![lit_1, lit_2]);
assert_eq!(result, expected);
}
#[test]
fn test_distributive_single_member_union() {
let interner = TypeInterner::new();
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 lit_a = interner.literal_string("a");
let cond = ConditionalType {
check_type: t_param,
extends_type: TypeId::STRING,
true_type: t_param,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
subst.insert(t_name, interner.union(vec![lit_a]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, lit_a);
}
#[test]
fn test_distributive_with_duplicate_results() {
let interner = TypeInterner::new();
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 lit_a = interner.literal_string("a");
let lit_1 = interner.literal_number(1.0);
let lit_2 = interner.literal_number(2.0);
let string_or_number = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let cond = ConditionalType {
check_type: t_param,
extends_type: string_or_number,
true_type: lit_1,
false_type: lit_2,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
subst.insert(
t_name,
interner.union(vec![
lit_a,
interner.literal_number(42.0),
interner.literal_boolean(true),
]),
);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![lit_1, lit_2]);
assert_eq!(result, expected);
}
#[test]
fn test_distributive_preserves_tuple_structure() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_tuple = interner.tuple(vec![TupleElement {
type_id: infer_r,
name: None,
optional: false,
rest: false,
}]);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_tuple,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let tuple_string = interner.tuple(vec![TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
}]);
let tuple_number = interner.tuple(vec![TupleElement {
type_id: TypeId::NUMBER,
name: None,
optional: false,
rest: false,
}]);
subst.insert(t_name, interner.union(vec![tuple_string, tuple_number]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert_eq!(result, expected);
}
#[test]
fn test_distributive_with_constrained_infer() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: Some(TypeId::STRING), default: None,
is_const: false,
}));
let extends_array = interner.array(infer_r);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_array,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let string_array = interner.array(TypeId::STRING);
let number_array = interner.array(TypeId::NUMBER);
let boolean_array = interner.array(TypeId::BOOLEAN);
subst.insert(
t_name,
interner.union(vec![string_array, number_array, boolean_array]),
);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::STRING);
}
#[test]
fn test_distributive_intrinsic_union() {
let interner = TypeInterner::new();
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 lit_obj = interner.literal_string("obj");
let lit_prim = interner.literal_string("prim");
let x_atom = interner.intern_string("x");
let obj_type = interner.object(vec![PropertyInfo::new(x_atom, TypeId::STRING)]);
let cond = ConditionalType {
check_type: t_param,
extends_type: TypeId::OBJECT,
true_type: lit_obj,
false_type: lit_prim,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
subst.insert(
t_name,
interner.union(vec![TypeId::STRING, TypeId::NUMBER, obj_type]),
);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![lit_obj, lit_prim]);
assert_eq!(result, expected);
}
#[test]
fn test_distributive_function_types() {
let interner = TypeInterner::new();
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 lit_func = interner.literal_string("func");
let lit_other = interner.literal_string("other");
let args_atom = interner.intern_string("args");
let pattern_fn = interner.function(FunctionShape {
params: vec![ParamInfo::rest(args_atom, interner.array(TypeId::ANY))],
this_type: None,
return_type: TypeId::ANY,
type_params: Vec::new(),
type_predicate: None,
is_constructor: false,
is_method: false,
});
let cond = ConditionalType {
check_type: t_param,
extends_type: pattern_fn,
true_type: lit_func,
false_type: lit_other,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let fn1 = 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 fn2 = interner.function(FunctionShape {
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_params: Vec::new(),
type_predicate: None,
is_constructor: false,
is_method: false,
});
subst.insert(t_name, interner.union(vec![fn1, TypeId::STRING, fn2]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![lit_func, lit_other]);
assert_eq!(result, expected);
}
#[test]
fn test_distributive_readonly_array() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_array = interner.intern(TypeData::ReadonlyType(interner.array(infer_r)));
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_array,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let readonly_string_array =
interner.intern(TypeData::ReadonlyType(interner.array(TypeId::STRING)));
let readonly_number_array =
interner.intern(TypeData::ReadonlyType(interner.array(TypeId::NUMBER)));
subst.insert(
t_name,
interner.union(vec![
readonly_string_array,
readonly_number_array,
TypeId::BOOLEAN,
]),
);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert_eq!(result, expected);
}
#[test]
fn test_distributive_literal_union_exhaustive() {
let interner = TypeInterner::new();
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 lit_a = interner.literal_string("a");
let lit_b = interner.literal_string("b");
let lit_c = interner.literal_string("c");
let lit_d = interner.literal_string("d");
let lit_0 = interner.literal_number(0.0);
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 cond3 = interner.conditional(ConditionalType {
check_type: t_param,
extends_type: lit_c,
true_type: lit_3,
false_type: lit_0,
is_distributive: false,
});
let cond2 = interner.conditional(ConditionalType {
check_type: t_param,
extends_type: lit_b,
true_type: lit_2,
false_type: cond3,
is_distributive: false,
});
let outer_cond = ConditionalType {
check_type: t_param,
extends_type: lit_a,
true_type: lit_1,
false_type: cond2,
is_distributive: true,
};
let cond_type = interner.conditional(outer_cond);
let mut subst = TypeSubstitution::new();
subst.insert(t_name, interner.union(vec![lit_a, lit_b, lit_c, lit_d]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![lit_0, lit_1, lit_2, lit_3]);
assert_eq!(result, expected);
}
#[test]
fn test_distributive_multiple_arrays() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_nested_array = interner.array(interner.array(infer_r));
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_nested_array,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let nested_string_array = interner.array(interner.array(TypeId::STRING));
let nested_number_array = interner.array(interner.array(TypeId::NUMBER));
subst.insert(
t_name,
interner.union(vec![
nested_string_array,
nested_number_array,
TypeId::BOOLEAN,
]),
);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert_eq!(result, expected);
}
#[test]
fn test_distributive_keyof_filter() {
let interner = TypeInterner::new();
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 lit_a = interner.literal_string("a");
let lit_b = interner.literal_string("b");
let lit_1 = interner.literal_number(1.0);
let keyof_any = interner.union(vec![TypeId::STRING, TypeId::NUMBER, TypeId::SYMBOL]);
let cond = ConditionalType {
check_type: t_param,
extends_type: keyof_any,
true_type: t_param,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
subst.insert(
t_name,
interner.union(vec![lit_a, lit_b, lit_1, TypeId::SYMBOL]),
);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![lit_a, lit_b, lit_1, TypeId::SYMBOL]);
assert_eq!(result, expected);
}
#[test]
fn test_distributive_mixed_primitive_union() {
let interner = TypeInterner::new();
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 lit_primitive = interner.literal_string("primitive");
let lit_other = interner.literal_string("other");
let lit_a = interner.literal_string("a");
let lit_1 = interner.literal_number(1.0);
let lit_true = interner.literal_boolean(true);
let empty_obj = interner.object(Vec::new());
let string_or_boolean = interner.union(vec![TypeId::STRING, TypeId::BOOLEAN]);
let cond = ConditionalType {
check_type: t_param,
extends_type: string_or_boolean,
true_type: lit_primitive,
false_type: lit_other,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
subst.insert(
t_name,
interner.union(vec![
lit_a,
lit_1,
lit_true,
TypeId::NULL,
TypeId::UNDEFINED,
empty_obj,
]),
);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![lit_primitive, lit_other]);
assert_eq!(result, expected);
}
#[test]
fn test_distributive_very_large_union() {
let interner = TypeInterner::new();
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 lit_yes = interner.literal_string("yes");
let lit_no = interner.literal_string("no");
let cond = ConditionalType {
check_type: t_param,
extends_type: TypeId::STRING,
true_type: lit_yes,
false_type: lit_no,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let members: Vec<TypeId> = (0..50)
.map(|i| {
if i < 25 {
interner.literal_string(&format!("str{i}"))
} else {
interner.literal_number(i as f64)
}
})
.collect();
subst.insert(t_name, interner.union(members));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![lit_yes, lit_no]);
assert_eq!(result, expected);
}
#[test]
fn test_distributive_all_to_same_result() {
let interner = TypeInterner::new();
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 lit_primitive = interner.literal_string("primitive");
let lit_other = interner.literal_string("other");
let lit_a = interner.literal_string("a");
let lit_1 = interner.literal_number(1.0);
let lit_true = interner.literal_boolean(true);
let primitives = interner.union(vec![TypeId::STRING, TypeId::NUMBER, TypeId::BOOLEAN]);
let cond = ConditionalType {
check_type: t_param,
extends_type: primitives,
true_type: lit_primitive,
false_type: lit_other,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
subst.insert(t_name, interner.union(vec![lit_a, lit_1, lit_true]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, lit_primitive);
}
#[test]
fn test_distributive_identity_preservation() {
let interner = TypeInterner::new();
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 lit_a = interner.literal_string("a");
let lit_1 = interner.literal_number(1.0);
let lit_true = interner.literal_boolean(true);
let cond = ConditionalType {
check_type: t_param,
extends_type: TypeId::ANY,
true_type: t_param,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let input = interner.union(vec![lit_a, lit_1, lit_true]);
subst.insert(t_name, input);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, input);
}
#[test]
fn test_distributive_two_infers_different_positions() {
let interner = TypeInterner::new();
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 infer_a_name = interner.intern_string("A");
let infer_a = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_a_name,
constraint: None,
default: None,
is_const: false,
}));
let infer_b_name = interner.intern_string("B");
let infer_b = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_b_name,
constraint: None,
default: None,
is_const: false,
}));
let prop_a = interner.intern_string("a");
let prop_b = interner.intern_string("b");
let extends_obj = interner.object(vec![
PropertyInfo::new(prop_a, infer_a),
PropertyInfo::new(prop_b, infer_b),
]);
let result_tuple = interner.tuple(vec![
TupleElement {
type_id: infer_a,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: infer_b,
name: None,
optional: false,
rest: false,
},
]);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_obj,
true_type: result_tuple,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let obj1 = interner.object(vec![
PropertyInfo::new(prop_a, TypeId::STRING),
PropertyInfo::new(prop_b, TypeId::NUMBER),
]);
let obj2 = interner.object(vec![
PropertyInfo::new(prop_a, TypeId::BOOLEAN),
PropertyInfo::new(prop_b, TypeId::SYMBOL),
]);
subst.insert(t_name, interner.union(vec![obj1, obj2]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
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::BOOLEAN,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::SYMBOL,
name: None,
optional: false,
rest: false,
},
]);
let expected = interner.union(vec![tuple1, tuple2]);
assert_eq!(result, expected);
}
#[test]
fn test_distributive_infer_return_type() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern_fn = interner.function(FunctionShape {
params: Vec::new(),
this_type: None,
return_type: infer_r,
type_params: Vec::new(),
type_predicate: None,
is_constructor: false,
is_method: false,
});
let cond = ConditionalType {
check_type: t_param,
extends_type: pattern_fn,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let fn_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 fn_number = interner.function(FunctionShape {
params: Vec::new(),
this_type: None,
return_type: TypeId::NUMBER,
type_params: Vec::new(),
type_predicate: None,
is_constructor: false,
is_method: false,
});
subst.insert(
t_name,
interner.union(vec![fn_string, fn_number, TypeId::STRING]),
);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert_eq!(result, expected);
}
#[test]
fn test_distributive_union_of_unions() {
let interner = TypeInterner::new();
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 lit_1 = interner.literal_number(1.0);
let lit_2 = interner.literal_number(2.0);
let lit_a = interner.literal_string("a");
let lit_b = interner.literal_string("b");
let cond = ConditionalType {
check_type: t_param,
extends_type: TypeId::STRING,
true_type: lit_1,
false_type: lit_2,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let strings = interner.union(vec![lit_a, lit_b]);
let numbers = interner.union(vec![
interner.literal_number(10.0),
interner.literal_number(20.0),
]);
subst.insert(t_name, interner.union(vec![strings, numbers]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![lit_1, lit_2]);
assert_eq!(result, expected);
}
#[test]
fn test_distributive_boolean_literals() {
let interner = TypeInterner::new();
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 lit_true = interner.literal_boolean(true);
let lit_false = interner.literal_boolean(false);
let lit_yes = interner.literal_string("yes");
let lit_no = interner.literal_string("no");
let lit_other = interner.literal_string("other");
let inner = interner.conditional(ConditionalType {
check_type: t_param,
extends_type: lit_false,
true_type: lit_no,
false_type: lit_other,
is_distributive: false,
});
let outer = ConditionalType {
check_type: t_param,
extends_type: lit_true,
true_type: lit_yes,
false_type: inner,
is_distributive: true,
};
let cond_type = interner.conditional(outer);
let mut subst = TypeSubstitution::new();
subst.insert(
t_name,
interner.union(vec![lit_true, lit_false, TypeId::NULL]),
);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![lit_yes, lit_no, lit_other]);
assert_eq!(result, expected);
}
#[test]
fn test_distributive_with_unknown() {
let interner = TypeInterner::new();
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 cond = ConditionalType {
check_type: t_param,
extends_type: TypeId::UNKNOWN,
true_type: t_param,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let input = interner.union(vec![TypeId::STRING, TypeId::NUMBER, TypeId::NULL]);
subst.insert(t_name, input);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, input);
}
#[test]
fn test_distributive_partial_object_match() {
let interner = TypeInterner::new();
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 x_atom = interner.intern_string("x");
let y_atom = interner.intern_string("y");
let lit_no_x = interner.literal_string("no-x");
let extends_obj = interner.object(vec![PropertyInfo::new(x_atom, TypeId::ANY)]);
let index_access =
interner.intern(TypeData::IndexAccess(t_param, interner.literal_string("x")));
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_obj,
true_type: index_access,
false_type: lit_no_x,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let obj1 = interner.object(vec![PropertyInfo::new(x_atom, TypeId::STRING)]);
let obj2 = interner.object(vec![PropertyInfo::new(y_atom, TypeId::NUMBER)]);
let obj3 = interner.object(vec![
PropertyInfo::new(x_atom, TypeId::BOOLEAN),
PropertyInfo::new(y_atom, TypeId::SYMBOL),
]);
subst.insert(t_name, interner.union(vec![obj1, obj2, obj3]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![TypeId::STRING, lit_no_x, TypeId::BOOLEAN]);
assert_eq!(result, expected);
}
#[test]
fn test_distributive_hundred_member_union() {
let interner = TypeInterner::new();
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 lit_match = interner.literal_string("match");
let lit_no_match = interner.literal_string("no-match");
let cond = ConditionalType {
check_type: t_param,
extends_type: TypeId::STRING,
true_type: lit_match,
false_type: lit_no_match,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let members: Vec<TypeId> = (0..100)
.map(|i| {
if i < 50 {
interner.literal_string(&format!("s{i}"))
} else {
interner.literal_number(i as f64)
}
})
.collect();
subst.insert(t_name, interner.union(members));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![lit_match, lit_no_match]);
assert_eq!(result, expected);
}
#[test]
fn test_distributive_triple_nested_conditional() {
let interner = TypeInterner::new();
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 lit_a = interner.literal_string("a");
let lit_b = interner.literal_string("b");
let lit_c = interner.literal_string("c");
let lit_d = interner.literal_string("d");
let lit_e = interner.literal_string("e");
let lit_0 = interner.literal_number(0.0);
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 lit_4 = interner.literal_number(4.0);
let cond4 = interner.conditional(ConditionalType {
check_type: t_param,
extends_type: lit_d,
true_type: lit_4,
false_type: lit_0,
is_distributive: false,
});
let cond3 = interner.conditional(ConditionalType {
check_type: t_param,
extends_type: lit_c,
true_type: lit_3,
false_type: cond4,
is_distributive: false,
});
let cond2 = interner.conditional(ConditionalType {
check_type: t_param,
extends_type: lit_b,
true_type: lit_2,
false_type: cond3,
is_distributive: false,
});
let outer = ConditionalType {
check_type: t_param,
extends_type: lit_a,
true_type: lit_1,
false_type: cond2,
is_distributive: true,
};
let cond_type = interner.conditional(outer);
let mut subst = TypeSubstitution::new();
subst.insert(
t_name,
interner.union(vec![lit_a, lit_b, lit_c, lit_d, lit_e]),
);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![lit_0, lit_1, lit_2, lit_3, lit_4]);
assert_eq!(result, expected);
}
#[test]
fn test_distributive_no_false_branch_matches() {
let interner = TypeInterner::new();
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 cond = ConditionalType {
check_type: t_param,
extends_type: TypeId::STRING,
true_type: t_param,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let lit_1 = interner.literal_number(1.0);
let lit_2 = interner.literal_number(2.0);
let lit_3 = interner.literal_number(3.0);
subst.insert(t_name, interner.union(vec![lit_1, lit_2, lit_3]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_distributive_empty_object_match() {
let interner = TypeInterner::new();
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 lit_object_like = interner.literal_string("object-like");
let lit_primitive = interner.literal_string("primitive");
let x_atom = interner.intern_string("x");
let empty_obj = interner.object(Vec::new());
let cond = ConditionalType {
check_type: t_param,
extends_type: empty_obj,
true_type: lit_object_like,
false_type: lit_primitive,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let obj_x = interner.object(vec![PropertyInfo::new(
x_atom,
interner.literal_number(1.0),
)]);
subst.insert(
t_name,
interner.union(vec![TypeId::STRING, TypeId::NUMBER, obj_x, TypeId::NULL]),
);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![lit_object_like, lit_primitive]);
assert_eq!(result, expected);
}
#[test]
fn test_distributive_literal_type_filter() {
let interner = TypeInterner::new();
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 lit_a = interner.literal_string("a");
let lit_b = interner.literal_string("b");
let lit_c = interner.literal_string("c");
let lit_d = interner.literal_string("d");
let lit_e = interner.literal_string("e");
let allowed = interner.union(vec![lit_a, lit_b, lit_c]);
let cond = ConditionalType {
check_type: t_param,
extends_type: allowed,
true_type: t_param,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
subst.insert(
t_name,
interner.union(vec![lit_a, lit_b, lit_c, lit_d, lit_e]),
);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![lit_a, lit_b, lit_c]);
assert_eq!(result, expected);
}
#[test]
fn test_distributive_numeric_literal_filter() {
let interner = TypeInterner::new();
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 lit_low = interner.literal_string("low");
let lit_mid = interner.literal_string("mid");
let lit_high = interner.literal_string("high");
let low_set = interner.union(vec![
interner.literal_number(1.0),
interner.literal_number(2.0),
interner.literal_number(3.0),
]);
let mid_set = interner.union(vec![
interner.literal_number(4.0),
interner.literal_number(5.0),
interner.literal_number(6.0),
]);
let inner = interner.conditional(ConditionalType {
check_type: t_param,
extends_type: mid_set,
true_type: lit_mid,
false_type: lit_high,
is_distributive: false,
});
let outer = ConditionalType {
check_type: t_param,
extends_type: low_set,
true_type: lit_low,
false_type: inner,
is_distributive: true,
};
let cond_type = interner.conditional(outer);
let mut subst = TypeSubstitution::new();
subst.insert(
t_name,
interner.union(vec![
interner.literal_number(1.0),
interner.literal_number(2.0),
interner.literal_number(5.0),
interner.literal_number(7.0),
interner.literal_number(10.0),
]),
);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![lit_low, lit_mid, lit_high]);
assert_eq!(result, expected);
}
#[test]
fn test_distributive_with_void() {
let interner = TypeInterner::new();
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 lit_void = interner.literal_string("void");
let lit_not_void = interner.literal_string("not-void");
let cond = ConditionalType {
check_type: t_param,
extends_type: TypeId::VOID,
true_type: lit_void,
false_type: lit_not_void,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
subst.insert(
t_name,
interner.union(vec![TypeId::VOID, TypeId::STRING, TypeId::UNDEFINED]),
);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![lit_void, lit_not_void]);
assert_eq!(result, expected);
}
#[test]
fn test_distributive_chained_conditionals() {
let interner = TypeInterner::new();
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 lit_str = interner.literal_string("str");
let lit_num = interner.literal_string("num");
let lit_other = interner.literal_string("other");
let inner_cond = ConditionalType {
check_type: t_param,
extends_type: TypeId::NUMBER,
true_type: lit_num,
false_type: lit_other,
is_distributive: true,
};
let inner = interner.conditional(inner_cond);
let outer_cond = ConditionalType {
check_type: t_param,
extends_type: TypeId::STRING,
true_type: lit_str,
false_type: inner,
is_distributive: true,
};
let outer = interner.conditional(outer_cond);
let mut subst = TypeSubstitution::new();
subst.insert(
t_name,
interner.union(vec![TypeId::STRING, TypeId::NUMBER, TypeId::BOOLEAN]),
);
let instantiated = instantiate_type(&interner, outer, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![lit_str, lit_num, lit_other]);
assert_eq!(result, expected);
}
#[test]
fn test_distributive_with_intersection_check() {
let interner = TypeInterner::new();
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 a_prop = interner.intern_string("a");
let b_prop = interner.intern_string("b");
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 extends_type = interner.intersection(vec![obj_a, obj_b]);
let lit_match = interner.literal_string("match");
let lit_no_match = interner.literal_string("no-match");
let cond = ConditionalType {
check_type: t_param,
extends_type,
true_type: lit_match,
false_type: lit_no_match,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let obj_ab = interner.object(vec![
PropertyInfo::new(a_prop, TypeId::STRING),
PropertyInfo::new(b_prop, TypeId::NUMBER),
]);
let mut subst = TypeSubstitution::new();
subst.insert(t_name, interner.union(vec![obj_ab, TypeId::STRING]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![lit_match, lit_no_match]);
assert_eq!(result, expected);
}
#[test]
fn test_distributive_with_bigint_literals() {
let interner = TypeInterner::new();
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 lit_bigint = interner.literal_string("bigint");
let lit_not = interner.literal_string("not-bigint");
let cond = ConditionalType {
check_type: t_param,
extends_type: TypeId::BIGINT,
true_type: lit_bigint,
false_type: lit_not,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
subst.insert(
t_name,
interner.union(vec![TypeId::BIGINT, TypeId::NUMBER, TypeId::STRING]),
);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![lit_bigint, lit_not]);
assert_eq!(result, expected);
}
#[test]
fn test_distributive_filter_nullables() {
let interner = TypeInterner::new();
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 nullish = interner.union(vec![TypeId::NULL, TypeId::UNDEFINED]);
let cond = ConditionalType {
check_type: t_param,
extends_type: nullish,
true_type: TypeId::NEVER,
false_type: t_param,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
subst.insert(
t_name,
interner.union(vec![
TypeId::STRING,
TypeId::NULL,
TypeId::NUMBER,
TypeId::UNDEFINED,
]),
);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert_eq!(result, expected);
}
#[test]
fn test_distributive_with_symbol() {
let interner = TypeInterner::new();
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 lit_sym = interner.literal_string("symbol");
let lit_not = interner.literal_string("not-symbol");
let cond = ConditionalType {
check_type: t_param,
extends_type: TypeId::SYMBOL,
true_type: lit_sym,
false_type: lit_not,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
subst.insert(t_name, interner.union(vec![TypeId::SYMBOL, TypeId::STRING]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![lit_sym, lit_not]);
assert_eq!(result, expected);
}
#[test]
fn test_distributive_with_object_keyword() {
let interner = TypeInterner::new();
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 lit_obj = interner.literal_string("object");
let lit_prim = interner.literal_string("primitive");
let cond = ConditionalType {
check_type: t_param,
extends_type: TypeId::OBJECT,
true_type: lit_obj,
false_type: lit_prim,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let obj = interner.object(vec![PropertyInfo::new(
interner.intern_string("x"),
TypeId::NUMBER,
)]);
let mut subst = TypeSubstitution::new();
subst.insert(
t_name,
interner.union(vec![obj, TypeId::STRING, TypeId::NUMBER]),
);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![lit_obj, lit_prim]);
assert_eq!(result, expected);
}
#[test]
fn test_distributive_infer_with_fallback() {
let interner = TypeInterner::new();
let t_name = interner.intern_string("T");
let v_name = interner.intern_string("V");
let t_param = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: t_name,
constraint: None,
default: None,
is_const: false,
}));
let v_infer = interner.intern(TypeData::Infer(TypeParamInfo {
name: v_name,
constraint: None,
default: None,
is_const: false,
}));
let value_prop = interner.intern_string("value");
let extends_obj = interner.object(vec![PropertyInfo::new(value_prop, v_infer)]);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_obj,
true_type: v_infer,
false_type: t_param,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let obj_with_value = interner.object(vec![PropertyInfo::new(value_prop, TypeId::NUMBER)]);
let mut subst = TypeSubstitution::new();
subst.insert(t_name, interner.union(vec![obj_with_value, TypeId::STRING]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![TypeId::NUMBER, TypeId::STRING]);
assert_eq!(result, expected);
}
#[test]
fn test_distributive_tuple_check() {
let interner = TypeInterner::new();
let t_name = interner.intern_string("T");
let first_name = interner.intern_string("First");
let rest_name = interner.intern_string("Rest");
let t_param = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: t_name,
constraint: None,
default: None,
is_const: false,
}));
let first_infer = interner.intern(TypeData::Infer(TypeParamInfo {
name: first_name,
constraint: None,
default: None,
is_const: false,
}));
let rest_infer = interner.intern(TypeData::Infer(TypeParamInfo {
name: rest_name,
constraint: None,
default: None,
is_const: false,
}));
let extends_tuple = interner.tuple(vec![
TupleElement {
type_id: first_infer,
optional: false,
name: None,
rest: false,
},
TupleElement {
type_id: rest_infer,
optional: false,
name: None,
rest: true,
},
]);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_tuple,
true_type: first_infer,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let tuple1 = 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 tuple2 = interner.tuple(vec![TupleElement {
type_id: TypeId::BOOLEAN,
optional: false,
name: None,
rest: false,
}]);
let mut subst = TypeSubstitution::new();
subst.insert(t_name, interner.union(vec![tuple1, tuple2, TypeId::STRING]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![TypeId::STRING, TypeId::BOOLEAN]);
assert_eq!(result, expected);
}
#[test]
fn test_distributive_with_literal_numbers() {
let interner = TypeInterner::new();
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 one = interner.literal_number(1.0);
let two = interner.literal_number(2.0);
let three = interner.literal_number(3.0);
let four = interner.literal_number(4.0);
let five = interner.literal_number(5.0);
let low_set = interner.union(vec![one, two, three]);
let lit_low = interner.literal_string("low");
let lit_high = interner.literal_string("high");
let cond = ConditionalType {
check_type: t_param,
extends_type: low_set,
true_type: lit_low,
false_type: lit_high,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
subst.insert(t_name, interner.union(vec![one, two, four, five]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![lit_low, lit_high]);
assert_eq!(result, expected);
}
#[test]
fn test_distributive_with_boolean_literal_union() {
let interner = TypeInterner::new();
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 lit_true = interner.literal_boolean(true);
let lit_false = interner.literal_boolean(false);
let lit_yes = interner.literal_string("yes");
let lit_no = interner.literal_string("no");
let cond = ConditionalType {
check_type: t_param,
extends_type: lit_true,
true_type: lit_yes,
false_type: lit_no,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
subst.insert(t_name, interner.union(vec![lit_true, lit_false]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![lit_yes, lit_no]);
assert_eq!(result, expected);
}
#[test]
fn test_distributive_readonly_array_unwrap() {
let interner = TypeInterner::new();
let t_name = interner.intern_string("T");
let u_name = interner.intern_string("U");
let t_param = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: t_name,
constraint: None,
default: None,
is_const: false,
}));
let u_infer = interner.intern(TypeData::Infer(TypeParamInfo {
name: u_name,
constraint: None,
default: None,
is_const: false,
}));
let readonly_array = interner.intern(TypeData::ReadonlyType(interner.array(u_infer)));
let cond = ConditionalType {
check_type: t_param,
extends_type: readonly_array,
true_type: u_infer,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let string_array = interner.array(TypeId::STRING);
let number_array = interner.array(TypeId::NUMBER);
let readonly_string = interner.intern(TypeData::ReadonlyType(string_array));
let readonly_number = interner.intern(TypeData::ReadonlyType(number_array));
let mut subst = TypeSubstitution::new();
subst.insert(
t_name,
interner.union(vec![readonly_string, readonly_number, TypeId::STRING]),
);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert_eq!(result, expected);
}
#[test]
fn test_distributive_promise_like_unwrap() {
let interner = TypeInterner::new();
let t_name = interner.intern_string("T");
let v_name = interner.intern_string("V");
let t_param = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: t_name,
constraint: None,
default: None,
is_const: false,
}));
let v_infer = interner.intern(TypeData::Infer(TypeParamInfo {
name: v_name,
constraint: None,
default: None,
is_const: false,
}));
let callback = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("value")),
type_id: v_infer,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::ANY,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let then_method = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("onfulfilled")),
type_id: callback,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::ANY,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let then_prop = interner.intern_string("then");
let extends_obj = interner.object(vec![PropertyInfo::method(then_prop, then_method)]);
let cond = ConditionalType {
check_type: t_param,
extends_type: extends_obj,
true_type: v_infer,
false_type: t_param,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let string_callback = 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::ANY,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let string_then = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("onfulfilled")),
type_id: string_callback,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::ANY,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let promise_string = interner.object(vec![PropertyInfo::method(then_prop, string_then)]);
let mut subst = TypeSubstitution::new();
subst.insert(t_name, interner.union(vec![promise_string, TypeId::NUMBER]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert_eq!(result, expected);
}
#[test]
fn test_return_type_async_promise_unwrapping() {
let interner = TypeInterner::new();
let promise_string = interner.object(vec![PropertyInfo::method(
interner.intern_string("then"),
TypeId::ANY,
)]);
let async_func = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: promise_string,
type_predicate: None,
is_constructor: false,
is_method: false,
});
match interner.lookup(async_func) {
Some(TypeData::Function(shape_id)) => {
let shape = interner.function_shape(shape_id);
assert_eq!(shape.return_type, promise_string);
}
_ => panic!("Expected Function type"),
}
}
#[test]
fn test_return_type_void_function() {
let interner = TypeInterner::new();
let void_func = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
match interner.lookup(void_func) {
Some(TypeData::Function(shape_id)) => {
let shape = interner.function_shape(shape_id);
assert_eq!(shape.return_type, TypeId::VOID);
}
_ => panic!("Expected Function type"),
}
}
#[test]
fn test_return_type_never_function() {
let interner = TypeInterner::new();
let never_func = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::NEVER,
type_predicate: None,
is_constructor: false,
is_method: false,
});
match interner.lookup(never_func) {
Some(TypeData::Function(shape_id)) => {
let shape = interner.function_shape(shape_id);
assert_eq!(shape.return_type, TypeId::NEVER);
}
_ => panic!("Expected Function type"),
}
}
#[test]
fn test_return_type_union_of_functions() {
let interner = TypeInterner::new();
let func_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 func_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 union_funcs = interner.union(vec![func_string, func_number]);
match interner.lookup(union_funcs) {
Some(TypeData::Union(list_id)) => {
let members = interner.type_list(list_id);
assert_eq!(members.len(), 2);
for member in members.iter() {
match interner.lookup(*member) {
Some(TypeData::Function(_)) => {}
_ => panic!("Expected Function in union"),
}
}
}
_ => panic!("Expected Union type"),
}
}
#[test]
fn test_return_type_conditional_return() {
let interner = TypeInterner::new();
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 cond_return = interner.conditional(ConditionalType {
check_type: t_param,
extends_type: TypeId::STRING,
true_type: TypeId::NUMBER,
false_type: TypeId::BOOLEAN,
is_distributive: false,
});
let generic_func = interner.function(FunctionShape {
type_params: vec![TypeParamInfo {
name: t_name,
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: cond_return,
type_predicate: None,
is_constructor: false,
is_method: false,
});
match interner.lookup(generic_func) {
Some(TypeData::Function(shape_id)) => {
let shape = interner.function_shape(shape_id);
match interner.lookup(shape.return_type) {
Some(TypeData::Conditional(_)) => {}
_ => panic!("Expected Conditional return type"),
}
}
_ => panic!("Expected Function type"),
}
}
#[test]
fn test_return_type_constructor_signature() {
let interner = TypeInterner::new();
let instance_type = interner.object(vec![PropertyInfo::new(
interner.intern_string("value"),
TypeId::STRING,
)]);
let ctor = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![],
construct_signatures: vec![CallSignature {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("initial")),
type_id: TypeId::STRING,
optional: false,
rest: false,
}],
this_type: None,
return_type: instance_type,
type_predicate: None,
is_method: false,
}],
properties: vec![],
string_index: None,
number_index: None,
});
match interner.lookup(ctor) {
Some(TypeData::Callable(shape_id)) => {
let shape = interner.callable_shape(shape_id);
assert_eq!(shape.construct_signatures.len(), 1);
assert_eq!(shape.construct_signatures[0].return_type, instance_type);
}
_ => panic!("Expected Callable type"),
}
}
#[test]
fn test_parameters_this_parameter() {
let interner = TypeInterner::new();
let window_type = interner.object(vec![PropertyInfo::new(
interner.intern_string("location"),
TypeId::STRING,
)]);
let func_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(window_type), return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
match interner.lookup(func_with_this) {
Some(TypeData::Function(shape_id)) => {
let shape = interner.function_shape(shape_id);
assert_eq!(shape.params.len(), 1);
assert_eq!(shape.params[0].type_id, TypeId::STRING);
assert!(shape.this_type.is_some());
}
_ => panic!("Expected Function type"),
}
}
#[test]
fn test_parameters_labeled_tuple_elements() {
let interner = TypeInterner::new();
let first_name = interner.intern_string("first");
let second_name = interner.intern_string("second");
let params_tuple = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: Some(first_name),
optional: false,
rest: false,
},
TupleElement {
type_id: TypeId::NUMBER,
name: Some(second_name),
optional: false,
rest: false,
},
]);
match interner.lookup(params_tuple) {
Some(TypeData::Tuple(list_id)) => {
let elements = interner.tuple_list(list_id);
assert_eq!(elements[0].name, Some(first_name));
assert_eq!(elements[1].name, Some(second_name));
}
_ => panic!("Expected Tuple type"),
}
}
#[test]
fn test_parameters_multiple_optional() {
let interner = TypeInterner::new();
let params_tuple = interner.tuple(vec![
TupleElement {
type_id: TypeId::STRING,
name: Some(interner.intern_string("a")),
optional: true,
rest: false,
},
TupleElement {
type_id: TypeId::NUMBER,
name: Some(interner.intern_string("b")),
optional: true,
rest: false,
},
TupleElement {
type_id: TypeId::BOOLEAN,
name: Some(interner.intern_string("c")),
optional: true,
rest: false,
},
]);
match interner.lookup(params_tuple) {
Some(TypeData::Tuple(list_id)) => {
let elements = interner.tuple_list(list_id);
assert_eq!(elements.len(), 3);
assert!(elements.iter().all(|e| e.optional));
}
_ => panic!("Expected Tuple type"),
}
}
#[test]
fn test_parameters_rest_with_tuple_type() {
let interner = TypeInterner::new();
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,
},
TupleElement {
type_id: TypeId::BOOLEAN,
name: None,
optional: false,
rest: false,
},
]);
match interner.lookup(rest_tuple) {
Some(TypeData::Tuple(list_id)) => {
let elements = interner.tuple_list(list_id);
assert_eq!(elements.len(), 3);
assert_eq!(elements[0].type_id, TypeId::STRING);
assert_eq!(elements[1].type_id, TypeId::NUMBER);
assert_eq!(elements[2].type_id, TypeId::BOOLEAN);
}
_ => panic!("Expected Tuple type"),
}
}
#[test]
fn test_parameters_constructor_signature() {
let interner = TypeInterner::new();
let instance_type = interner.object(vec![]);
let 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,
},
ParamInfo {
name: Some(interner.intern_string("y")),
type_id: TypeId::NUMBER,
optional: true,
rest: false,
},
],
this_type: None,
return_type: instance_type,
type_predicate: None,
is_method: false,
}],
properties: vec![],
string_index: None,
number_index: None,
});
match interner.lookup(ctor) {
Some(TypeData::Callable(shape_id)) => {
let shape = interner.callable_shape(shape_id);
let params = &shape.construct_signatures[0].params;
assert_eq!(params.len(), 2);
assert_eq!(params[0].type_id, TypeId::STRING);
assert!(!params[0].optional);
assert_eq!(params[1].type_id, TypeId::NUMBER);
assert!(params[1].optional);
}
_ => panic!("Expected Callable type"),
}
}
#[test]
fn test_return_type_with_infer_in_conditional() {
let interner = TypeInterner::new();
let t_name = interner.intern_string("T");
let r_name = interner.intern_string("R");
let _t_param = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: t_name,
constraint: None,
default: None,
is_const: false,
}));
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: r_name,
constraint: None,
default: None,
is_const: false,
}));
let _any_array = interner.array(TypeId::ANY);
let func_pattern = 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: infer_r,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let string_func = 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 substituted = ConditionalType {
check_type: string_func,
extends_type: func_pattern,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &substituted);
assert!(result != TypeId::ERROR);
}
#[test]
fn test_parameters_with_infer_in_conditional() {
let interner = TypeInterner::new();
let p_name = interner.intern_string("P");
let infer_p = interner.intern(TypeData::Infer(TypeParamInfo {
name: p_name,
constraint: None,
default: None,
is_const: false,
}));
let func_pattern = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("args")),
type_id: infer_p, optional: false,
rest: true,
}],
this_type: None,
return_type: TypeId::ANY,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let test_func = 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 cond = ConditionalType {
check_type: test_func,
extends_type: func_pattern,
true_type: infer_p,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result != TypeId::ERROR);
}
#[test]
fn test_return_type_generic_with_constraint() {
let interner = TypeInterner::new();
let t_name = interner.intern_string("T");
let func_type = 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: TypeId::ANY,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let t_param = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: t_name,
constraint: Some(func_type),
default: None,
is_const: false,
}));
match interner.lookup(t_param) {
Some(TypeData::TypeParameter(info)) => {
assert!(info.constraint.is_some());
}
_ => panic!("Expected TypeParameter"),
}
}
#[test]
fn test_parameters_variadic_tuple_type() {
let interner = TypeInterner::new();
let t_name = interner.intern_string("T");
let t_array = interner.array(TypeId::ANY);
let t_param = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: t_name,
constraint: Some(t_array),
default: None,
is_const: false,
}));
let variadic_tuple = interner.tuple(vec![
TupleElement {
type_id: t_param,
name: None,
optional: false,
rest: true, },
TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
},
]);
match interner.lookup(variadic_tuple) {
Some(TypeData::Tuple(list_id)) => {
let elements = interner.tuple_list(list_id);
assert_eq!(elements.len(), 2);
assert!(elements[0].rest);
assert!(!elements[1].rest);
}
_ => panic!("Expected Tuple type"),
}
}
#[test]
fn test_return_type_intersection_of_functions() {
let interner = TypeInterner::new();
let func_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 func_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 intersection = interner.intersection(vec![func_string, func_number]);
match interner.lookup(intersection) {
Some(TypeData::Callable(shape_id)) => {
let shape = interner.callable_shape(shape_id);
assert_eq!(
shape.call_signatures.len(),
2,
"Should have both call signatures"
);
let return_types: Vec<TypeId> = shape
.call_signatures
.iter()
.map(|sig| sig.return_type)
.collect();
assert!(return_types.contains(&TypeId::STRING));
assert!(return_types.contains(&TypeId::NUMBER));
}
_ => panic!("Expected Callable type with overloaded signatures"),
}
}
#[test]
fn test_parameters_union_of_functions_with_different_arities() {
let interner = TypeInterner::new();
let func1 = 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 func2 = 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 union_funcs = interner.union(vec![func1, func2]);
match interner.lookup(union_funcs) {
Some(TypeData::Union(list_id)) => {
let members = interner.type_list(list_id);
assert_eq!(members.len(), 2);
}
_ => panic!("Expected Union type"),
}
}
#[test]
fn test_return_type_mapped_type_method() {
let interner = TypeInterner::new();
let t_name = interner.intern_string("T");
let k_name = interner.intern_string("K");
let t_param = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: t_name,
constraint: None,
default: None,
is_const: false,
}));
let keyof_t = interner.intern(TypeData::KeyOf(t_param));
let k_param_info = TypeParamInfo {
name: k_name,
constraint: Some(keyof_t),
default: None,
is_const: false,
};
let k_param = interner.intern(TypeData::TypeParameter(k_param_info.clone()));
let index_access = interner.intern(TypeData::IndexAccess(t_param, k_param));
let mapped = MappedType {
type_param: k_param_info,
constraint: keyof_t,
name_type: None,
template: index_access, readonly_modifier: None,
optional_modifier: None,
};
let result = evaluate_mapped(&interner, &mapped);
assert!(result != TypeId::ERROR);
}
#[test]
fn test_this_parameter_type_extraction() {
let interner = TypeInterner::new();
let window_type = interner.object(vec![
PropertyInfo::readonly(interner.intern_string("document"), TypeId::ANY),
PropertyInfo::new(interner.intern_string("location"), TypeId::STRING),
]);
let func_with_this = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: Some(window_type),
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
match interner.lookup(func_with_this) {
Some(TypeData::Function(shape_id)) => {
let shape = interner.function_shape(shape_id);
assert_eq!(shape.this_type, Some(window_type));
}
_ => panic!("Expected Function type"),
}
}
#[test]
fn test_omit_this_parameter() {
let interner = TypeInterner::new();
let window_type = interner.object(vec![PropertyInfo::new(
interner.intern_string("location"),
TypeId::STRING,
)]);
let func_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(window_type),
return_type: TypeId::VOID,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let func_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,
});
match interner.lookup(func_with_this) {
Some(TypeData::Function(with_id)) => {
let with_shape = interner.function_shape(with_id);
match interner.lookup(func_without_this) {
Some(TypeData::Function(without_id)) => {
let without_shape = interner.function_shape(without_id);
assert_eq!(with_shape.params.len(), without_shape.params.len());
assert!(with_shape.this_type.is_some());
assert!(without_shape.this_type.is_none());
}
_ => panic!("Expected Function type"),
}
}
_ => panic!("Expected Function type"),
}
}
#[test]
fn test_instance_type_from_constructor() {
let interner = TypeInterner::new();
let get_value_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_type = interner.object(vec![
PropertyInfo::new(interner.intern_string("value"), TypeId::STRING),
PropertyInfo::method(interner.intern_string("getValue"), get_value_method),
]);
let ctor = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![],
construct_signatures: vec![CallSignature {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("initial")),
type_id: TypeId::STRING,
optional: false,
rest: false,
}],
this_type: None,
return_type: instance_type,
type_predicate: None,
is_method: false,
}],
properties: vec![],
string_index: None,
number_index: None,
});
match interner.lookup(ctor) {
Some(TypeData::Callable(shape_id)) => {
let shape = interner.callable_shape(shape_id);
assert_eq!(shape.construct_signatures.len(), 1);
let extracted_instance = shape.construct_signatures[0].return_type;
assert_eq!(extracted_instance, instance_type);
}
_ => panic!("Expected Callable type"),
}
}
#[test]
fn test_constructor_parameters_with_generics() {
let interner = TypeInterner::new();
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 container = interner.object(vec![PropertyInfo::new(
interner.intern_string("value"),
t_param,
)]);
let generic_ctor = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![],
construct_signatures: vec![CallSignature {
type_params: vec![TypeParamInfo {
name: t_name,
constraint: None,
default: None,
is_const: false,
}],
params: vec![ParamInfo {
name: Some(interner.intern_string("value")),
type_id: t_param,
optional: false,
rest: false,
}],
this_type: None,
return_type: container,
type_predicate: None,
is_method: false,
}],
properties: vec![],
string_index: None,
number_index: None,
});
match interner.lookup(generic_ctor) {
Some(TypeData::Callable(shape_id)) => {
let shape = interner.callable_shape(shape_id);
let sig = &shape.construct_signatures[0];
assert_eq!(sig.type_params.len(), 1);
assert_eq!(sig.type_params[0].name, t_name);
assert_eq!(sig.params.len(), 1);
assert_eq!(sig.params[0].type_id, t_param);
}
_ => panic!("Expected Callable type"),
}
}
#[test]
fn test_awaited_with_nested_promises() {
let interner = TypeInterner::new();
let inner_then = 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 inner_promise = interner.object(vec![PropertyInfo::method(
interner.intern_string("then"),
inner_then,
)]);
let outer_then = interner.function(FunctionShape {
type_params: vec![],
params: vec![],
this_type: None,
return_type: inner_promise,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let outer_promise = interner.object(vec![PropertyInfo::method(
interner.intern_string("then"),
outer_then,
)]);
match interner.lookup(outer_promise) {
Some(TypeData::Object(shape_id)) => {
let shape = interner.object_shape(shape_id);
assert!(!shape.properties.is_empty());
}
_ => panic!("Expected Object type"),
}
}
#[test]
fn test_readonly_array_type() {
let interner = TypeInterner::new();
let readonly_arr = interner.array(TypeId::STRING);
match interner.lookup(readonly_arr) {
Some(TypeData::Array(element)) => {
assert_eq!(element, TypeId::STRING);
}
_ => panic!("Expected Array type"),
}
}
#[test]
fn test_nonnullable_type() {
let interner = TypeInterner::new();
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 null_or_undefined = interner.union(vec![TypeId::NULL, TypeId::UNDEFINED]);
let _non_nullable_cond = ConditionalType {
check_type: t_param,
extends_type: null_or_undefined,
true_type: TypeId::NEVER,
false_type: t_param,
is_distributive: true,
};
let string_or_null = interner.union(vec![TypeId::STRING, TypeId::NULL]);
let test_cond = ConditionalType {
check_type: string_or_null,
extends_type: null_or_undefined,
true_type: TypeId::NEVER,
false_type: string_or_null,
is_distributive: true,
};
let result = evaluate_conditional(&interner, &test_cond);
assert!(result != TypeId::ERROR);
}
#[test]
fn test_extract_type_pattern() {
let interner = TypeInterner::new();
let source = interner.union(vec![TypeId::STRING, TypeId::NUMBER, TypeId::BOOLEAN]);
let pattern = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let cond = ConditionalType {
check_type: source,
extends_type: pattern,
true_type: source,
false_type: TypeId::NEVER,
is_distributive: true,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result != TypeId::ERROR);
}
#[test]
fn test_exclude_type_pattern() {
let interner = TypeInterner::new();
let source = interner.union(vec![TypeId::STRING, TypeId::NUMBER, TypeId::BOOLEAN]);
let pattern = TypeId::STRING;
let cond = ConditionalType {
check_type: source,
extends_type: pattern,
true_type: TypeId::NEVER,
false_type: source,
is_distributive: true,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result != TypeId::ERROR);
}
#[test]
fn test_distributive_over_large_union() {
let interner = TypeInterner::new();
let large_union = interner.union(vec![
TypeId::STRING,
TypeId::NUMBER,
TypeId::BOOLEAN,
TypeId::NULL,
TypeId::UNDEFINED,
TypeId::SYMBOL,
TypeId::BIGINT,
]);
let lit_yes = interner.literal_string("yes");
let lit_no = interner.literal_string("no");
let cond = ConditionalType {
check_type: large_union,
extends_type: TypeId::STRING,
true_type: lit_yes,
false_type: lit_no,
is_distributive: true,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result != TypeId::ERROR);
}
#[test]
fn test_distributive_nested_conditionals() {
let interner = TypeInterner::new();
let union_abc = interner.union(vec![TypeId::STRING, TypeId::NUMBER, TypeId::BOOLEAN]);
let lit_x = interner.literal_string("x");
let lit_y = interner.literal_string("y");
let lit_z = interner.literal_string("z");
let inner_cond = interner.conditional(ConditionalType {
check_type: union_abc,
extends_type: TypeId::NUMBER,
true_type: lit_x,
false_type: lit_y,
is_distributive: true,
});
let outer_cond = ConditionalType {
check_type: union_abc,
extends_type: TypeId::STRING,
true_type: inner_cond,
false_type: lit_z,
is_distributive: true,
};
let result = evaluate_conditional(&interner, &outer_cond);
assert!(result != TypeId::ERROR);
}
#[test]
fn test_distributive_with_never_absorption() {
let interner = TypeInterner::new();
let union_with_never = interner.union(vec![TypeId::STRING, TypeId::NEVER]);
let lit_yes = interner.literal_string("yes");
let lit_no = interner.literal_string("no");
let cond = ConditionalType {
check_type: union_with_never,
extends_type: TypeId::STRING,
true_type: lit_yes,
false_type: lit_no,
is_distributive: true,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result != TypeId::ERROR);
}
#[test]
fn test_distributive_all_never_result() {
let interner = TypeInterner::new();
let cond = ConditionalType {
check_type: TypeId::NUMBER,
extends_type: TypeId::STRING,
true_type: TypeId::NEVER,
false_type: TypeId::NEVER,
is_distributive: true,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_distributive_filter_to_single_type() {
let interner = TypeInterner::new();
let source = interner.union(vec![TypeId::STRING, TypeId::NUMBER, TypeId::BOOLEAN]);
let cond = ConditionalType {
check_type: source,
extends_type: TypeId::NUMBER,
true_type: source, false_type: TypeId::NEVER,
is_distributive: true,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result != TypeId::ERROR);
}
#[test]
fn test_distributive_with_literal_types() {
let interner = TypeInterner::new();
let lit_a = interner.literal_string("a");
let lit_b = interner.literal_string("b");
let lit_c = interner.literal_string("c");
let lit_1 = interner.literal_number(1.0);
let lit_0 = interner.literal_number(0.0);
let source = interner.union(vec![lit_a, lit_b, lit_c]);
let cond = ConditionalType {
check_type: source,
extends_type: lit_a,
true_type: lit_1,
false_type: lit_0,
is_distributive: true,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result != TypeId::ERROR);
}
#[test]
fn test_distributive_with_object_types() {
let interner = TypeInterner::new();
let obj_with_x = interner.object(vec![PropertyInfo::new(
interner.intern_string("x"),
TypeId::NUMBER,
)]);
let obj_with_y = interner.object(vec![PropertyInfo::new(
interner.intern_string("y"),
TypeId::STRING,
)]);
let source = interner.union(vec![obj_with_x, obj_with_y]);
let pattern = interner.object(vec![PropertyInfo::new(
interner.intern_string("x"),
TypeId::NUMBER,
)]);
let cond = ConditionalType {
check_type: source,
extends_type: pattern,
true_type: TypeId::NUMBER,
false_type: TypeId::NEVER,
is_distributive: true,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result != TypeId::ERROR);
}
#[test]
fn test_non_distributive_wrapped_type_param() {
let interner = TypeInterner::new();
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 wrapped_t = interner.tuple(vec![TupleElement {
type_id: t_param,
name: None,
optional: false,
rest: false,
}]);
let wrapped_string = interner.tuple(vec![TupleElement {
type_id: TypeId::STRING,
name: None,
optional: false,
rest: false,
}]);
let lit_yes = interner.literal_string("yes");
let lit_no = interner.literal_string("no");
let cond = ConditionalType {
check_type: wrapped_t,
extends_type: wrapped_string,
true_type: lit_yes,
false_type: lit_no,
is_distributive: false, };
let result = evaluate_conditional(&interner, &cond);
assert!(result != TypeId::ERROR);
}
#[test]
fn test_distributive_preserves_type_relationships() {
let interner = TypeInterner::new();
let lit_hello = interner.literal_string("hello");
let source = interner.union(vec![TypeId::STRING, lit_hello]);
let lit_yes = interner.literal_string("yes");
let lit_no = interner.literal_string("no");
let cond = ConditionalType {
check_type: source,
extends_type: TypeId::STRING,
true_type: lit_yes,
false_type: lit_no,
is_distributive: true,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result != TypeId::ERROR);
}
#[test]
fn test_distributive_with_any_in_union() {
let interner = TypeInterner::new();
let union_with_any = interner.union(vec![TypeId::ANY, TypeId::STRING]);
let lit_yes = interner.literal_string("yes");
let lit_no = interner.literal_string("no");
let cond = ConditionalType {
check_type: union_with_any,
extends_type: TypeId::NUMBER,
true_type: lit_yes,
false_type: lit_no,
is_distributive: true,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result != TypeId::ERROR);
}
#[test]
fn test_distributive_with_unknown_direct() {
let interner = TypeInterner::new();
let source = interner.union(vec![TypeId::STRING, TypeId::NUMBER, TypeId::NULL]);
let lit_yes = interner.literal_string("yes");
let lit_no = interner.literal_string("no");
let cond = ConditionalType {
check_type: source,
extends_type: TypeId::UNKNOWN,
true_type: lit_yes,
false_type: lit_no,
is_distributive: true,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result != TypeId::ERROR);
}
#[test]
fn test_distributive_infer_in_extends() {
let interner = TypeInterner::new();
let u_name = interner.intern_string("U");
let infer_u = interner.intern(TypeData::Infer(TypeParamInfo {
name: u_name,
constraint: None,
default: None,
is_const: false,
}));
let array_pattern = interner.array(infer_u);
let string_array = interner.array(TypeId::STRING);
let number_array = interner.array(TypeId::NUMBER);
let source = interner.union(vec![string_array, number_array, TypeId::BOOLEAN]);
let cond = ConditionalType {
check_type: source,
extends_type: array_pattern,
true_type: infer_u,
false_type: TypeId::NEVER,
is_distributive: true,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result != TypeId::ERROR);
}
#[test]
fn test_distributive_multiple_type_params() {
let interner = TypeInterner::new();
let t_name = interner.intern_string("T");
let u_name = interner.intern_string("U");
let t_param = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: t_name,
constraint: None,
default: None,
is_const: false,
}));
let u_param = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: u_name,
constraint: Some(TypeId::STRING), default: None,
is_const: false,
}));
let lit_yes = interner.literal_string("yes");
let lit_no = interner.literal_string("no");
let cond = ConditionalType {
check_type: t_param,
extends_type: u_param,
true_type: lit_yes,
false_type: lit_no,
is_distributive: true,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result != TypeId::ERROR);
}
#[test]
fn test_distributive_recursive_pattern() {
let interner = TypeInterner::new();
let source = interner.union(vec![
interner.array(TypeId::STRING),
interner.array(TypeId::NUMBER),
TypeId::BOOLEAN,
]);
let any_array = interner.array(TypeId::ANY);
let cond = ConditionalType {
check_type: source,
extends_type: any_array,
true_type: TypeId::STRING, false_type: source,
is_distributive: true,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result != TypeId::ERROR);
}
#[test]
fn test_distributive_boolean_special_case() {
let interner = TypeInterner::new();
let lit_true = interner.literal_boolean(true);
let lit_yes = interner.literal_string("yes");
let lit_no = interner.literal_string("no");
let cond = ConditionalType {
check_type: TypeId::BOOLEAN, extends_type: lit_true,
true_type: lit_yes,
false_type: lit_no,
is_distributive: true,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result != TypeId::ERROR);
}
#[test]
fn test_distributive_empty_union_to_never() {
let interner = TypeInterner::new();
let source = TypeId::STRING;
let cond = ConditionalType {
check_type: source,
extends_type: TypeId::STRING,
true_type: TypeId::NEVER,
false_type: source,
is_distributive: true,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_distributive_function_type_union() {
let interner = TypeInterner::new();
let func1 = 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 func2 = 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 source = interner.union(vec![func1, func2, TypeId::BOOLEAN]);
let any_func = 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: TypeId::ANY,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let cond = ConditionalType {
check_type: source,
extends_type: any_func,
true_type: TypeId::STRING, false_type: TypeId::NEVER,
is_distributive: true,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result != TypeId::ERROR);
}
#[test]
fn test_infer_variadic_tuple_head() {
let interner = TypeInterner::new();
let infer_h_name = interner.intern_string("H");
let infer_h = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_h_name,
constraint: None,
default: None,
is_const: false,
}));
let infer_t_name = interner.intern_string("T");
let infer_t = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_t_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = interner.tuple(vec![
TupleElement {
type_id: infer_h,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: infer_t,
name: None,
optional: false,
rest: true,
},
]);
let input = 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 cond = ConditionalType {
check_type: input,
extends_type: pattern,
true_type: infer_h,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result == TypeId::STRING || result != TypeId::ERROR);
}
#[test]
fn test_infer_variadic_tuple_tail() {
let interner = TypeInterner::new();
let infer_h_name = interner.intern_string("H");
let infer_h = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_h_name,
constraint: None,
default: None,
is_const: false,
}));
let infer_l_name = interner.intern_string("L");
let infer_l = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_l_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = interner.tuple(vec![
TupleElement {
type_id: infer_h,
name: None,
optional: false,
rest: true,
},
TupleElement {
type_id: infer_l,
name: None,
optional: false,
rest: false,
},
]);
let input = 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 cond = ConditionalType {
check_type: input,
extends_type: pattern,
true_type: infer_l,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result == TypeId::BOOLEAN || result != TypeId::ERROR);
}
#[test]
fn test_infer_variadic_tuple_middle() {
let interner = TypeInterner::new();
let infer_f_name = interner.intern_string("F");
let infer_f = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_f_name,
constraint: None,
default: None,
is_const: false,
}));
let infer_m_name = interner.intern_string("M");
let infer_m = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_m_name,
constraint: None,
default: None,
is_const: false,
}));
let infer_l_name = interner.intern_string("L");
let infer_l = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_l_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = interner.tuple(vec![
TupleElement {
type_id: infer_f,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: infer_m,
name: None,
optional: false,
rest: true,
},
TupleElement {
type_id: infer_l,
name: None,
optional: false,
rest: false,
},
]);
let input = 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,
},
TupleElement {
type_id: TypeId::SYMBOL,
name: None,
optional: false,
rest: false,
},
]);
let cond = ConditionalType {
check_type: input,
extends_type: pattern,
true_type: infer_f,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result == TypeId::STRING || result != TypeId::ERROR);
}
#[test]
fn test_infer_from_overloaded_callable() {
let interner = TypeInterner::new();
let infer_r_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_r_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = 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: infer_r,
type_predicate: None,
is_constructor: false,
is_method: false,
});
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,
});
let cond = ConditionalType {
check_type: callable,
extends_type: pattern,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result != TypeId::ERROR);
}
#[test]
fn test_infer_from_construct_signature() {
let interner = TypeInterner::new();
let infer_t_name = interner.intern_string("T");
let infer_t = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_t_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![],
construct_signatures: vec![CallSignature {
type_params: vec![],
params: vec![],
this_type: None,
return_type: infer_t,
type_predicate: None,
is_method: false,
}],
properties: vec![],
string_index: None,
number_index: None,
});
let input = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![],
construct_signatures: vec![CallSignature {
type_params: vec![],
params: vec![],
this_type: None,
return_type: TypeId::STRING,
type_predicate: None,
is_method: false,
}],
properties: vec![],
string_index: None,
number_index: None,
});
let cond = ConditionalType {
check_type: input,
extends_type: pattern,
true_type: infer_t,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result == TypeId::STRING || result != TypeId::ERROR);
}
#[test]
fn test_infer_with_index_access_result() {
let interner = TypeInterner::new();
let infer_p_name = interner.intern_string("P");
let infer_p = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_p_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = interner.object(vec![PropertyInfo::new(
interner.intern_string("prop"),
infer_p,
)]);
let input = interner.object(vec![PropertyInfo::new(
interner.intern_string("prop"),
TypeId::NUMBER,
)]);
let index_access = interner.intern(TypeData::IndexAccess(
input,
interner.literal_string("prop"),
));
let cond = ConditionalType {
check_type: input,
extends_type: pattern,
true_type: index_access,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result != TypeId::ERROR);
}
#[test]
fn test_infer_from_index_signature_value() {
let interner = TypeInterner::new();
let infer_v_name = interner.intern_string("V");
let infer_v = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_v_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: infer_v,
readonly: false,
}),
number_index: None,
});
let input = 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 cond = ConditionalType {
check_type: input,
extends_type: pattern,
true_type: infer_v,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result == TypeId::NUMBER || result != TypeId::ERROR);
}
#[test]
fn test_infer_promise_like_unwrap() {
let interner = TypeInterner::new();
let infer_t_name = interner.intern_string("T");
let infer_t = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_t_name,
constraint: None,
default: None,
is_const: false,
}));
let callback_param = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("value")),
type_id: infer_t,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::ANY,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let then_fn = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("onfulfilled")),
type_id: callback_param,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::ANY,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let pattern = interner.object(vec![PropertyInfo::method(
interner.intern_string("then"),
then_fn,
)]);
let input_callback = 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::ANY,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let input_then = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo {
name: Some(interner.intern_string("onfulfilled")),
type_id: input_callback,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::ANY,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let input = interner.object(vec![PropertyInfo::method(
interner.intern_string("then"),
input_then,
)]);
let cond = ConditionalType {
check_type: input,
extends_type: pattern,
true_type: infer_t,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result == TypeId::STRING || result != TypeId::ERROR);
}
#[test]
fn test_infer_from_mapped_type_output() {
let interner = TypeInterner::new();
let infer_v_name = interner.intern_string("V");
let infer_v = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_v_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = interner.object(vec![
PropertyInfo::new(interner.intern_string("a"), infer_v),
PropertyInfo::new(interner.intern_string("b"), infer_v),
]);
let input = interner.object(vec![
PropertyInfo::new(interner.intern_string("a"), TypeId::STRING),
PropertyInfo::new(interner.intern_string("b"), TypeId::STRING),
]);
let cond = ConditionalType {
check_type: input,
extends_type: pattern,
true_type: infer_v,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result == TypeId::STRING || result != TypeId::ERROR);
}
#[test]
fn test_infer_same_name_different_values() {
let interner = TypeInterner::new();
let infer_v_name = interner.intern_string("V");
let infer_v = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_v_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = interner.object(vec![
PropertyInfo::new(interner.intern_string("a"), infer_v),
PropertyInfo::new(interner.intern_string("b"), infer_v),
]);
let input = interner.object(vec![
PropertyInfo::new(interner.intern_string("a"), TypeId::STRING),
PropertyInfo::new(interner.intern_string("b"), TypeId::NUMBER),
]);
let cond = ConditionalType {
check_type: input,
extends_type: pattern,
true_type: infer_v,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result != TypeId::ERROR);
}
#[test]
fn test_infer_with_keyof_constraint() {
let interner = TypeInterner::new();
let infer_k_name = interner.intern_string("K");
let infer_k = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_k_name,
constraint: Some(TypeId::STRING), default: None,
is_const: false,
}));
let pattern = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![],
string_index: Some(IndexSignature {
key_type: infer_k,
value_type: TypeId::NUMBER,
readonly: false,
}),
number_index: None,
});
let input = 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 cond = ConditionalType {
check_type: input,
extends_type: pattern,
true_type: infer_k,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result == TypeId::STRING || result != TypeId::ERROR);
}
#[test]
fn test_infer_from_branded_intersection() {
let interner = TypeInterner::new();
let infer_b_name = interner.intern_string("B");
let infer_b = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_b_name,
constraint: None,
default: None,
is_const: false,
}));
let brand_pattern = interner.object(vec![PropertyInfo::new(
interner.intern_string("__brand"),
infer_b,
)]);
let brand_lit = interner.literal_string("UserId");
let brand_obj = interner.object(vec![PropertyInfo::new(
interner.intern_string("__brand"),
brand_lit,
)]);
let input = interner.intersection(vec![TypeId::STRING, brand_obj]);
let cond = ConditionalType {
check_type: input,
extends_type: brand_pattern,
true_type: infer_b,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result == brand_lit || result != TypeId::ERROR);
}
#[test]
fn test_infer_ignores_readonly() {
let interner = TypeInterner::new();
let infer_t_name = interner.intern_string("T");
let infer_t = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_t_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = interner.object(vec![PropertyInfo {
name: interner.intern_string("prop"),
type_id: infer_t,
write_type: infer_t,
optional: false,
readonly: true, is_method: false,
visibility: Visibility::Public,
parent_id: None,
}]);
let input = interner.object(vec![PropertyInfo::new(
interner.intern_string("prop"),
TypeId::NUMBER,
)]);
let cond = ConditionalType {
check_type: input,
extends_type: pattern,
true_type: infer_t,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result == TypeId::NUMBER || result != TypeId::ERROR);
}
#[test]
fn test_infer_optional_tuple_element() {
let interner = TypeInterner::new();
let infer_a_name = interner.intern_string("A");
let infer_a = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_a_name,
constraint: None,
default: None,
is_const: false,
}));
let infer_b_name = interner.intern_string("B");
let infer_b = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_b_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = interner.tuple(vec![
TupleElement {
type_id: infer_a,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: infer_b,
name: None,
optional: true,
rest: false,
},
]);
let input = 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 result_tuple = interner.tuple(vec![
TupleElement {
type_id: infer_a,
name: None,
optional: false,
rest: false,
},
TupleElement {
type_id: infer_b,
name: None,
optional: false,
rest: false,
},
]);
let cond = ConditionalType {
check_type: input,
extends_type: pattern,
true_type: result_tuple,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result != TypeId::ERROR);
}
#[test]
fn test_template_literal_with_number_type() {
let interner = TypeInterner::new();
let template = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("id_")),
TemplateSpan::Type(TypeId::NUMBER),
]);
match interner.lookup(template) {
Some(TypeData::TemplateLiteral(_)) => (),
_ => panic!("Expected TemplateLiteral type"),
}
}
#[test]
fn test_template_literal_with_boolean_type() {
let interner = TypeInterner::new();
let template = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("is_")),
TemplateSpan::Type(TypeId::BOOLEAN),
]);
match interner.lookup(template) {
Some(TypeData::Union(list_id)) => {
let members = interner.type_list(list_id);
assert_eq!(
members.len(),
2,
"Expected 2 members in union for boolean expansion"
);
for member in members.iter() {
match interner.lookup(*member) {
Some(TypeData::Literal(LiteralValue::String(_))) => (),
other => panic!("Expected string literal in union, got {other:?}"),
}
}
}
other => panic!("Expected Union type for `is_${{boolean}}`, got {other:?}"),
}
}
#[test]
fn test_template_literal_cartesian_product() {
let interner = TypeInterner::new();
let lit_a = interner.literal_string("a");
let lit_b = interner.literal_string("b");
let union1 = interner.union(vec![lit_a, lit_b]);
let lit_1 = interner.literal_string("1");
let lit_2 = interner.literal_string("2");
let union2 = interner.union(vec![lit_1, lit_2]);
let template = interner.template_literal(vec![
TemplateSpan::Type(union1),
TemplateSpan::Text(interner.intern_string("_")),
TemplateSpan::Type(union2),
]);
match interner.lookup(template) {
Some(TypeData::Union(members_id)) => {
let members = interner.type_list(members_id);
assert_eq!(
members.len(),
4,
"Expected 4 members in cartesian product union"
);
}
_ => panic!(
"Expected Union type for template with multiple union interpolations, got {:?}",
interner.lookup(template)
),
}
}
#[test]
fn test_template_literal_with_never() {
let interner = TypeInterner::new();
let template = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("prefix_")),
TemplateSpan::Type(TypeId::NEVER),
]);
let result = evaluate_type(&interner, template);
assert!(result == TypeId::NEVER || result == template);
}
#[test]
fn test_template_literal_with_any() {
let interner = TypeInterner::new();
let template = interner.template_literal(vec![TemplateSpan::Type(TypeId::ANY)]);
assert_eq!(template, TypeId::STRING);
}
#[test]
fn test_template_literal_concatenation() {
let interner = TypeInterner::new();
let hello = interner.literal_string("hello");
let world = interner.literal_string("world");
let template =
interner.template_literal(vec![TemplateSpan::Type(hello), TemplateSpan::Type(world)]);
match interner.lookup(template) {
Some(TypeData::Literal(LiteralValue::String(atom))) => {
let s = interner.resolve_atom_ref(atom);
assert_eq!(
s.as_ref(),
"helloworld",
"Expected concatenated string literal"
);
}
_ => panic!(
"Expected string literal for concatenated string interpolations, got {:?}",
interner.lookup(template)
),
}
}
#[test]
fn test_template_literal_empty_string() {
let interner = TypeInterner::new();
let template = interner.template_literal(vec![]);
let result = evaluate_type(&interner, template);
assert!(result != TypeId::ERROR);
}
#[test]
fn test_template_literal_single_text() {
let interner = TypeInterner::new();
let template =
interner.template_literal(vec![TemplateSpan::Text(interner.intern_string("hello"))]);
let result = evaluate_type(&interner, template);
assert!(result != TypeId::ERROR);
}
#[test]
fn test_template_literal_pattern_infer_numeric() {
let interner = TypeInterner::new();
let n_name = interner.intern_string("N");
let infer_n = interner.intern(TypeData::Infer(TypeParamInfo {
name: n_name,
constraint: Some(TypeId::NUMBER),
default: None,
is_const: false,
}));
let extends_template = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("id_")),
TemplateSpan::Type(infer_n),
]);
let lit_id_42 = interner.literal_string("id_42");
let cond = ConditionalType {
check_type: lit_id_42,
extends_type: extends_template,
true_type: infer_n,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result != TypeId::ERROR);
}
#[test]
fn test_template_literal_multiple_adjacent_types() {
let interner = TypeInterner::new();
let lit_x = interner.literal_string("x");
let lit_y = interner.literal_string("y");
let lit_z = interner.literal_string("z");
let template = interner.template_literal(vec![
TemplateSpan::Type(lit_x),
TemplateSpan::Type(lit_y),
TemplateSpan::Type(lit_z),
]);
match interner.lookup(template) {
Some(TypeData::Literal(LiteralValue::String(atom))) => {
let s = interner.resolve_atom_ref(atom);
assert_eq!(s.as_ref(), "xyz", "Expected concatenated string literal");
}
_ => panic!(
"Expected string literal for concatenated string interpolations, got {:?}",
interner.lookup(template)
),
}
}
#[test]
fn test_template_literal_union_in_middle() {
let interner = TypeInterner::new();
let lit_a = interner.literal_string("a");
let lit_b = interner.literal_string("b");
let lit_c = interner.literal_string("c");
let union = interner.union(vec![lit_a, lit_b, lit_c]);
let template = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("pre_")),
TemplateSpan::Type(union),
TemplateSpan::Text(interner.intern_string("_suf")),
]);
match interner.lookup(template) {
Some(TypeData::Union(members_id)) => {
let members = interner.type_list(members_id);
assert_eq!(members.len(), 3, "Expected 3 members in union");
}
_ => panic!(
"Expected Union type for template with union interpolation, got {:?}",
interner.lookup(template)
),
}
}
#[test]
fn test_template_literal_bigint_type() {
let interner = TypeInterner::new();
let template = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("value_")),
TemplateSpan::Type(TypeId::BIGINT),
]);
match interner.lookup(template) {
Some(TypeData::TemplateLiteral(_)) => (),
_ => panic!("Expected TemplateLiteral type"),
}
}
#[test]
fn test_template_literal_null_undefined() {
let interner = TypeInterner::new();
let template_null = interner.template_literal(vec![TemplateSpan::Type(TypeId::NULL)]);
let template_undefined = interner.template_literal(vec![TemplateSpan::Type(TypeId::UNDEFINED)]);
match interner.lookup(template_null) {
Some(TypeData::Literal(LiteralValue::String(atom))) => {
let s = interner.resolve_atom_ref(atom);
assert_eq!(s.as_ref(), "null", "Expected 'null' string literal");
}
_ => panic!(
"Expected string literal 'null' for `${{null}}`, got {:?}",
interner.lookup(template_null)
),
}
match interner.lookup(template_undefined) {
Some(TypeData::Literal(LiteralValue::String(atom))) => {
let s = interner.resolve_atom_ref(atom);
assert_eq!(
s.as_ref(),
"undefined",
"Expected 'undefined' string literal"
);
}
_ => panic!(
"Expected string literal 'undefined' for `${{undefined}}`, got {:?}",
interner.lookup(template_undefined)
),
}
}
#[test]
fn test_template_literal_subtype_of_string() {
let interner = TypeInterner::new();
let template = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("foo_")),
TemplateSpan::Type(TypeId::STRING),
]);
let mut checker = SubtypeChecker::new(&interner);
let extends = checker.is_subtype_of(template, TypeId::STRING);
assert!(extends);
}
#[test]
fn test_template_literal_specific_extends_pattern() {
let interner = TypeInterner::new();
let literal = interner.literal_string("foo_bar");
let pattern = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("foo_")),
TemplateSpan::Type(TypeId::STRING),
]);
let mut checker = SubtypeChecker::new(&interner);
let extends = checker.is_subtype_of(literal, pattern);
assert!(extends);
}
#[test]
fn test_keyof_intersection_with_never() {
let interner = TypeInterner::new();
let obj = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
TypeId::STRING,
)]);
let intersection = interner.intersection(vec![obj, TypeId::NEVER]);
let result = evaluate_keyof(&interner, intersection);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_keyof_union_with_any() {
let interner = TypeInterner::new();
let obj = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
TypeId::STRING,
)]);
let union = interner.union(vec![obj, TypeId::ANY]);
let result = evaluate_keyof(&interner, union);
assert!(result != TypeId::ERROR);
}
#[test]
fn test_keyof_intersection_with_any() {
let interner = TypeInterner::new();
let obj = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
TypeId::STRING,
)]);
let intersection = interner.intersection(vec![obj, TypeId::ANY]);
let result = evaluate_keyof(&interner, intersection);
assert!(result != TypeId::ERROR);
}
#[test]
fn test_keyof_union_with_unknown() {
let interner = TypeInterner::new();
let obj = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
TypeId::STRING,
)]);
let union = interner.union(vec![obj, TypeId::UNKNOWN]);
let result = evaluate_keyof(&interner, union);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_keyof_four_way_intersection() {
let interner = TypeInterner::new();
let obj_a = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
TypeId::STRING,
)]);
let obj_b = interner.object(vec![PropertyInfo::new(
interner.intern_string("b"),
TypeId::NUMBER,
)]);
let obj_c = interner.object(vec![PropertyInfo::new(
interner.intern_string("c"),
TypeId::BOOLEAN,
)]);
let obj_d = interner.object(vec![PropertyInfo::new(
interner.intern_string("d"),
TypeId::STRING,
)]);
let intersection = interner.intersection(vec![obj_a, obj_b, obj_c, obj_d]);
let result = evaluate_keyof(&interner, intersection);
let lit_a = interner.literal_string("a");
let lit_b = interner.literal_string("b");
let lit_c = interner.literal_string("c");
let lit_d = interner.literal_string("d");
let expected = interner.union(vec![lit_a, lit_b, lit_c, lit_d]);
assert_eq!(result, expected);
}
#[test]
fn test_keyof_four_way_union() {
let interner = TypeInterner::new();
let common_key = interner.intern_string("common");
let obj_a = interner.object(vec![
PropertyInfo::new(common_key, TypeId::STRING),
PropertyInfo::new(interner.intern_string("a"), TypeId::STRING),
]);
let obj_b = interner.object(vec![
PropertyInfo::new(common_key, TypeId::STRING),
PropertyInfo::new(interner.intern_string("b"), TypeId::NUMBER),
]);
let obj_c = interner.object(vec![PropertyInfo::new(common_key, TypeId::STRING)]);
let obj_d = interner.object(vec![
PropertyInfo::new(common_key, TypeId::STRING),
PropertyInfo::new(interner.intern_string("d"), TypeId::BOOLEAN),
]);
let union = interner.union(vec![obj_a, obj_b, obj_c, obj_d]);
let result = evaluate_keyof(&interner, union);
let expected = interner.literal_string("common");
assert_eq!(result, expected);
}
#[test]
fn test_keyof_mixed_intersection_union() {
let interner = TypeInterner::new();
let obj_a = interner.object(vec![
PropertyInfo::new(interner.intern_string("a"), TypeId::STRING),
PropertyInfo::new(interner.intern_string("common"), TypeId::STRING),
]);
let obj_b = interner.object(vec![PropertyInfo::new(
interner.intern_string("b"),
TypeId::NUMBER,
)]);
let obj_c = interner.object(vec![PropertyInfo::new(
interner.intern_string("common"),
TypeId::STRING,
)]);
let a_and_b = interner.intersection(vec![obj_a, obj_b]);
let union = interner.union(vec![a_and_b, obj_c]);
let result = evaluate_keyof(&interner, union);
let expected = interner.literal_string("common");
assert_eq!(result, expected);
}
#[test]
fn test_keyof_intersection_both_index_signatures() {
let interner = TypeInterner::new();
let string_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::STRING,
readonly: false,
}),
number_index: None,
});
let number_indexed = 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 intersection = interner.intersection(vec![string_indexed, number_indexed]);
let result = evaluate_keyof(&interner, intersection);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert_eq!(result, expected);
}
#[test]
fn test_keyof_union_index_and_literal() {
let interner = TypeInterner::new();
let string_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::STRING,
readonly: false,
}),
number_index: None,
});
let literal_obj = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
TypeId::NUMBER,
)]);
let union = interner.union(vec![string_indexed, literal_obj]);
let result = evaluate_keyof(&interner, union);
let expected = interner.literal_string("a");
assert_eq!(result, expected);
}
#[test]
fn test_keyof_intersection_with_callable() {
let interner = TypeInterner::new();
let obj = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
TypeId::STRING,
)]);
let callable = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![CallSignature {
type_params: Vec::new(),
params: Vec::new(),
this_type: None,
return_type: TypeId::VOID,
type_predicate: None,
is_method: false,
}],
construct_signatures: Vec::new(),
properties: Vec::new(),
string_index: None,
number_index: None,
});
let intersection = interner.intersection(vec![obj, callable]);
let result = evaluate_keyof(&interner, intersection);
let lit_a = interner.literal_string("a");
let mut checker = SubtypeChecker::new(&interner);
assert!(checker.is_subtype_of(lit_a, result));
}
#[test]
fn test_keyof_intersection_with_array() {
let interner = TypeInterner::new();
let obj = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
TypeId::STRING,
)]);
let arr = interner.array(TypeId::STRING);
let intersection = interner.intersection(vec![obj, arr]);
let result = evaluate_keyof(&interner, intersection);
assert!(result != TypeId::ERROR);
}
#[test]
fn test_keyof_empty_intersection() {
let interner = TypeInterner::new();
let intersection = interner.intersection(vec![TypeId::STRING, TypeId::NUMBER]);
let result = evaluate_keyof(&interner, intersection);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_keyof_empty_union() {
let interner = TypeInterner::new();
let result = evaluate_keyof(&interner, TypeId::NEVER);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_keyof_nested_keyof() {
let interner = TypeInterner::new();
let obj = interner.object(vec![
PropertyInfo::new(interner.intern_string("a"), TypeId::STRING),
PropertyInfo::new(interner.intern_string("b"), TypeId::NUMBER),
]);
let keyof_obj = evaluate_keyof(&interner, obj);
let keyof_keyof = evaluate_keyof(&interner, keyof_obj);
assert!(keyof_keyof != TypeId::ERROR);
}
#[test]
fn test_callable_param_infer_union_of_signatures() {
let interner = TypeInterner::new();
let t_name = interner.intern_string("T");
let p_name = interner.intern_string("P");
let t_param = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: t_name,
constraint: None,
default: None,
is_const: false,
}));
let infer_p = interner.intern(TypeData::Infer(TypeParamInfo {
name: p_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern_fn = interner.function(FunctionShape {
params: vec![ParamInfo::unnamed(infer_p)],
return_type: TypeId::ANY,
type_predicate: None,
this_type: None,
type_params: Vec::new(),
is_constructor: false,
is_method: false,
});
let cond = ConditionalType {
check_type: t_param,
extends_type: pattern_fn,
true_type: infer_p,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let fn_string = interner.function(FunctionShape {
params: vec![ParamInfo::unnamed(TypeId::STRING)],
return_type: TypeId::VOID,
type_predicate: None,
this_type: None,
type_params: Vec::new(),
is_constructor: false,
is_method: false,
});
let fn_number = interner.function(FunctionShape {
params: vec![ParamInfo::unnamed(TypeId::NUMBER)],
return_type: TypeId::VOID,
type_predicate: None,
this_type: None,
type_params: Vec::new(),
is_constructor: false,
is_method: false,
});
let fn_union = interner.union(vec![fn_string, fn_number]);
let mut subst = TypeSubstitution::new();
subst.insert(t_name, fn_union);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert_eq!(result, expected);
}
#[test]
fn test_callable_param_infer_overloaded_callable() {
let interner = TypeInterner::new();
let t_name = interner.intern_string("T");
let p_name = interner.intern_string("P");
let t_param = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: t_name,
constraint: None,
default: None,
is_const: false,
}));
let infer_p = interner.intern(TypeData::Infer(TypeParamInfo {
name: p_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern_callable = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![CallSignature {
params: vec![ParamInfo::unnamed(infer_p)],
return_type: TypeId::ANY,
type_predicate: None,
this_type: None,
type_params: Vec::new(),
is_method: false,
}],
construct_signatures: Vec::new(),
properties: Vec::new(),
string_index: None,
number_index: None,
});
let cond = ConditionalType {
check_type: t_param,
extends_type: pattern_callable,
true_type: infer_p,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let overloaded = interner.callable(CallableShape {
symbol: None,
call_signatures: vec![
CallSignature {
params: vec![ParamInfo::unnamed(TypeId::STRING)],
return_type: TypeId::VOID,
type_predicate: None,
this_type: None,
type_params: Vec::new(),
is_method: false,
},
CallSignature {
params: vec![ParamInfo::unnamed(TypeId::NUMBER)],
return_type: TypeId::BOOLEAN,
type_predicate: None,
this_type: None,
type_params: Vec::new(),
is_method: false,
},
],
construct_signatures: Vec::new(),
properties: Vec::new(),
string_index: None,
number_index: None,
});
let mut subst = TypeSubstitution::new();
subst.insert(t_name, overloaded);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::NUMBER);
}
#[test]
fn test_callable_param_infer_mixed_union() {
let interner = TypeInterner::new();
let t_name = interner.intern_string("T");
let p_name = interner.intern_string("P");
let t_param = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: t_name,
constraint: None,
default: None,
is_const: false,
}));
let infer_p = interner.intern(TypeData::Infer(TypeParamInfo {
name: p_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern_fn = interner.function(FunctionShape {
params: vec![ParamInfo::unnamed(infer_p)],
return_type: TypeId::ANY,
type_predicate: None,
this_type: None,
type_params: Vec::new(),
is_constructor: false,
is_method: false,
});
let cond = ConditionalType {
check_type: t_param,
extends_type: pattern_fn,
true_type: infer_p,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let fn_string = interner.function(FunctionShape {
params: vec![ParamInfo::unnamed(TypeId::STRING)],
return_type: TypeId::VOID,
type_predicate: None,
this_type: None,
type_params: Vec::new(),
is_constructor: false,
is_method: false,
});
let mixed_union = interner.union(vec![fn_string, TypeId::NUMBER]);
let mut subst = TypeSubstitution::new();
subst.insert(t_name, mixed_union);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::STRING);
}
#[test]
fn test_callable_return_and_param_infer_separately() {
let interner = TypeInterner::new();
let t_name = interner.intern_string("T");
let p_name = interner.intern_string("P");
let r_name = interner.intern_string("R");
let t_param = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: t_name,
constraint: None,
default: None,
is_const: false,
}));
let infer_p = interner.intern(TypeData::Infer(TypeParamInfo {
name: p_name,
constraint: None,
default: None,
is_const: false,
}));
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: r_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern_fn = interner.function(FunctionShape {
params: vec![ParamInfo::unnamed(infer_p)],
return_type: infer_r,
type_predicate: None,
this_type: None,
type_params: Vec::new(),
is_constructor: false,
is_method: false,
});
let tuple_type = interner.tuple(vec![
TupleElement {
type_id: infer_p,
optional: false,
rest: false,
name: None,
},
TupleElement {
type_id: infer_r,
optional: false,
rest: false,
name: None,
},
]);
let cond = ConditionalType {
check_type: t_param,
extends_type: pattern_fn,
true_type: tuple_type,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let source_fn = interner.function(FunctionShape {
params: vec![ParamInfo::unnamed(TypeId::STRING)],
return_type: TypeId::NUMBER,
type_predicate: None,
this_type: None,
type_params: Vec::new(),
is_constructor: false,
is_method: false,
});
let mut subst = TypeSubstitution::new();
subst.insert(t_name, source_fn);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = 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,
},
]);
assert_eq!(result, expected);
}
#[test]
fn test_callable_multiple_params_infer() {
let interner = TypeInterner::new();
let t_name = interner.intern_string("T");
let a_name = interner.intern_string("A");
let b_name = interner.intern_string("B");
let t_param = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: t_name,
constraint: None,
default: None,
is_const: false,
}));
let infer_a = interner.intern(TypeData::Infer(TypeParamInfo {
name: a_name,
constraint: None,
default: None,
is_const: false,
}));
let infer_b = interner.intern(TypeData::Infer(TypeParamInfo {
name: b_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern_fn = interner.function(FunctionShape {
params: vec![ParamInfo::unnamed(infer_a), ParamInfo::unnamed(infer_b)],
return_type: TypeId::ANY,
type_predicate: None,
this_type: None,
type_params: Vec::new(),
is_constructor: false,
is_method: false,
});
let tuple_type = interner.tuple(vec![
TupleElement {
type_id: infer_a,
optional: false,
rest: false,
name: None,
},
TupleElement {
type_id: infer_b,
optional: false,
rest: false,
name: None,
},
]);
let cond = ConditionalType {
check_type: t_param,
extends_type: pattern_fn,
true_type: tuple_type,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let source_fn = interner.function(FunctionShape {
params: vec![
ParamInfo::unnamed(TypeId::STRING),
ParamInfo::unnamed(TypeId::NUMBER),
],
return_type: TypeId::VOID,
type_predicate: None,
this_type: None,
type_params: Vec::new(),
is_constructor: false,
is_method: false,
});
let mut subst = TypeSubstitution::new();
subst.insert(t_name, source_fn);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = 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,
},
]);
assert_eq!(result, expected);
}
#[test]
fn test_mapped_type_homomorphic_preserves_optional() {
let interner = TypeInterner::new();
let source = interner.object(vec![
PropertyInfo::new(interner.intern_string("required"), TypeId::STRING),
PropertyInfo::opt(interner.intern_string("optional"), TypeId::NUMBER),
]);
let keyof_source = interner.intern(TypeData::KeyOf(source));
let mapped = MappedType {
type_param: TypeParamInfo {
name: interner.intern_string("K"),
constraint: None,
default: None,
is_const: false,
},
constraint: keyof_source,
name_type: None,
template: TypeId::BOOLEAN,
readonly_modifier: None,
optional_modifier: None,
};
let result = evaluate_mapped(&interner, &mapped);
assert!(result != TypeId::ERROR);
}
#[test]
fn test_mapped_type_homomorphic_preserves_readonly() {
let interner = TypeInterner::new();
let source = interner.object(vec![
PropertyInfo::new(interner.intern_string("mutable"), TypeId::STRING),
PropertyInfo::readonly(interner.intern_string("immutable"), TypeId::NUMBER),
]);
let keyof_source = interner.intern(TypeData::KeyOf(source));
let mapped = MappedType {
type_param: TypeParamInfo {
name: interner.intern_string("K"),
constraint: None,
default: None,
is_const: false,
},
constraint: keyof_source,
name_type: None,
template: TypeId::BOOLEAN,
readonly_modifier: None,
optional_modifier: None,
};
let result = evaluate_mapped(&interner, &mapped);
assert!(result != TypeId::ERROR);
}
#[test]
fn test_mapped_type_key_remap_to_getter_setter() {
let interner = TypeInterner::new();
let key_x = interner.literal_string("x");
let key_y = interner.literal_string("y");
let keys = interner.union(vec![key_x, key_y]);
let get_x = interner.literal_string("getX");
let get_y = interner.literal_string("getY");
let remapped_keys = interner.union(vec![get_x, get_y]);
let mapped = MappedType {
type_param: TypeParamInfo {
name: interner.intern_string("K"),
constraint: None,
default: None,
is_const: false,
},
constraint: keys,
name_type: Some(remapped_keys),
template: TypeId::NUMBER,
readonly_modifier: None,
optional_modifier: None,
};
let result = evaluate_mapped(&interner, &mapped);
assert!(result != TypeId::ERROR);
}
#[test]
fn test_mapped_type_key_remap_filter_by_type() {
let interner = TypeInterner::new();
let key_name = interner.literal_string("name");
let key_age = interner.literal_string("age");
let keys = interner.union(vec![key_name, key_age]);
let mapped = MappedType {
type_param: TypeParamInfo {
name: interner.intern_string("K"),
constraint: None,
default: None,
is_const: false,
},
constraint: keys,
name_type: Some(key_name),
template: TypeId::STRING,
readonly_modifier: None,
optional_modifier: None,
};
let result = evaluate_mapped(&interner, &mapped);
assert!(result != TypeId::ERROR);
}
#[test]
fn test_mapped_type_nested_mapped() {
let interner = TypeInterner::new();
let key_a = interner.literal_string("a");
let key_b = interner.literal_string("b");
let outer_keys = interner.union(vec![key_a, key_b]);
let inner_template = TypeId::BOOLEAN;
let mapped = MappedType {
type_param: TypeParamInfo {
name: interner.intern_string("K"),
constraint: None,
default: None,
is_const: false,
},
constraint: outer_keys,
name_type: None,
template: inner_template,
readonly_modifier: None,
optional_modifier: None,
};
let result = evaluate_mapped(&interner, &mapped);
assert!(result != TypeId::ERROR);
}
#[test]
fn test_mapped_type_with_conditional_template() {
let interner = TypeInterner::new();
let key_x = interner.literal_string("x");
let key_y = interner.literal_string("y");
let keys = interner.union(vec![key_x, key_y]);
let cond = ConditionalType {
check_type: TypeId::STRING,
extends_type: TypeId::STRING,
true_type: TypeId::NUMBER,
false_type: TypeId::BOOLEAN,
is_distributive: false,
};
let cond_template = interner.conditional(cond);
let mapped = MappedType {
type_param: TypeParamInfo {
name: interner.intern_string("K"),
constraint: None,
default: None,
is_const: false,
},
constraint: keys,
name_type: None,
template: cond_template,
readonly_modifier: None,
optional_modifier: None,
};
let result = evaluate_mapped(&interner, &mapped);
assert!(result != TypeId::ERROR);
}
#[test]
fn test_mapped_type_union_key_constraint() {
let interner = TypeInterner::new();
let obj_a = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
TypeId::STRING,
)]);
let obj_b = interner.object(vec![PropertyInfo::new(
interner.intern_string("b"),
TypeId::NUMBER,
)]);
let union = interner.union(vec![obj_a, obj_b]);
let keyof_union = interner.intern(TypeData::KeyOf(union));
let mapped = MappedType {
type_param: TypeParamInfo {
name: interner.intern_string("K"),
constraint: None,
default: None,
is_const: false,
},
constraint: keyof_union,
name_type: None,
template: TypeId::BOOLEAN,
readonly_modifier: None,
optional_modifier: None,
};
let result = evaluate_mapped(&interner, &mapped);
assert!(result != TypeId::ERROR);
}
#[test]
fn test_mapped_type_intersection_source() {
let interner = TypeInterner::new();
let obj_a = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
TypeId::STRING,
)]);
let obj_b = interner.object(vec![PropertyInfo::new(
interner.intern_string("b"),
TypeId::NUMBER,
)]);
let intersection = interner.intersection(vec![obj_a, obj_b]);
let keyof_intersection = interner.intern(TypeData::KeyOf(intersection));
let mapped = MappedType {
type_param: TypeParamInfo {
name: interner.intern_string("K"),
constraint: None,
default: None,
is_const: false,
},
constraint: keyof_intersection,
name_type: None,
template: TypeId::BOOLEAN,
readonly_modifier: None,
optional_modifier: None,
};
let result = evaluate_mapped(&interner, &mapped);
assert!(result != TypeId::ERROR);
}
#[test]
fn test_mapped_type_key_remap_exclude_pattern() {
let interner = TypeInterner::new();
let key_public = interner.literal_string("public");
let key_internal = interner.literal_string("internal");
let keys = interner.union(vec![key_public, key_internal]);
let mapped = MappedType {
type_param: TypeParamInfo {
name: interner.intern_string("K"),
constraint: None,
default: None,
is_const: false,
},
constraint: keys,
name_type: Some(key_public),
template: TypeId::STRING,
readonly_modifier: None,
optional_modifier: None,
};
let result = evaluate_mapped(&interner, &mapped);
assert!(result != TypeId::ERROR);
}
#[test]
fn test_mapped_type_deep_readonly() {
let interner = TypeInterner::new();
let key_x = interner.literal_string("x");
let key_y = interner.literal_string("y");
let keys = interner.union(vec![key_x, key_y]);
let mapped = MappedType {
type_param: TypeParamInfo {
name: interner.intern_string("K"),
constraint: None,
default: None,
is_const: false,
},
constraint: keys,
name_type: None,
template: TypeId::OBJECT,
readonly_modifier: Some(MappedModifier::Add),
optional_modifier: None,
};
let result = evaluate_mapped(&interner, &mapped);
assert!(result != TypeId::ERROR);
}
#[test]
fn test_mapped_type_pick_pattern() {
let interner = TypeInterner::new();
let key_a = interner.literal_string("a");
let mapped = MappedType {
type_param: TypeParamInfo {
name: interner.intern_string("P"),
constraint: None,
default: None,
is_const: false,
},
constraint: key_a,
name_type: None,
template: TypeId::STRING,
readonly_modifier: None,
optional_modifier: None,
};
let result = evaluate_mapped(&interner, &mapped);
let expected = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
TypeId::STRING,
)]);
assert_eq!(result, expected);
}
#[test]
fn test_mapped_type_record_pattern() {
let interner = TypeInterner::new();
let key_x = interner.literal_string("x");
let key_y = interner.literal_string("y");
let key_z = interner.literal_string("z");
let keys = interner.union(vec![key_x, key_y, key_z]);
let mapped = MappedType {
type_param: TypeParamInfo {
name: interner.intern_string("P"),
constraint: None,
default: None,
is_const: false,
},
constraint: keys,
name_type: None,
template: TypeId::NUMBER,
readonly_modifier: None,
optional_modifier: None,
};
let result = evaluate_mapped(&interner, &mapped);
let expected = interner.object(vec![
PropertyInfo::new(interner.intern_string("x"), TypeId::NUMBER),
PropertyInfo::new(interner.intern_string("y"), TypeId::NUMBER),
PropertyInfo::new(interner.intern_string("z"), TypeId::NUMBER),
]);
assert_eq!(result, expected);
}
#[test]
fn test_mapped_type_mutable_pattern() {
let interner = TypeInterner::new();
let key_x = interner.literal_string("x");
let key_y = interner.literal_string("y");
let keys = interner.union(vec![key_x, key_y]);
let 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: Some(MappedModifier::Remove),
optional_modifier: None,
};
let result = evaluate_mapped(&interner, &mapped);
let expected = interner.object(vec![
PropertyInfo::new(interner.intern_string("x"), TypeId::STRING),
PropertyInfo::new(interner.intern_string("y"), TypeId::STRING),
]);
assert_eq!(result, expected);
}
#[test]
fn test_mapped_type_required_pattern() {
let interner = TypeInterner::new();
let key_x = interner.literal_string("x");
let key_y = interner.literal_string("y");
let keys = interner.union(vec![key_x, key_y]);
let 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 result = evaluate_mapped(&interner, &mapped);
let expected = interner.object(vec![
PropertyInfo::new(interner.intern_string("x"), TypeId::STRING),
PropertyInfo::new(interner.intern_string("y"), TypeId::STRING),
]);
assert_eq!(result, expected);
}
#[test]
fn test_mapped_type_empty_keys() {
let interner = TypeInterner::new();
let mapped = MappedType {
type_param: TypeParamInfo {
name: interner.intern_string("K"),
constraint: None,
default: None,
is_const: false,
},
constraint: TypeId::NEVER,
name_type: None,
template: TypeId::STRING,
readonly_modifier: None,
optional_modifier: None,
};
let result = evaluate_mapped(&interner, &mapped);
let expected = interner.object(vec![]);
assert_eq!(result, expected);
}
#[test]
fn test_mapped_type_single_literal_key() {
let interner = TypeInterner::new();
let key = interner.literal_string("only");
let mapped = MappedType {
type_param: TypeParamInfo {
name: interner.intern_string("K"),
constraint: None,
default: None,
is_const: false,
},
constraint: key,
name_type: None,
template: TypeId::NUMBER,
readonly_modifier: None,
optional_modifier: None,
};
let result = evaluate_mapped(&interner, &mapped);
let expected = interner.object(vec![PropertyInfo::new(
interner.intern_string("only"),
TypeId::NUMBER,
)]);
assert_eq!(result, expected);
}
#[test]
fn test_infer_return_void_vs_undefined() {
let interner = TypeInterner::new();
let t_name = interner.intern_string("T");
let r_name = interner.intern_string("R");
let t_param = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: t_name,
constraint: None,
default: None,
is_const: false,
}));
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: r_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern_fn = interner.function(FunctionShape {
params: Vec::new(),
return_type: infer_r,
type_predicate: None,
this_type: None,
type_params: Vec::new(),
is_constructor: false,
is_method: false,
});
let cond = ConditionalType {
check_type: t_param,
extends_type: pattern_fn,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let source_fn = interner.function(FunctionShape {
params: Vec::new(),
return_type: TypeId::VOID,
type_predicate: None,
this_type: None,
type_params: Vec::new(),
is_constructor: false,
is_method: false,
});
let mut subst = TypeSubstitution::new();
subst.insert(t_name, source_fn);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::VOID);
}
#[test]
fn test_infer_return_promise_like() {
let interner = TypeInterner::new();
let t_name = interner.intern_string("T");
let r_name = interner.intern_string("R");
let t_param = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: t_name,
constraint: None,
default: None,
is_const: false,
}));
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: r_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern_fn = interner.function(FunctionShape {
params: Vec::new(),
return_type: infer_r,
type_predicate: None,
this_type: None,
type_params: Vec::new(),
is_constructor: false,
is_method: false,
});
let cond = ConditionalType {
check_type: t_param,
extends_type: pattern_fn,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let then_name = interner.intern_string("then");
let promise_string = interner.object(vec![PropertyInfo {
name: then_name,
type_id: TypeId::ANY, write_type: TypeId::ANY,
optional: false,
readonly: false,
is_method: true,
visibility: Visibility::Public,
parent_id: None,
}]);
let source_fn = interner.function(FunctionShape {
params: Vec::new(),
return_type: promise_string,
type_predicate: None,
this_type: None,
type_params: Vec::new(),
is_constructor: false,
is_method: false,
});
let mut subst = TypeSubstitution::new();
subst.insert(t_name, source_fn);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, promise_string);
}
#[test]
fn test_infer_return_union() {
let interner = TypeInterner::new();
let t_name = interner.intern_string("T");
let r_name = interner.intern_string("R");
let t_param = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: t_name,
constraint: None,
default: None,
is_const: false,
}));
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: r_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern_fn = interner.function(FunctionShape {
params: Vec::new(),
return_type: infer_r,
type_predicate: None,
this_type: None,
type_params: Vec::new(),
is_constructor: false,
is_method: false,
});
let cond = ConditionalType {
check_type: t_param,
extends_type: pattern_fn,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let union_return = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let source_fn = interner.function(FunctionShape {
params: Vec::new(),
return_type: union_return,
type_predicate: None,
this_type: None,
type_params: Vec::new(),
is_constructor: false,
is_method: false,
});
let mut subst = TypeSubstitution::new();
subst.insert(t_name, source_fn);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert_eq!(result, expected);
}
#[test]
fn test_infer_return_never() {
let interner = TypeInterner::new();
let t_name = interner.intern_string("T");
let r_name = interner.intern_string("R");
let t_param = interner.intern(TypeData::TypeParameter(TypeParamInfo {
name: t_name,
constraint: None,
default: None,
is_const: false,
}));
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: r_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern_fn = interner.function(FunctionShape {
params: Vec::new(),
return_type: infer_r,
type_predicate: None,
this_type: None,
type_params: Vec::new(),
is_constructor: false,
is_method: false,
});
let cond = ConditionalType {
check_type: t_param,
extends_type: pattern_fn,
true_type: infer_r,
false_type: TypeId::UNKNOWN,
is_distributive: false,
};
let cond_type = interner.conditional(cond);
let source_fn = interner.function(FunctionShape {
params: Vec::new(),
return_type: TypeId::NEVER,
type_predicate: None,
this_type: None,
type_params: Vec::new(),
is_constructor: false,
is_method: false,
});
let mut subst = TypeSubstitution::new();
subst.insert(t_name, source_fn);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_distribution_over_large_union() {
let interner = TypeInterner::new();
let lit_a = interner.literal_string("a");
let lit_b = interner.literal_string("b");
let lit_c = interner.literal_string("c");
let lit_d = interner.literal_string("d");
let lit_e = interner.literal_string("e");
let lit_yes = interner.literal_string("yes");
let lit_no = interner.literal_string("no");
let large_union = interner.union(vec![lit_a, lit_b, lit_c, lit_d, lit_e]);
let cond = ConditionalType {
check_type: large_union,
extends_type: TypeId::STRING,
true_type: lit_yes,
false_type: lit_no,
is_distributive: true,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, lit_yes);
}
#[test]
fn test_distribution_over_mixed_union() {
let interner = TypeInterner::new();
let lit_val = interner.literal_string("literal");
let mixed_union = interner.union(vec![TypeId::STRING, TypeId::NUMBER, lit_val]);
let cond = ConditionalType {
check_type: mixed_union,
extends_type: TypeId::STRING,
true_type: mixed_union, false_type: TypeId::NEVER,
is_distributive: true,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result != TypeId::ERROR);
assert!(result != TypeId::NEVER);
}
#[test]
fn test_distribution_over_union_all_false() {
let interner = TypeInterner::new();
let lit_yes = interner.literal_string("yes");
let lit_no = interner.literal_string("no");
let non_string_union = interner.union(vec![TypeId::NUMBER, TypeId::BOOLEAN, TypeId::SYMBOL]);
let cond = ConditionalType {
check_type: non_string_union,
extends_type: TypeId::STRING,
true_type: lit_yes,
false_type: lit_no,
is_distributive: true,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, lit_no);
}
#[test]
fn test_distribution_with_never_check_type() {
let interner = TypeInterner::new();
let lit_yes = interner.literal_string("yes");
let lit_no = interner.literal_string("no");
let cond = ConditionalType {
check_type: TypeId::NEVER,
extends_type: TypeId::STRING,
true_type: lit_yes,
false_type: lit_no,
is_distributive: true,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_distribution_with_any_check_type() {
let interner = TypeInterner::new();
let lit_yes = interner.literal_string("yes");
let lit_no = interner.literal_string("no");
let cond = ConditionalType {
check_type: TypeId::ANY,
extends_type: TypeId::STRING,
true_type: lit_yes,
false_type: lit_no,
is_distributive: true,
};
let result = evaluate_conditional(&interner, &cond);
let expected = interner.union(vec![lit_yes, lit_no]);
assert!(result == expected || result == lit_yes || result == lit_no);
}
#[test]
fn test_distribution_nested_conditional() {
let interner = TypeInterner::new();
let lit_a = interner.literal_string("a");
let lit_b = interner.literal_string("b");
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 check_union = interner.union(vec![lit_a, lit_b, TypeId::NUMBER]);
let inner_cond = ConditionalType {
check_type: check_union,
extends_type: lit_a,
true_type: lit_1,
false_type: lit_2,
is_distributive: true,
};
let inner_result = interner.conditional(inner_cond);
let outer_cond = ConditionalType {
check_type: check_union,
extends_type: TypeId::STRING,
true_type: inner_result,
false_type: lit_3,
is_distributive: true,
};
let result = evaluate_conditional(&interner, &outer_cond);
assert!(result != TypeId::ERROR);
}
#[test]
fn test_distribution_over_union_of_objects() {
let interner = TypeInterner::new();
let x_name = interner.intern_string("x");
let y_name = interner.intern_string("y");
let obj_xy = interner.object(vec![
PropertyInfo::new(x_name, TypeId::STRING),
PropertyInfo::new(y_name, TypeId::NUMBER),
]);
let obj_x_num = interner.object(vec![PropertyInfo::new(x_name, TypeId::NUMBER)]);
let obj_x_str = interner.object(vec![PropertyInfo::new(x_name, TypeId::STRING)]);
let target = interner.object(vec![PropertyInfo::new(x_name, TypeId::STRING)]);
let union = interner.union(vec![obj_xy, obj_x_num, obj_x_str]);
let cond = ConditionalType {
check_type: union,
extends_type: target,
true_type: union,
false_type: TypeId::NEVER,
is_distributive: true,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result != TypeId::ERROR);
assert!(result != TypeId::NEVER);
}
#[test]
fn test_distribution_over_intersection_of_unions() {
let interner = TypeInterner::new();
let lit_yes = interner.literal_string("yes");
let lit_no = interner.literal_string("no");
let union1 = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let union2 = interner.union(vec![TypeId::STRING, TypeId::BOOLEAN]);
let intersection = interner.intersection(vec![union1, union2]);
let cond = ConditionalType {
check_type: intersection,
extends_type: TypeId::STRING,
true_type: lit_yes,
false_type: lit_no,
is_distributive: true,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result == lit_yes || result != TypeId::ERROR);
}
#[test]
fn test_distribution_over_union_with_unknown() {
let interner = TypeInterner::new();
let union = interner.union(vec![TypeId::STRING, TypeId::NUMBER, TypeId::UNKNOWN]);
let cond = ConditionalType {
check_type: union,
extends_type: TypeId::UNKNOWN,
true_type: union,
false_type: TypeId::NEVER,
is_distributive: true,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result != TypeId::NEVER);
assert!(result != TypeId::ERROR);
}
#[test]
fn test_distribution_exclude_pattern() {
let interner = TypeInterner::new();
let check_union = interner.union(vec![TypeId::STRING, TypeId::NUMBER, TypeId::BOOLEAN]);
let cond = ConditionalType {
check_type: check_union,
extends_type: TypeId::NUMBER,
true_type: TypeId::NEVER,
false_type: check_union, is_distributive: true,
};
let result = evaluate_conditional(&interner, &cond);
let expected = interner.union(vec![TypeId::STRING, TypeId::BOOLEAN]);
assert!(result == expected || result != TypeId::ERROR);
}
#[test]
fn test_distribution_extract_pattern() {
let interner = TypeInterner::new();
let check_union = interner.union(vec![TypeId::STRING, TypeId::NUMBER, TypeId::BOOLEAN]);
let target_union = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let cond = ConditionalType {
check_type: check_union,
extends_type: target_union,
true_type: check_union, false_type: TypeId::NEVER,
is_distributive: true,
};
let result = evaluate_conditional(&interner, &cond);
assert!(result != TypeId::ERROR);
}
#[test]
fn test_distribution_with_literal_union() {
let interner = TypeInterner::new();
let lit_a = interner.literal_string("a");
let lit_b = interner.literal_string("b");
let lit_c = interner.literal_string("c");
let lit_d = interner.literal_string("d");
let lit_match = interner.literal_string("match");
let lit_no_match = interner.literal_string("no-match");
let check_union = interner.union(vec![lit_a, lit_c, lit_b, lit_d]);
let extends_union = interner.union(vec![lit_a, lit_b]);
let cond = ConditionalType {
check_type: check_union,
extends_type: extends_union,
true_type: lit_match,
false_type: lit_no_match,
is_distributive: true,
};
let result = evaluate_conditional(&interner, &cond);
let expected = interner.union(vec![lit_match, lit_no_match]);
assert!(result == expected || result != TypeId::ERROR);
}
#[test]
fn test_non_distribution_tuple_wrapped() {
let interner = TypeInterner::new();
let lit_yes = interner.literal_string("yes");
let lit_no = interner.literal_string("no");
let check_union = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
let check_tuple = interner.tuple(vec![TupleElement {
type_id: check_union,
optional: false,
name: None,
rest: false,
}]);
let extends_tuple = interner.tuple(vec![TupleElement {
type_id: TypeId::STRING,
optional: false,
name: None,
rest: false,
}]);
let cond = ConditionalType {
check_type: check_tuple,
extends_type: extends_tuple,
true_type: lit_yes,
false_type: lit_no,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, lit_no);
}
#[test]
fn test_distribution_boolean_special() {
let interner = TypeInterner::new();
let lit_yes = interner.literal_string("yes");
let lit_no = interner.literal_string("no");
let lit_true = interner.literal_boolean(true);
let cond = ConditionalType {
check_type: TypeId::BOOLEAN,
extends_type: lit_true,
true_type: lit_yes,
false_type: lit_no,
is_distributive: true,
};
let result = evaluate_conditional(&interner, &cond);
let expected = interner.union(vec![lit_yes, lit_no]);
assert!(result == expected || result == lit_yes || result == lit_no || result != TypeId::ERROR);
}
#[test]
fn test_distribution_with_function_types() {
let interner = TypeInterner::new();
let lit_function = interner.literal_string("function");
let lit_not_function = interner.literal_string("not-function");
let any_array = interner.array(TypeId::ANY);
let fn_pattern = interner.function(FunctionShape {
type_params: vec![],
params: vec![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 fn1 = 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 fn2 = interner.function(FunctionShape {
type_params: vec![],
params: vec![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_constructor: false,
is_method: false,
});
let check_union = interner.union(vec![fn1, TypeId::STRING, fn2]);
let cond = ConditionalType {
check_type: check_union,
extends_type: fn_pattern,
true_type: lit_function,
false_type: lit_not_function,
is_distributive: true,
};
let result = evaluate_conditional(&interner, &cond);
let expected = interner.union(vec![lit_function, lit_not_function]);
assert!(result == expected || result != TypeId::ERROR);
}
#[test]
fn test_distribution_keyof_result() {
let interner = TypeInterner::new();
let lit_a = interner.literal_string("a");
let lit_b = interner.literal_string("b");
let lit_c = interner.literal_string("c");
let check_union = interner.union(vec![lit_a, lit_b, lit_c]);
let keyof_result = interner.union(vec![lit_a, lit_b]);
let cond = ConditionalType {
check_type: check_union,
extends_type: keyof_result,
true_type: check_union, false_type: TypeId::NEVER,
is_distributive: true,
};
let result = evaluate_conditional(&interner, &cond);
let expected = interner.union(vec![lit_a, lit_b]);
assert!(result == expected || result != TypeId::ERROR);
}
#[test]
fn test_indexed_access_simple_property() {
let interner = TypeInterner::new();
let obj = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
TypeId::STRING,
)]);
let key_a = interner.literal_string("a");
let result = evaluate_index_access(&interner, obj, key_a);
assert_eq!(result, TypeId::STRING);
}
#[test]
fn test_indexed_access_multiple_properties() {
let interner = TypeInterner::new();
let obj = interner.object(vec![
PropertyInfo::new(interner.intern_string("a"), TypeId::STRING),
PropertyInfo::new(interner.intern_string("b"), TypeId::NUMBER),
]);
let key_a = interner.literal_string("a");
let key_b = interner.literal_string("b");
assert_eq!(evaluate_index_access(&interner, obj, key_a), TypeId::STRING);
assert_eq!(evaluate_index_access(&interner, obj, key_b), TypeId::NUMBER);
}
#[test]
fn test_indexed_access_union_key() {
let interner = TypeInterner::new();
let obj = interner.object(vec![
PropertyInfo::new(interner.intern_string("a"), TypeId::STRING),
PropertyInfo::new(interner.intern_string("b"), TypeId::NUMBER),
]);
let key_a = interner.literal_string("a");
let key_b = interner.literal_string("b");
let union_key = interner.union(vec![key_a, key_b]);
let result = evaluate_index_access(&interner, obj, union_key);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert_eq!(result, expected);
}
#[test]
fn test_indexed_access_nested_two_levels() {
let interner = TypeInterner::new();
let inner_obj = interner.object(vec![PropertyInfo::new(
interner.intern_string("inner"),
TypeId::STRING,
)]);
let outer_obj = interner.object(vec![PropertyInfo::new(
interner.intern_string("outer"),
inner_obj,
)]);
let key_outer = interner.literal_string("outer");
let key_inner = interner.literal_string("inner");
let first_access = evaluate_index_access(&interner, outer_obj, key_outer);
assert_eq!(first_access, inner_obj);
let second_access = evaluate_index_access(&interner, first_access, key_inner);
assert_eq!(second_access, TypeId::STRING);
}
#[test]
fn test_indexed_access_deeply_nested() {
let interner = TypeInterner::new();
let d_obj = interner.object(vec![PropertyInfo::new(
interner.intern_string("d"),
TypeId::NUMBER,
)]);
let c_obj = interner.object(vec![PropertyInfo::new(interner.intern_string("c"), d_obj)]);
let b_obj = interner.object(vec![PropertyInfo::new(interner.intern_string("b"), c_obj)]);
let a_obj = interner.object(vec![PropertyInfo::new(interner.intern_string("a"), b_obj)]);
let key_a = interner.literal_string("a");
let key_b = interner.literal_string("b");
let key_c = interner.literal_string("c");
let key_d = interner.literal_string("d");
let r1 = evaluate_index_access(&interner, a_obj, key_a);
let r2 = evaluate_index_access(&interner, r1, key_b);
let r3 = evaluate_index_access(&interner, r2, key_c);
let r4 = evaluate_index_access(&interner, r3, key_d);
assert_eq!(r4, TypeId::NUMBER);
}
#[test]
fn test_indexed_access_array_element() {
let interner = TypeInterner::new();
let string_array = interner.array(TypeId::STRING);
let result = evaluate_index_access(&interner, string_array, TypeId::NUMBER);
assert_eq!(result, TypeId::STRING);
}
#[test]
fn test_indexed_access_tuple_each_element() {
let interner = TypeInterner::new();
let 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,
},
TupleElement {
type_id: TypeId::BOOLEAN,
optional: false,
name: None,
rest: false,
},
]);
let key_0 = interner.literal_number(0.0);
let key_1 = interner.literal_number(1.0);
let key_2 = interner.literal_number(2.0);
assert_eq!(
evaluate_index_access(&interner, tuple, key_0),
TypeId::STRING
);
assert_eq!(
evaluate_index_access(&interner, tuple, key_1),
TypeId::NUMBER
);
assert_eq!(
evaluate_index_access(&interner, tuple, key_2),
TypeId::BOOLEAN
);
}
#[test]
fn test_indexed_access_tuple_number_index() {
let interner = TypeInterner::new();
let 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,
},
TupleElement {
type_id: TypeId::BOOLEAN,
optional: false,
name: None,
rest: false,
},
]);
let result = evaluate_index_access(&interner, tuple, TypeId::NUMBER);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER, TypeId::BOOLEAN]);
assert_eq!(result, expected);
}
#[test]
fn test_indexed_access_with_optional_property() {
let interner = TypeInterner::new();
let obj = interner.object(vec![PropertyInfo::opt(
interner.intern_string("a"),
TypeId::STRING,
)]);
let key_a = interner.literal_string("a");
let result = evaluate_index_access(&interner, obj, key_a);
let expected = interner.union(vec![TypeId::STRING, TypeId::UNDEFINED]);
assert_eq!(result, expected);
}
#[test]
fn test_indexed_access_with_readonly_property() {
let interner = TypeInterner::new();
let obj = interner.object(vec![PropertyInfo::readonly(
interner.intern_string("a"),
TypeId::STRING,
)]);
let key_a = interner.literal_string("a");
let result = evaluate_index_access(&interner, obj, key_a);
assert_eq!(result, TypeId::STRING);
}
#[test]
fn test_indexed_access_union_of_objects() {
let interner = TypeInterner::new();
let obj1 = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
TypeId::STRING,
)]);
let obj2 = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
TypeId::NUMBER,
)]);
let union_obj = interner.union(vec![obj1, obj2]);
let key_a = interner.literal_string("a");
let result = evaluate_index_access(&interner, union_obj, key_a);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert_eq!(result, expected);
}
#[test]
fn test_indexed_access_intersection_object() {
let interner = TypeInterner::new();
let obj1 = interner.object(vec![PropertyInfo::new(
interner.intern_string("a"),
TypeId::STRING,
)]);
let obj2 = interner.object(vec![PropertyInfo::new(
interner.intern_string("b"),
TypeId::NUMBER,
)]);
let intersection = interner.intersection(vec![obj1, obj2]);
let key_a = interner.literal_string("a");
let key_b = interner.literal_string("b");
let result_a = evaluate_index_access(&interner, intersection, key_a);
let result_b = evaluate_index_access(&interner, intersection, key_b);
assert!(result_a != TypeId::ERROR);
assert!(result_b != TypeId::ERROR);
}
#[test]
fn test_indexed_access_string_index_signature() {
let interner = TypeInterner::new();
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::NUMBER,
readonly: false,
}),
number_index: None,
});
let any_key = interner.literal_string("anyKey");
let result = evaluate_index_access(&interner, obj, any_key);
assert_eq!(result, TypeId::NUMBER);
}
#[test]
fn test_indexed_access_number_index_signature() {
let interner = TypeInterner::new();
let 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 key_42 = interner.literal_number(42.0);
let result = evaluate_index_access(&interner, obj, key_42);
assert_eq!(result, TypeId::STRING);
}
#[test]
fn test_indexed_access_property_overrides_index_signature() {
let interner = TypeInterner::new();
let obj = interner.object_with_index(ObjectShape {
symbol: None,
flags: ObjectFlags::empty(),
properties: vec![PropertyInfo::new(
interner.intern_string("a"),
TypeId::BOOLEAN,
)],
string_index: Some(IndexSignature {
key_type: TypeId::STRING,
value_type: TypeId::NUMBER,
readonly: false,
}),
number_index: None,
});
let key_a = interner.literal_string("a");
let result = evaluate_index_access(&interner, obj, key_a);
assert_eq!(result, TypeId::BOOLEAN);
}
#[test]
fn test_indexed_access_nested_with_union_intermediate() {
let interner = TypeInterner::new();
let obj1 = interner.object(vec![PropertyInfo::new(
interner.intern_string("value"),
TypeId::STRING,
)]);
let obj2 = interner.object(vec![PropertyInfo::new(
interner.intern_string("value"),
TypeId::NUMBER,
)]);
let union_data = interner.union(vec![obj1, obj2]);
let outer = interner.object(vec![PropertyInfo::new(
interner.intern_string("data"),
union_data,
)]);
let key_data = interner.literal_string("data");
let key_value = interner.literal_string("value");
let r1 = evaluate_index_access(&interner, outer, key_data);
let r2 = evaluate_index_access(&interner, r1, key_value);
let expected = interner.union(vec![TypeId::STRING, TypeId::NUMBER]);
assert_eq!(r2, expected);
}
#[test]
fn test_indexed_access_literal_types() {
let interner = TypeInterner::new();
let lit_active = interner.literal_string("active");
let lit_inactive = interner.literal_string("inactive");
let status_type = interner.union(vec![lit_active, lit_inactive]);
let obj = interner.object(vec![PropertyInfo::new(
interner.intern_string("status"),
status_type,
)]);
let key_status = interner.literal_string("status");
let result = evaluate_index_access(&interner, obj, key_status);
assert_eq!(result, status_type);
}
#[test]
fn test_indexed_access_function_property() {
let interner = TypeInterner::new();
let fn_type = 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::method(
interner.intern_string("fn"),
fn_type,
)]);
let key_fn = interner.literal_string("fn");
let result = evaluate_index_access(&interner, obj, key_fn);
assert_eq!(result, fn_type);
}
#[test]
fn test_indexed_access_array_method_property() {
let interner = TypeInterner::new();
let string_array = interner.array(TypeId::STRING);
let key_length = interner.literal_string("length");
let result = evaluate_index_access(&interner, string_array, key_length);
assert_eq!(result, TypeId::NUMBER);
}
#[test]
fn test_indexed_access_nested_array() {
let interner = TypeInterner::new();
let string_array = interner.array(TypeId::STRING);
let r1 = evaluate_index_access(&interner, string_array, TypeId::NUMBER);
assert_eq!(r1, TypeId::STRING);
let r2 = evaluate_index_access(&interner, r1, TypeId::NUMBER);
assert_eq!(r2, TypeId::STRING);
}
#[test]
fn test_indexed_access_2d_array() {
let interner = TypeInterner::new();
let number_array = interner.array(TypeId::NUMBER);
let array_2d = interner.array(number_array);
let key_0 = interner.literal_number(0.0);
let r1 = evaluate_index_access(&interner, array_2d, key_0);
assert_eq!(r1, number_array);
let r2 = evaluate_index_access(&interner, r1, key_0);
assert_eq!(r2, TypeId::NUMBER);
}
#[test]
fn test_keyof_template_literal_union_interpolation() {
let interner = TypeInterner::new();
let lit_a = interner.literal_string("A");
let lit_b = interner.literal_string("B");
let lit_c = interner.literal_string("C");
let union_abc = interner.union(vec![lit_a, lit_b, lit_c]);
let template = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("get")),
TemplateSpan::Type(union_abc),
TemplateSpan::Text(interner.intern_string("Done")),
]);
let result = evaluate_keyof(&interner, template);
let expected = evaluate_keyof(&interner, TypeId::STRING);
assert_eq!(result, expected);
}
#[test]
fn test_keyof_union_of_template_literals() {
let interner = TypeInterner::new();
let template1 = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("foo")),
TemplateSpan::Type(TypeId::STRING),
]);
let template2 = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("bar")),
TemplateSpan::Type(TypeId::STRING),
]);
let union_templates = interner.union(vec![template1, template2]);
let result = evaluate_keyof(&interner, union_templates);
let expected = evaluate_keyof(&interner, TypeId::STRING);
assert_eq!(result, expected);
}
#[test]
fn test_conditional_infer_template_with_keyof_result() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("K");
let infer_k = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("get")),
TemplateSpan::Type(infer_k),
TemplateSpan::Text(interner.intern_string("Done")),
]);
let cond = ConditionalType {
check_type: t_param,
extends_type: pattern,
true_type: infer_k,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let input = interner.literal_string("getFooDone");
subst.insert(t_name, input);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.literal_string("Foo");
assert_eq!(result, expected);
}
#[test]
fn test_template_literal_with_uppercase_intrinsic_pattern() {
let interner = TypeInterner::new();
let lit_a = interner.literal_string("a");
let lit_b = interner.literal_string("b");
let input_union = interner.union(vec![lit_a, lit_b]);
let template = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("on")),
TemplateSpan::Type(input_union),
TemplateSpan::Text(interner.intern_string("Change")),
]);
match interner.lookup(template) {
Some(TypeData::Union(members_id)) => {
let members = interner.type_list(members_id);
assert_eq!(members.len(), 2, "Expected 2 members in expanded union");
}
_ => panic!(
"Expected Union type for template with union interpolation, got {:?}",
interner.lookup(template)
),
}
}
#[test]
fn test_nested_conditional_template_literal_infer() {
let interner = TypeInterner::new();
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 infer_r_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_r_name,
constraint: None,
default: None,
is_const: false,
}));
let infer_s_name = interner.intern_string("S");
let infer_s = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_s_name,
constraint: None,
default: None,
is_const: false,
}));
let outer_pattern = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("prefix")),
TemplateSpan::Type(infer_r),
]);
let inner_pattern = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("suffix")),
TemplateSpan::Type(infer_s),
]);
let inner_cond = ConditionalType {
check_type: infer_r,
extends_type: inner_pattern,
true_type: infer_s,
false_type: TypeId::NEVER,
is_distributive: false,
};
let outer_cond = ConditionalType {
check_type: t_param,
extends_type: outer_pattern,
true_type: interner.conditional(inner_cond),
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(outer_cond);
let mut subst = TypeSubstitution::new();
let input = interner.literal_string("prefixsuffixValue");
subst.insert(t_name, input);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.literal_string("Value");
assert_eq!(result, expected);
}
#[test]
fn test_template_literal_conditional_extends_template() {
let interner = TypeInterner::new();
let infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("prefix")),
TemplateSpan::Type(infer_r),
]);
let check_type = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("prefix")),
TemplateSpan::Type(TypeId::STRING),
]);
let cond = ConditionalType {
check_type,
extends_type: pattern,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, TypeId::STRING);
}
#[test]
fn test_template_literal_escape_sequences() {
let interner = TypeInterner::new();
let template = interner.template_literal(vec![TemplateSpan::Text(
interner.intern_string("line1\\nline2"),
)]);
if let Some(TypeData::Literal(LiteralValue::String(atom))) = interner.lookup(template) {
let resolved = interner.resolve_atom_ref(atom);
assert!(
resolved.contains("\\n"),
"Escape sequence should be preserved"
);
} else {
panic!(
"Expected string literal for text-only template, got {:?}",
interner.lookup(template)
);
}
}
#[test]
fn test_template_literal_infer_with_special_chars() {
let interner = TypeInterner::new();
let infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("data-")),
TemplateSpan::Type(infer_r),
]);
let input = interner.literal_string("data-value");
let cond = ConditionalType {
check_type: input,
extends_type: pattern,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
let expected = interner.literal_string("value");
assert_eq!(result, expected);
}
#[test]
fn test_complex_keyof_template_infer_composition() {
let interner = TypeInterner::new();
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 infer_k_name = interner.intern_string("K");
let infer_k = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_k_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("get")),
TemplateSpan::Type(infer_k),
]);
let cond = ConditionalType {
check_type: t_param,
extends_type: pattern,
true_type: infer_k,
false_type: TypeId::NEVER,
is_distributive: true,
};
let obj = interner.object(vec![
PropertyInfo::new(interner.intern_string("getName"), TypeId::STRING),
PropertyInfo::new(interner.intern_string("getAge"), TypeId::NUMBER),
]);
let keys_of_obj = evaluate_keyof(&interner, obj);
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
subst.insert(t_name, keys_of_obj);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
if let Some(TypeData::Union(members)) = interner.lookup(result) {
let members = interner.type_list(members);
assert_eq!(members.len(), 2);
} else {
panic!("Expected union of extracted names");
}
}
#[test]
fn test_template_literal_with_number_interpolation() {
let interner = TypeInterner::new();
let template = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("item")),
TemplateSpan::Type(TypeId::NUMBER),
]);
if let Some(TypeData::TemplateLiteral(spans)) = interner.lookup(template) {
let spans = interner.template_list(spans);
assert_eq!(spans.len(), 2);
} else {
panic!("Expected template literal");
}
}
#[test]
fn test_template_literal_two_infers_union_input() {
let interner = TypeInterner::new();
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 infer_a_name = interner.intern_string("A");
let infer_a = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_a_name,
constraint: None,
default: None,
is_const: false,
}));
let infer_b_name = interner.intern_string("B");
let infer_b = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_b_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = interner.template_literal(vec![
TemplateSpan::Type(infer_a),
TemplateSpan::Text(interner.intern_string("-")),
TemplateSpan::Type(infer_b),
]);
let result_template = interner.template_literal(vec![
TemplateSpan::Type(infer_a),
TemplateSpan::Text(interner.intern_string("-")),
TemplateSpan::Type(infer_b),
]);
let cond = ConditionalType {
check_type: t_param,
extends_type: pattern,
true_type: result_template,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let foo_bar = interner.literal_string("foo-bar");
let baz_qux = interner.literal_string("baz-qux");
subst.insert(t_name, interner.union(vec![foo_bar, baz_qux]));
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
if let Some(TypeData::Union(members)) = interner.lookup(result) {
let members = interner.type_list(members);
assert_eq!(members.len(), 2);
} else {
panic!("Expected union");
}
}
#[test]
fn test_template_literal_constrained_infer() {
let interner = TypeInterner::new();
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 infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: Some(TypeId::STRING), default: None,
is_const: false,
}));
let pattern = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("prefix")),
TemplateSpan::Type(infer_r),
]);
let cond = ConditionalType {
check_type: t_param,
extends_type: pattern,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let input = interner.literal_string("prefixValue");
subst.insert(t_name, input);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.literal_string("Value");
assert_eq!(result, expected);
}
#[test]
fn test_keyof_object_with_template_literal_computed_keys() {
let interner = TypeInterner::new();
let obj = interner.object(vec![
PropertyInfo::new(interner.intern_string("getName"), TypeId::STRING),
PropertyInfo::new(interner.intern_string("getAge"), TypeId::NUMBER),
]);
let result = evaluate_keyof(&interner, obj);
if let Some(TypeData::Union(members)) = interner.lookup(result) {
let members = interner.type_list(members);
assert_eq!(members.len(), 2);
} else {
panic!("Expected union of property names");
}
}
#[test]
fn test_empty_template_literal() {
let interner = TypeInterner::new();
let template = interner.template_literal(vec![]);
if let Some(TypeData::Literal(LiteralValue::String(atom))) = interner.lookup(template) {
let s = interner.resolve_atom_ref(atom);
assert_eq!(
s.as_ref(),
"",
"Empty template literal should be empty string"
);
} else {
panic!(
"Expected empty string literal for empty template literal, got {:?}",
interner.lookup(template)
);
}
}
#[test]
fn test_template_literal_only_text() {
let interner = TypeInterner::new();
let template =
interner.template_literal(vec![TemplateSpan::Text(interner.intern_string("hello"))]);
if let Some(TypeData::Literal(LiteralValue::String(atom))) = interner.lookup(template) {
let s = interner.resolve_atom_ref(atom);
assert_eq!(
s.as_ref(),
"hello",
"Text-only template literal should be 'hello' string literal"
);
} else {
panic!(
"Expected string literal for text-only template literal, got {:?}",
interner.lookup(template)
);
}
let result = evaluate_keyof(&interner, template);
let expected = evaluate_keyof(&interner, TypeId::STRING);
assert_eq!(result, expected);
}
#[test]
fn test_template_literal_only_type_interpolation() {
let interner = TypeInterner::new();
let template = interner.template_literal(vec![TemplateSpan::Type(TypeId::STRING)]);
if let Some(TypeData::TemplateLiteral(spans)) = interner.lookup(template) {
let spans = interner.template_list(spans);
assert_eq!(spans.len(), 1);
} else {
panic!("Expected template literal");
}
let result = evaluate_keyof(&interner, template);
let expected = evaluate_keyof(&interner, TypeId::STRING);
assert_eq!(result, expected);
}
#[test]
fn test_distributive_conditional_template_union() {
let interner = TypeInterner::new();
let infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = interner.template_literal(vec![
TemplateSpan::Type(infer_r),
TemplateSpan::Text(interner.intern_string("x")),
]);
let lit_ax = interner.literal_string("ax");
let lit_bx = interner.literal_string("bx");
let lit_c = interner.literal_string("c");
let input_union = interner.union(vec![lit_ax, lit_bx, lit_c]);
let cond = ConditionalType {
check_type: input_union,
extends_type: pattern,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: true,
};
let result = evaluate_conditional(&interner, &cond);
if let Some(TypeData::Union(members)) = interner.lookup(result) {
let members = interner.type_list(members);
assert_eq!(members.len(), 2);
} else {
panic!("Expected union");
}
}
#[test]
fn test_non_distributive_conditional_template_union() {
let interner = TypeInterner::new();
let infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = interner.template_literal(vec![
TemplateSpan::Type(infer_r),
TemplateSpan::Text(interner.intern_string("x")),
]);
let lit_ax = interner.literal_string("ax");
let lit_bx = interner.literal_string("bx");
let input_union = interner.union(vec![lit_ax, lit_bx]);
let cond = ConditionalType {
check_type: input_union,
extends_type: pattern,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false, };
let result = evaluate_conditional(&interner, &cond);
let lit_a = interner.literal_string("a");
let lit_b = interner.literal_string("b");
let expected_union = interner.union(vec![lit_a, lit_b]);
assert!(
result == TypeId::NEVER || result == TypeId::STRING || result == expected_union,
"Expected never, string, or \"a\" | \"b\", got {result:?}"
);
}
#[test]
fn test_template_literal_with_boolean_interpolation() {
let interner = TypeInterner::new();
let template = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("flag")),
TemplateSpan::Type(TypeId::BOOLEAN),
]);
match interner.lookup(template) {
Some(TypeData::Union(list_id)) => {
let members = interner.type_list(list_id);
assert_eq!(members.len(), 2, "Expected 2 members for boolean expansion");
}
other => panic!("Expected Union type for `flag${{boolean}}`, got {other:?}"),
}
}
#[test]
fn test_template_literal_literal_union_pattern() {
let interner = TypeInterner::new();
let lit_a = interner.literal_string("a");
let lit_b = interner.literal_string("b");
let union_ab = interner.union(vec![lit_a, lit_b]);
let pattern = interner.template_literal(vec![
TemplateSpan::Type(union_ab),
TemplateSpan::Text(interner.intern_string("x")),
]);
let input = interner.literal_string("ax");
let cond = ConditionalType {
check_type: input,
extends_type: pattern,
true_type: input,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, input);
}
#[test]
fn test_template_literal_index_access_scenario() {
let interner = TypeInterner::new();
let obj = interner.object(vec![
PropertyInfo::new(interner.intern_string("item0"), TypeId::STRING),
PropertyInfo::new(interner.intern_string("item1"), TypeId::NUMBER),
]);
let key = interner.literal_string("item0");
let result = evaluate_index_access(&interner, obj, key);
assert_eq!(result, TypeId::STRING);
}
#[test]
fn test_template_literal_mapped_type_pattern() {
let interner = TypeInterner::new();
let infer_s_name = interner.intern_string("S");
let infer_s = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_s_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern_template = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("get")),
TemplateSpan::Type(infer_s),
]);
if let Some(TypeData::TemplateLiteral(spans)) = interner.lookup(pattern_template) {
let spans = interner.template_list(spans);
assert_eq!(spans.len(), 2);
} else {
panic!("Expected template literal");
}
}
#[test]
fn test_template_literal_multiple_infers_complex_pattern() {
let interner = TypeInterner::new();
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 infer_a_name = interner.intern_string("A");
let infer_a = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_a_name,
constraint: None,
default: None,
is_const: false,
}));
let infer_b_name = interner.intern_string("B");
let infer_b = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_b_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("start")),
TemplateSpan::Type(infer_a),
TemplateSpan::Text(interner.intern_string("-middle")),
TemplateSpan::Type(infer_b),
TemplateSpan::Text(interner.intern_string("-end")),
]);
let cond = ConditionalType {
check_type: t_param,
extends_type: pattern,
true_type: infer_a, false_type: TypeId::NEVER,
is_distributive: true,
};
let cond_type = interner.conditional(cond);
let mut subst = TypeSubstitution::new();
let input = interner.literal_string("startFOO-middleBAR-end");
subst.insert(t_name, input);
let instantiated = instantiate_type(&interner, cond_type, &subst);
let result = evaluate_type(&interner, instantiated);
let expected = interner.literal_string("FOO");
assert_eq!(result, expected);
}
#[test]
fn test_template_literal_nested_union_interpolation() {
let interner = TypeInterner::new();
let lit_a = interner.literal_string("a");
let lit_b = interner.literal_string("b");
let union_ab = interner.union(vec![lit_a, lit_b]);
let lit_c = interner.literal_string("c");
let lit_d = interner.literal_string("d");
let union_cd = interner.union(vec![lit_c, lit_d]);
let nested_union = interner.union(vec![union_ab, union_cd]);
let template = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("prefix")),
TemplateSpan::Type(nested_union),
]);
match interner.lookup(template) {
Some(TypeData::Union(members_id)) => {
let members = interner.type_list(members_id);
assert_eq!(members.len(), 4, "Expected 4 members in expanded union");
}
_ => panic!(
"Expected Union type for template with nested union interpolation, got {:?}",
interner.lookup(template)
),
}
}
#[test]
fn test_template_literal_matches_template_literal() {
let interner = TypeInterner::new();
let infer_name = interner.intern_string("R");
let infer_r = interner.intern(TypeData::Infer(TypeParamInfo {
name: infer_name,
constraint: None,
default: None,
is_const: false,
}));
let pattern = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("foo")),
TemplateSpan::Type(infer_r),
]);
let check_template = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("foo")),
TemplateSpan::Type(TypeId::STRING),
]);
let cond = ConditionalType {
check_type: check_template,
extends_type: pattern,
true_type: infer_r,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, TypeId::STRING);
}
#[test]
fn test_keyof_template_literal_number_union_interpolation() {
let interner = TypeInterner::new();
let lit_0 = interner.literal_number(0.0);
let lit_1 = interner.literal_number(1.0);
let lit_2 = interner.literal_number(2.0);
let union_012 = interner.union(vec![lit_0, lit_1, lit_2]);
let template = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("item")),
TemplateSpan::Type(union_012),
]);
let result = evaluate_keyof(&interner, template);
let expected = evaluate_keyof(&interner, TypeId::STRING);
assert_eq!(result, expected);
}
#[test]
fn test_template_literal_conditional_same_pattern() {
let interner = TypeInterner::new();
let template1 = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("prefix")),
TemplateSpan::Type(TypeId::STRING),
]);
let template2 = interner.template_literal(vec![
TemplateSpan::Text(interner.intern_string("prefix")),
TemplateSpan::Type(TypeId::STRING),
]);
let cond = ConditionalType {
check_type: template1,
extends_type: template2,
true_type: TypeId::STRING,
false_type: TypeId::NEVER,
is_distributive: false,
};
let result = evaluate_conditional(&interner, &cond);
assert_eq!(result, TypeId::STRING);
}
#[test]
fn test_tail_recursive_conditional() {
let interner = TypeInterner::new();
let mut current_type = TypeId::STRING;
for _ in 0..60 {
let cond = ConditionalType {
check_type: TypeId::STRING,
extends_type: TypeId::NUMBER,
true_type: TypeId::NEVER,
false_type: current_type,
is_distributive: false,
};
current_type = interner.conditional(cond);
}
let mut evaluator = TypeEvaluator::new(&interner);
let result = evaluator.evaluate(current_type);
assert_eq!(result, TypeId::STRING);
}
#[test]
fn test_intersection_reduction_disjoint_primitives() {
let interner = TypeInterner::new();
let intersection = interner.intersection(vec![TypeId::STRING, TypeId::NUMBER]);
let mut evaluator = TypeEvaluator::new(&interner);
let result = evaluator.evaluate(intersection);
assert_eq!(result, TypeId::NEVER);
}
#[test]
fn test_intersection_reduction_any() {
let interner = TypeInterner::new();
let intersection = interner.intersection(vec![TypeId::STRING, TypeId::ANY]);
let mut evaluator = TypeEvaluator::new(&interner);
let result = evaluator.evaluate(intersection);
assert_eq!(result, TypeId::ANY);
}
#[test]
fn test_union_reduction_duplicates() {
let interner = TypeInterner::new();
let union = interner.union(vec![TypeId::STRING, TypeId::STRING]);
let mut evaluator = TypeEvaluator::new(&interner);
let result = evaluator.evaluate(union);
assert_eq!(result, TypeId::STRING);
}
#[test]
fn test_union_reduction_literal_into_base() {
let interner = TypeInterner::new();
let hello = interner.literal_string("hello");
let union = interner.union(vec![hello, TypeId::STRING]);
let mut evaluator = TypeEvaluator::new(&interner);
let result = evaluator.evaluate(union);
assert_eq!(result, TypeId::STRING);
}