use super::*;
use crate::intern::TypeInterner;
use crate::subtype::SubtypeChecker;
use crate::types::{FunctionShape, ParamInfo, TypeData};
#[test]
fn test_function_construction() {
let interner = TypeInterner::new();
let func = 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,
});
if let Some(TypeData::Function(shape_id)) = interner.lookup(func) {
let shape = interner.function_shape(shape_id);
assert_eq!(shape.params.len(), 1);
assert_eq!(shape.return_type, TypeId::STRING);
} else {
panic!("Expected function type");
}
}
#[test]
fn test_function_no_params() {
let interner = TypeInterner::new();
let func = interner.function(FunctionShape {
params: Vec::new(),
this_type: None,
return_type: TypeId::VOID,
type_params: Vec::new(),
type_predicate: None,
is_constructor: false,
is_method: false,
});
if let Some(TypeData::Function(shape_id)) = interner.lookup(func) {
let shape = interner.function_shape(shape_id);
assert_eq!(shape.params.len(), 0);
} else {
panic!("Expected function type");
}
}
#[test]
fn test_function_multiple_params() {
let interner = TypeInterner::new();
let func = interner.function(FunctionShape {
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,
},
ParamInfo {
name: Some(interner.intern_string("c")),
type_id: TypeId::BOOLEAN,
optional: false,
rest: false,
},
],
this_type: None,
return_type: TypeId::VOID,
type_params: Vec::new(),
type_predicate: None,
is_constructor: false,
is_method: false,
});
if let Some(TypeData::Function(shape_id)) = interner.lookup(func) {
let shape = interner.function_shape(shape_id);
assert_eq!(shape.params.len(), 3);
} else {
panic!("Expected function type");
}
}
#[test]
fn test_function_same_type_is_subtype() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let func = 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,
});
assert!(
checker.is_subtype_of(func, func),
"Function should be subtype of itself"
);
}
#[test]
fn test_function_return_type_covariance() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let string_or_number = interner.union2(TypeId::STRING, TypeId::NUMBER);
let string_return = 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,
});
let union_return = 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: string_or_number,
type_params: Vec::new(),
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(
checker.is_subtype_of(string_return, union_return),
"Function with string return should be subtype of function with string | number return"
);
}
#[test]
fn test_function_param_type_contravariance() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let string_or_number = interner.union2(TypeId::STRING, TypeId::NUMBER);
let union_param = interner.function(FunctionShape {
params: vec![ParamInfo {
name: Some(interner.intern_string("x")),
type_id: string_or_number,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::VOID,
type_params: Vec::new(),
type_predicate: None,
is_constructor: false,
is_method: false,
});
let string_param = interner.function(FunctionShape {
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_params: Vec::new(),
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(
checker.is_subtype_of(union_param, string_param),
"Function with union param should be subtype of function with string param"
);
}
#[test]
fn test_function_optional_param() {
let interner = TypeInterner::new();
let func = interner.function(FunctionShape {
params: vec![
ParamInfo {
name: Some(interner.intern_string("required")),
type_id: TypeId::STRING,
optional: false,
rest: false,
},
ParamInfo {
name: Some(interner.intern_string("optional")),
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,
});
if let Some(TypeData::Function(shape_id)) = interner.lookup(func) {
let shape = interner.function_shape(shape_id);
assert_eq!(shape.params.len(), 2);
assert!(!shape.params[0].optional);
assert!(shape.params[1].optional);
} else {
panic!("Expected function type");
}
}
#[test]
fn test_function_rest_param() {
let interner = TypeInterner::new();
let func = interner.function(FunctionShape {
params: vec![
ParamInfo {
name: Some(interner.intern_string("first")),
type_id: TypeId::STRING,
optional: false,
rest: false,
},
ParamInfo {
name: Some(interner.intern_string("rest")),
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,
});
if let Some(TypeData::Function(shape_id)) = interner.lookup(func) {
let shape = interner.function_shape(shape_id);
assert_eq!(shape.params.len(), 2);
assert!(!shape.params[0].rest);
assert!(shape.params[1].rest);
} else {
panic!("Expected function type");
}
}
#[test]
fn test_function_assignable_to_any() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let func = 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,
});
assert!(
checker.is_subtype_of(func, TypeId::ANY),
"Function should be subtype of any"
);
}
#[test]
fn test_any_assignable_to_function() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let func = 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,
});
assert!(
checker.is_subtype_of(TypeId::ANY, func),
"any should be subtype of function"
);
}
#[test]
fn test_function_identity_stability() {
let interner = TypeInterner::new();
let shape = 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,
};
let func1 = interner.function(shape.clone());
let func2 = interner.function(shape);
assert_eq!(
func1, func2,
"Same function construction should produce same TypeId"
);
}
#[test]
fn test_function_not_subtype_different_param_count() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let one_param = 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::VOID,
type_params: Vec::new(),
type_predicate: None,
is_constructor: false,
is_method: false,
});
let two_params = interner.function(FunctionShape {
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::VOID,
type_params: Vec::new(),
type_predicate: None,
is_constructor: false,
is_method: false,
});
let _ = (
checker.is_subtype_of(one_param, two_params),
checker.is_subtype_of(two_params, one_param),
);
}
#[test]
fn test_never_assignable_to_function() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let func = 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,
});
assert!(
checker.is_subtype_of(TypeId::NEVER, func),
"never should be subtype of function"
);
}
#[test]
fn test_method_flag() {
let interner = TypeInterner::new();
let method = 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: true,
});
if let Some(TypeData::Function(shape_id)) = interner.lookup(method) {
let shape = interner.function_shape(shape_id);
assert!(shape.is_method);
} else {
panic!("Expected function type");
}
}
#[test]
fn test_constructor_flag() {
let interner = TypeInterner::new();
let constructor = interner.function(FunctionShape {
params: vec![ParamInfo {
name: Some(interner.intern_string("value")),
type_id: TypeId::STRING,
optional: false,
rest: false,
}],
this_type: None,
return_type: TypeId::OBJECT,
type_params: Vec::new(),
type_predicate: None,
is_constructor: true,
is_method: false,
});
if let Some(TypeData::Function(shape_id)) = interner.lookup(constructor) {
let shape = interner.function_shape(shape_id);
assert!(shape.is_constructor);
} else {
panic!("Expected function type");
}
}
#[test]
fn test_function_void_return() {
let interner = TypeInterner::new();
let func = 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::VOID,
type_params: Vec::new(),
type_predicate: None,
is_constructor: false,
is_method: false,
});
if let Some(TypeData::Function(shape_id)) = interner.lookup(func) {
let shape = interner.function_shape(shape_id);
assert_eq!(shape.return_type, TypeId::VOID);
} else {
panic!("Expected function type");
}
}