use super::*;
use crate::TypeInterner;
fn type_predicate(interner: &TypeInterner, param_name: &str, type_id: TypeId) -> TypePredicate {
TypePredicate {
asserts: false,
target: TypePredicateTarget::Identifier(interner.intern_string(param_name)),
type_id: Some(type_id),
parameter_index: None,
}
}
fn asserts_predicate(interner: &TypeInterner, param_name: &str, type_id: TypeId) -> TypePredicate {
TypePredicate {
asserts: true,
target: TypePredicateTarget::Identifier(interner.intern_string(param_name)),
type_id: Some(type_id),
parameter_index: None,
}
}
fn bare_asserts(interner: &TypeInterner, param_name: &str) -> TypePredicate {
TypePredicate {
asserts: true,
target: TypePredicateTarget::Identifier(interner.intern_string(param_name)),
type_id: None,
parameter_index: None,
}
}
fn fn_with_predicate(
interner: &TypeInterner,
param_name: &str,
param_type: TypeId,
return_type: TypeId,
type_predicate: Option<TypePredicate>,
) -> FunctionShape {
let param_name_atom = interner.intern_string(param_name);
FunctionShape {
type_params: vec![],
params: vec![ParamInfo::required(param_name_atom, param_type)],
this_type: None,
return_type,
type_predicate,
is_constructor: false,
is_method: false,
}
}
#[test]
fn test_type_guard_more_specific_than_no_predicate() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let string_param = interner.intern_string("x");
let helper = type_predicate(&interner, "x", TypeId::STRING);
let helper_asserts = asserts_predicate(&interner, "x", TypeId::STRING);
let helper_bare_asserts = bare_asserts(&interner, "x");
let helper_fn_with_predicate = fn_with_predicate(
&interner,
"x",
TypeId::STRING,
TypeId::STRING,
Some(helper.clone()),
);
assert_eq!(helper.type_id, Some(TypeId::STRING));
assert!(helper_asserts.asserts);
assert!(helper_bare_asserts.asserts);
assert!(helper_fn_with_predicate.type_predicate.is_some());
let source_fn = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo::required(string_param, TypeId::STRING)],
this_type: None,
return_type: TypeId::STRING,
type_predicate: Some(TypePredicate {
parameter_index: None,
asserts: false,
target: TypePredicateTarget::Identifier(string_param),
type_id: Some(TypeId::STRING),
}),
is_constructor: false,
is_method: false,
});
let target_fn = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo::required(string_param, TypeId::STRING)],
this_type: None,
return_type: TypeId::BOOLEAN,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(
!checker.is_subtype_of(source_fn, target_fn),
"Function with type guard should NOT be assignable to function without guard"
);
}
#[test]
fn test_no_predicate_not_compatible_with_type_guard() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let string_param = interner.intern_string("x");
let source_fn = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo::required(string_param, TypeId::STRING)],
this_type: None,
return_type: TypeId::BOOLEAN,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let target_fn = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo::required(string_param, TypeId::STRING)],
this_type: None,
return_type: TypeId::STRING,
type_predicate: Some(TypePredicate {
parameter_index: None,
asserts: false,
target: TypePredicateTarget::Identifier(string_param),
type_id: Some(TypeId::STRING),
}),
is_constructor: false,
is_method: false,
});
assert!(
!checker.is_subtype_of(source_fn, target_fn),
"Function returning boolean should NOT be assignable to function with type guard returning string"
);
}
#[test]
fn test_no_predicate_compatible_with_type_guard_matching_return() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let param_x = interner.intern_string("x");
let source_fn = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo::required(param_x, TypeId::UNKNOWN)],
this_type: None,
return_type: TypeId::STRING,
type_predicate: None,
is_constructor: false,
is_method: false,
});
let target_fn = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo::required(param_x, TypeId::UNKNOWN)],
this_type: None,
return_type: TypeId::STRING,
type_predicate: Some(TypePredicate {
parameter_index: None,
asserts: false,
target: TypePredicateTarget::Identifier(param_x),
type_id: Some(TypeId::STRING),
}),
is_constructor: false,
is_method: false,
});
assert!(
checker.is_subtype_of(source_fn, target_fn),
"Function returning string should be assignable to function with type guard returning string"
);
}
#[test]
fn test_type_guard_narrowing_is_compatible() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let animal = interner.object(vec![]);
let dog = interner.object(vec![]);
let param_x = interner.intern_string("x");
let source_fn = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo::required(param_x, animal)],
this_type: None,
return_type: dog,
type_predicate: Some(TypePredicate {
parameter_index: None,
asserts: false,
target: TypePredicateTarget::Identifier(param_x),
type_id: Some(dog),
}),
is_constructor: false,
is_method: false,
});
let target_fn = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo::required(param_x, animal)],
this_type: None,
return_type: animal,
type_predicate: Some(TypePredicate {
parameter_index: None,
asserts: false,
target: TypePredicateTarget::Identifier(param_x),
type_id: Some(animal),
}),
is_constructor: false,
is_method: false,
});
assert!(
checker.is_subtype_of(source_fn, target_fn),
"Narrower type guard should be assignable to wider type guard"
);
}
#[test]
fn test_type_guard_different_parameters_incompatible() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let param_x = interner.intern_string("x");
let param_y = interner.intern_string("y");
let source_fn = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo::required(param_x, TypeId::STRING)],
this_type: None,
return_type: TypeId::STRING,
type_predicate: Some(TypePredicate {
parameter_index: None,
asserts: false,
target: TypePredicateTarget::Identifier(param_x),
type_id: Some(TypeId::STRING),
}),
is_constructor: false,
is_method: false,
});
let target_fn = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo::required(param_y, TypeId::STRING)],
this_type: None,
return_type: TypeId::STRING,
type_predicate: Some(TypePredicate {
parameter_index: None,
asserts: false,
target: TypePredicateTarget::Identifier(param_y),
type_id: Some(TypeId::STRING),
}),
is_constructor: false,
is_method: false,
});
assert!(
!checker.is_subtype_of(source_fn, target_fn),
"Type guards on different parameters should be incompatible"
);
}
#[test]
fn test_asserts_more_specific_than_type_guard() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let param_x = interner.intern_string("x");
let source_fn = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo::required(param_x, TypeId::UNKNOWN)],
this_type: None,
return_type: TypeId::STRING,
type_predicate: Some(TypePredicate {
parameter_index: None,
asserts: true,
target: TypePredicateTarget::Identifier(param_x),
type_id: Some(TypeId::STRING),
}),
is_constructor: false,
is_method: false,
});
let target_fn = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo::required(param_x, TypeId::UNKNOWN)],
this_type: None,
return_type: TypeId::STRING,
type_predicate: Some(TypePredicate {
parameter_index: None,
asserts: false,
target: TypePredicateTarget::Identifier(param_x),
type_id: Some(TypeId::STRING),
}),
is_constructor: false,
is_method: false,
});
assert!(
!checker.is_subtype_of(source_fn, target_fn),
"Assertion should NOT be assignable to type guard (incompatible kinds)"
);
}
#[test]
fn test_type_guard_not_compatible_with_asserts() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let param_x = interner.intern_string("x");
let source_fn = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo::required(param_x, TypeId::UNKNOWN)],
this_type: None,
return_type: TypeId::STRING,
type_predicate: Some(TypePredicate {
parameter_index: None,
asserts: false,
target: TypePredicateTarget::Identifier(param_x),
type_id: Some(TypeId::STRING),
}),
is_constructor: false,
is_method: false,
});
let target_fn = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo::required(param_x, TypeId::UNKNOWN)],
this_type: None,
return_type: TypeId::STRING,
type_predicate: Some(TypePredicate {
parameter_index: None,
asserts: true,
target: TypePredicateTarget::Identifier(param_x),
type_id: Some(TypeId::STRING),
}),
is_constructor: false,
is_method: false,
});
assert!(
!checker.is_subtype_of(source_fn, target_fn),
"Type guard should NOT be assignable to assertion (incompatible kinds)"
);
}
#[test]
fn test_bare_asserts_compatibility() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let param_x = interner.intern_string("x");
let source_fn = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo::required(param_x, TypeId::UNKNOWN)],
this_type: None,
return_type: TypeId::VOID,
type_predicate: Some(TypePredicate {
parameter_index: None,
asserts: true,
target: TypePredicateTarget::Identifier(param_x),
type_id: None, }),
is_constructor: false,
is_method: false,
});
let target_fn = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo::required(param_x, TypeId::UNKNOWN)],
this_type: None,
return_type: TypeId::STRING,
type_predicate: Some(TypePredicate {
parameter_index: None,
asserts: true,
target: TypePredicateTarget::Identifier(param_x),
type_id: Some(TypeId::STRING),
}),
is_constructor: false,
is_method: false,
});
assert!(
!checker.is_subtype_of(source_fn, target_fn),
"Bare assertion should NOT be assignable to typed assertion"
);
}
#[test]
#[ignore = "Type guard in overloads not fully implemented"]
fn test_type_guard_in_overloads() {
let interner = TypeInterner::new();
let mut checker = SubtypeChecker::new(&interner);
let param_value = interner.intern_string("value");
let type_guard_fn = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo::required(param_value, TypeId::UNKNOWN)],
this_type: None,
return_type: TypeId::STRING,
type_predicate: Some(TypePredicate {
parameter_index: None,
asserts: false,
target: TypePredicateTarget::Identifier(param_value),
type_id: Some(TypeId::STRING),
}),
is_constructor: false,
is_method: false,
});
let regular_fn = interner.function(FunctionShape {
type_params: vec![],
params: vec![ParamInfo::required(param_value, TypeId::UNKNOWN)],
this_type: None,
return_type: TypeId::STRING,
type_predicate: None,
is_constructor: false,
is_method: false,
});
assert!(
!checker.is_subtype_of(type_guard_fn, regular_fn),
"Type guard function should NOT be assignable to regular function"
);
assert!(
checker.is_subtype_of(regular_fn, type_guard_fn),
"Regular function should be assignable to type guard function"
);
}