use rex::{
BuiltinTypeId, Engine, EngineError, GasMeter, Heap, Library, Parser, Pointer, Token, Type,
TypeError, Value,
};
fn strip_type_span(mut err: TypeError) -> TypeError {
while let TypeError::Spanned { error, .. } = err {
err = *error;
}
err
}
async fn eval(code: &str) -> Result<(Heap, Pointer, Type), EngineError> {
let tokens = Token::tokenize(code).unwrap();
let mut parser = Parser::new(tokens);
let program = parser.parse_program(&mut GasMeter::default()).unwrap();
let mut engine = Engine::with_prelude(()).unwrap();
let mut library = Library::global();
library.add_decls(program.decls.clone());
engine.inject_library(library)?;
let mut gas = GasMeter::default();
let (pointer, ty) = rex::Evaluator::new_with_compiler(
rex::RuntimeEnv::new(engine.clone()),
rex::Compiler::new(engine.clone()),
)
.eval(program.expr.as_ref(), &mut gas)
.await
.map_err(|err| err.into_engine_error())?;
let heap = engine.into_heap();
Ok((heap, pointer, ty))
}
#[tokio::test]
async fn spec_record_update_requires_refinement_for_sum_types() {
let code = r#"
type Foo = Bar { x: i32 } | Baz { x: i32 }
let
f = \ (foo : Foo) -> { foo with { x = 2 } }
in
f (Bar { x = 1 })
"#;
let err = match eval(code).await {
Ok(_) => panic!("expected error"),
Err(e) => e,
};
let EngineError::Type(te) = err else {
panic!("expected type error, got {err}");
};
assert!(matches!(
strip_type_span(te),
TypeError::FieldNotKnown { .. }
));
}
#[tokio::test]
async fn spec_typeclass_instance_overlap_is_rejected() {
let code = r#"
class C a
c : i32
instance C i32
c = 0
instance C i32
c = 1
c
"#;
let err = match eval(code).await {
Ok(_) => panic!("expected error"),
Err(e) => e,
};
assert!(matches!(err, EngineError::DuplicateTypeclassImpl { .. }));
}
#[tokio::test]
async fn spec_typeclass_method_value_without_type_is_ambiguous() {
let code = r#"
class Pick a
pick : a
instance Pick i32
pick = 0
instance Pick bool
pick = true
pick
"#;
let err = match eval(code).await {
Ok(_) => panic!("expected error"),
Err(e) => e,
};
assert!(matches!(err, EngineError::AmbiguousOverload { .. }));
}
#[tokio::test]
async fn spec_defaulting_picks_a_concrete_type_for_numeric_classes() {
let (heap, pointer, ty) = eval("zero").await.unwrap();
assert_eq!(ty, Type::builtin(BuiltinTypeId::F32));
let value = heap.get(&pointer).unwrap();
assert!(matches!(value.as_ref(), Value::F32(_)));
}
#[tokio::test]
async fn spec_integer_literals_unify_with_integral_context() {
let (heap, pointer, ty) = eval("let x: u64 = 4 in x").await.unwrap();
assert_eq!(ty, Type::builtin(BuiltinTypeId::U64));
let value = heap.get(&pointer).unwrap();
match value.as_ref() {
Value::U64(n) => assert_eq!(*n, 4),
_ => panic!("expected u64, got {}", heap.type_name(&pointer).unwrap()),
}
}
#[tokio::test]
async fn test_let_tuple_destructuring() {
let (heap, pointer, ty) = eval("let t = (1, \"Hello\", true), (x, y, z) = t in x")
.await
.unwrap();
assert_eq!(ty, Type::builtin(BuiltinTypeId::I32));
match heap.get(&pointer).unwrap().as_ref() {
Value::I32(n) => assert_eq!(*n, 1),
_ => panic!("expected i32, got {}", heap.type_name(&pointer).unwrap()),
}
let (heap, pointer, ty) = eval("let t = (1, \"Hello\", true), (x, y, z) = t in y")
.await
.unwrap();
assert_eq!(ty, Type::builtin(BuiltinTypeId::String));
match heap.get(&pointer).unwrap().as_ref() {
Value::String(s) => assert_eq!(s, "Hello"),
_ => panic!("expected string, got {}", heap.type_name(&pointer).unwrap()),
}
let (heap, pointer, ty) = eval("let t = (1, \"Hello\", true), (x, y, z) = t in z")
.await
.unwrap();
assert_eq!(ty, Type::builtin(BuiltinTypeId::Bool));
match heap.get(&pointer).unwrap().as_ref() {
Value::Bool(b) => assert!(*b),
_ => panic!("expected bool, got {}", heap.type_name(&pointer).unwrap()),
}
}
#[tokio::test]
async fn test_match_tuple_destructuring() {
let (heap, pointer, ty) = eval("let t = (1, \"Hello\", true) in match t when (x, y, z) -> x")
.await
.unwrap();
assert_eq!(ty, Type::builtin(BuiltinTypeId::I32));
match heap.get(&pointer).unwrap().as_ref() {
Value::I32(n) => assert_eq!(*n, 1),
_ => panic!("expected i32, got {}", heap.type_name(&pointer).unwrap()),
}
let (heap, pointer, ty) = eval("let t = (1, \"Hello\", true) in match t when (x, y, z) -> y")
.await
.unwrap();
assert_eq!(ty, Type::builtin(BuiltinTypeId::String));
match heap.get(&pointer).unwrap().as_ref() {
Value::String(s) => assert_eq!(s, "Hello"),
_ => panic!("expected string, got {}", heap.type_name(&pointer).unwrap()),
}
let (heap, pointer, ty) = eval("let t = (1, \"Hello\", true) in match t when (x, y, z) -> z")
.await
.unwrap();
assert_eq!(ty, Type::builtin(BuiltinTypeId::Bool));
match heap.get(&pointer).unwrap().as_ref() {
Value::Bool(b) => assert!(*b),
_ => panic!("expected bool, got {}", heap.type_name(&pointer).unwrap()),
}
}
#[tokio::test]
async fn test_tuple_projection() {
let (heap, pointer, ty) = eval("let t = (4, \"Hello\", true) in t.0").await.unwrap();
assert_eq!(ty, Type::builtin(BuiltinTypeId::I32));
match heap.get(&pointer).unwrap().as_ref() {
Value::I32(n) => assert_eq!(*n, 4),
_ => panic!("expected i32, got {}", heap.type_name(&pointer).unwrap()),
}
let (heap, pointer, ty) = eval("let t = (4, \"Hello\", true) in t.1").await.unwrap();
assert_eq!(ty, Type::builtin(BuiltinTypeId::String));
match heap.get(&pointer).unwrap().as_ref() {
Value::String(s) => assert_eq!(s, "Hello"),
_ => panic!("expected string, got {}", heap.type_name(&pointer).unwrap()),
}
let (heap, pointer, ty) = eval("let t = (4, \"Hello\", true) in t.2").await.unwrap();
assert_eq!(ty, Type::builtin(BuiltinTypeId::Bool));
match heap.get(&pointer).unwrap().as_ref() {
Value::Bool(b) => assert!(*b),
_ => panic!("expected bool, got {}", heap.type_name(&pointer).unwrap()),
}
}