//! Tests for basic functionality. use core::iter; use arithmetic_parser::grammars::Parse; use arithmetic_typing::{ arith::{Num, NumArithmetic}, defs::Prelude, error::{ErrorContext, ErrorKind, ErrorPathFragment}, Function, TupleLen, Type, TypeEnvironment, UnknownLen, }; use assert_matches::assert_matches; use crate::{assert_incompatible_types, hash_fn_type, zip_fn_type, ErrorsExt, F32Grammar}; #[test] fn statements_with_a_block() { let code = "y = { x = 3; 2 * x }; x ^ y == 6 * x;"; let block = F32Grammar::parse_statements(code).unwrap(); let mut type_env: TypeEnvironment = iter::once(("x", Type::NUM)).collect(); type_env.process_statements(&block).unwrap(); assert_eq!(*type_env.get("y").unwrap(), Type::NUM); } #[test] fn boolean_statements() { let code = "y = x == x ^ 2; y = y || { x = 3; x != 7 };"; let block = F32Grammar::parse_statements(code).unwrap(); let mut type_env: TypeEnvironment = iter::once(("x", Type::NUM)).collect(); type_env.process_statements(&block).unwrap(); assert_eq!(type_env["y"], Type::BOOL); } #[test] fn spreading_binary_ops() { let code = " x = 3 * (1, 2); y = (1, x, 3) * 4; "; let block = F32Grammar::parse_statements(code).unwrap(); let mut type_env = TypeEnvironment::new(); type_env.process_statements(&block).unwrap(); assert_eq!(type_env["x"].to_string(), "(Num, Num)"); assert_eq!(type_env["y"].to_string(), "(Num, (Num, Num), Num)"); } #[test] fn function_definition() { let code = " sign = |x, msg| { (r, R) = hash() * (1, 3); c = hash(R, msg); (R, r + c * x) }; "; let block = F32Grammar::parse_statements(code).unwrap(); let mut type_env = TypeEnvironment::new(); type_env.insert("hash", hash_fn_type()); type_env.process_statements(&block).unwrap(); assert_eq!( type_env.get("sign").unwrap().to_string(), "for<'T: Hash> (Num, 'T) -> (Num, Num)" ); } #[test] fn non_linear_types_in_function() { let code = " compare = |x, y| x == y; compare_hash = |x, z| x == 2 ^ hash(z); add_hashes = |x, y| hash(x, y) + hash(y, x); "; let block = F32Grammar::parse_statements(code).unwrap(); let mut type_env = TypeEnvironment::new(); type_env.insert("hash", hash_fn_type()); type_env.process_statements(&block).unwrap(); assert_eq!( type_env.get("compare").unwrap().to_string(), "('T, 'T) -> Bool" ); assert_eq!( type_env.get("compare_hash").unwrap().to_string(), "for<'T: Hash> (Num, 'T) -> Bool" ); assert_eq!( type_env.get("add_hashes").unwrap().to_string(), "for<'T: Hash, 'U: Hash> ('T, 'U) -> Num" ); } #[test] fn method_basics() { let code = " foo = 3.plus(4); do_something = |x| x - 5; bar = foo.do_something(); "; let block = F32Grammar::parse_statements(code).unwrap(); let mut type_env = TypeEnvironment::new(); let plus_type = Function::builder() .with_arg(Type::NUM) .with_arg(Type::NUM) .returning(Type::NUM); type_env.insert("plus", plus_type); type_env.process_statements(&block).unwrap(); assert_eq!(*type_env.get("bar").unwrap(), Type::NUM); } #[test] fn immediately_invoked_function() { let code = "flag = (|x| x + 3)(4) == 7;"; let block = F32Grammar::parse_statements(code).unwrap(); let mut type_env = TypeEnvironment::new(); type_env.process_statements(&block).unwrap(); assert_eq!(*type_env.get("flag").unwrap(), Type::BOOL); } #[test] fn variable_scoping() { let code = "x = 5; y = { x = (1, x); x * (2, 3) };"; let block = F32Grammar::parse_statements(code).unwrap(); let mut type_env = TypeEnvironment::new(); type_env.process_statements(&block).unwrap(); assert_eq!( *type_env.get("y").unwrap(), Type::Tuple(vec![Type::NUM; 2].into()) ); } #[test] fn destructuring_for_tuple_on_assignment() { let code = " (x, ...ys) = (1, 2, 3); (...zs, fn, flag) = (4, 5, 6, |x| x + 3, 1 == 1); "; let block = F32Grammar::parse_statements(code).unwrap(); let mut type_env = TypeEnvironment::new(); type_env.process_statements(&block).unwrap(); assert_eq!(type_env["x"], Type::NUM); assert_eq!(type_env["ys"], Type::slice(Type::NUM, TupleLen::from(2))); assert_eq!(type_env["zs"], Type::slice(Type::NUM, TupleLen::from(3))); assert_matches!(type_env["fn"], Type::Function(_)); assert_eq!(type_env["flag"], Type::BOOL); } #[test] fn destructuring_with_unnamed_middle() { let code = r#" (x, y, ...) = (1, 2, || 3); (..., z) = (1 == 1,); "#; let block = F32Grammar::parse_statements(code).unwrap(); let mut type_env = TypeEnvironment::new(); type_env.process_statements(&block).unwrap(); assert_eq!(type_env["x"], Type::NUM); assert_eq!(type_env["y"], Type::NUM); assert_eq!(type_env["z"], Type::BOOL); } #[test] fn destructuring_for_fn_args() { let code = r#" shift = |shift: Num, ...xs| xs + shift; shift(1, 2, 3, 4) == (3, 4, 5); shift(1, (2, 3), (4, 5)) == ((3, 4), (5, 6)); 3.shift(5) "#; let block = F32Grammar::parse_statements(code).unwrap(); let mut type_env = TypeEnvironment::new(); let res = type_env.process_statements(&block).unwrap(); assert_eq!( type_env["shift"].to_string(), "for<'T: Lin> (Num, ...['T; N]) -> ['T; N]" ); assert_eq!(res.to_string(), "(Num)"); { let bogus_code = "shift(1, 2, (3, 4))"; let bogus_block = F32Grammar::parse_statements(bogus_code).unwrap(); let err = type_env .process_statements(&bogus_block) .unwrap_err() .single(); assert_matches!(err.kind(), ErrorKind::TypeMismatch(..)); } { let bogus_code = "shift(1, 1 == 2, 1 == 1)"; let bogus_block = F32Grammar::parse_statements(bogus_code).unwrap(); let err = type_env .process_statements(&bogus_block) .unwrap_err() .single(); assert_matches!(err.kind(), ErrorKind::FailedConstraint { .. }); } let bogus_code = "(x, _, _) = 1.shift(2, 3);"; let bogus_block = F32Grammar::parse_statements(bogus_code).unwrap(); let err = type_env .process_statements(&bogus_block) .unwrap_err() .single(); assert_matches!( err.kind(), ErrorKind::TupleLenMismatch { lhs, rhs, .. } if *lhs == TupleLen::from(3) && *rhs == TupleLen::from(2) ) } #[test] fn fn_args_can_be_unified_with_concrete_length() { let code = "weird = |...xs| { (x, y) = xs; x };"; let block = F32Grammar::parse_statements(code).unwrap(); let mut type_env = TypeEnvironment::new(); type_env.process_statements(&block).unwrap(); assert_eq!(type_env["weird"].to_string(), "('T, 'T) -> 'T"); } #[test] fn exact_lengths_for_gathering_fn() { let code = r#" gather = |...xs| xs; (x, y) = gather(1, 2); (head, ...) = gather(1 == 1, 1 == 2, 1 == 3); (_, ...tail) = gather(4, 5, 6); "#; let block = F32Grammar::parse_statements(code).unwrap(); let mut type_env = TypeEnvironment::new(); type_env.process_statements(&block).unwrap(); assert_eq!(type_env["gather"].to_string(), "(...['T; N]) -> ['T; N]"); assert_eq!(type_env["x"], Type::NUM); assert_eq!(type_env["y"], Type::NUM); assert_eq!(type_env["head"], Type::BOOL); assert_eq!(type_env["tail"], Type::slice(Type::NUM, TupleLen::from(2))); } #[test] fn inferring_type_from_embedded_function() { let code = "double = |x| { (x, || (x, 2 * x)) };"; let block = F32Grammar::parse_statements(code).unwrap(); let mut type_env = TypeEnvironment::new(); type_env.process_statements(&block).unwrap(); assert_eq!( type_env["double"].to_string(), "(Num) -> (Num, () -> (Num, Num))" ); } #[test] fn free_and_bound_type_vars() { let code = r#" concat = |x| { |y| (x, y) }; x = concat(2)(5); partial = concat(3); y = (partial(2), partial((1, 1))); "#; let block = F32Grammar::parse_statements(code).unwrap(); let mut type_env = TypeEnvironment::new(); type_env.process_statements(&block).unwrap(); assert_eq!( type_env.get("concat").unwrap().to_string(), "('T) -> ('U) -> ('T, 'U)" ); assert_eq!(type_env.get("x").unwrap().to_string(), "(Num, Num)"); assert_eq!( type_env.get("partial").unwrap().to_string(), "('U) -> (Num, 'U)" ); assert_eq!( type_env.get("y").unwrap().to_string(), "((Num, Num), (Num, (Num, Num)))" ); } #[test] fn attributing_type_vars_to_correct_fn() { let code = "double = |x| { (x, || (x, x)) };"; let block = F32Grammar::parse_statements(code).unwrap(); let mut type_env = TypeEnvironment::new(); type_env.process_statements(&block).unwrap(); assert_eq!( type_env["double"].to_string(), "('T) -> ('T, () -> ('T, 'T))" ); } #[test] fn defining_and_calling_embedded_function() { let code = r#" call_double = |x| { double = |x| (x, x); double(x) == (1, 3) }; "#; let block = F32Grammar::parse_statements(code).unwrap(); let mut type_env = TypeEnvironment::new(); type_env.process_statements(&block).unwrap(); assert_eq!(type_env["call_double"].to_string(), "(Num) -> Bool"); } #[test] fn varargs_in_embedded_fn() { let code = r#" create_sum = |init| |...xs| xs.fold(init, |acc, x| acc + x); sum = create_sum(0); sum(1, 2, 3, 4) == 10; other_sum = create_sum((0, 0)); other_sum((1, 2), (3, 4)) == (4, 6); "#; let block = F32Grammar::parse_statements(code).unwrap(); let mut type_env = TypeEnvironment::new(); type_env.insert("fold", Prelude::Fold); type_env.process_statements(&block).unwrap(); assert_eq!( type_env["create_sum"].to_string(), "for<'T: Ops> ('T) -> (...['T; N]) -> 'T" ); assert_eq!(type_env["sum"].to_string(), "(...[Num; N]) -> Num"); assert_eq!( type_env["other_sum"].to_string(), "(...[(Num, Num); N]) -> (Num, Num)" ); } #[test] fn function_as_arg() { let code = "mapper = |(x, y), map| (map(x), map(y));"; let block = F32Grammar::parse_statements(code).unwrap(); let mut type_env = TypeEnvironment::new(); type_env.process_statements(&block).unwrap(); assert_eq!( type_env["mapper"].to_string(), "(('T, 'T), ('T) -> 'U) -> ('U, 'U)" ); } #[test] fn function_as_arg_with_more_constraints() { let code = "mapper = |(x, y), map| map(x) + map(y);"; let block = F32Grammar::parse_statements(code).unwrap(); let mut type_env = TypeEnvironment::new(); type_env.process_statements(&block).unwrap(); assert_eq!( type_env["mapper"].to_string(), "for<'U: Ops> (('T, 'T), ('T) -> 'U) -> 'U" ); } #[test] fn function_as_arg_with_even_more_constraints() { let code = "mapper = |(x, y), map| map(x * map(y));"; let block = F32Grammar::parse_statements(code).unwrap(); let mut type_env = TypeEnvironment::new(); type_env.process_statements(&block).unwrap(); assert_eq!( type_env["mapper"].to_string(), "for<'T: Ops> (('T, 'T), ('T) -> 'T) -> 'T" ); } #[test] fn function_arg_with_multiple_args() { let code = "test_fn = |x, fun| fun(x, x * 3);"; let block = F32Grammar::parse_statements(code).unwrap(); let mut type_env = TypeEnvironment::new(); type_env.process_statements(&block).unwrap(); assert_eq!( type_env["test_fn"].to_string(), "(Num, (Num, Num) -> 'T) -> 'T" ); } #[test] fn function_as_arg_within_tuple() { let code = r#" test_fn = |struct, y| { (fn, x) = struct; fn(x / 3) * y }; "#; let block = F32Grammar::parse_statements(code).unwrap(); let mut type_env = TypeEnvironment::new(); type_env.process_statements(&block).unwrap(); assert_eq!( type_env.get("test_fn").unwrap().to_string(), "for<'T: Ops> (((Num) -> 'T, Num), 'T) -> 'T" ); } #[test] fn function_instantiations_are_independent() { let code = r#" identity = |x| x; x = (identity(5), identity(1 == 2)); "#; let block = F32Grammar::parse_statements(code).unwrap(); let mut type_env = TypeEnvironment::new(); type_env.process_statements(&block).unwrap(); assert_eq!(type_env.get("x").unwrap().to_string(), "(Num, Bool)"); } #[test] fn function_instantiations_with_alias() { let code = r#" identity = |x| x; alias = identity; x = (identity(5), alias(alias(1 == 2))); "#; let block = F32Grammar::parse_statements(code).unwrap(); let mut type_env = TypeEnvironment::new(); type_env.process_statements(&block).unwrap(); assert_eq!(type_env.get("x").unwrap().to_string(), "(Num, Bool)"); } #[test] fn aliasing_generic_native_function() { let code = r#" reduce = fold; (#{ x: 1, y: 2 }, #{ x: 3, y: 4 }).reduce(0, |acc, { x, y }| acc + x + y); (1, 2, 3).reduce(0, |acc, x| acc + x) "#; let block = F32Grammar::parse_statements(code).unwrap(); let output = TypeEnvironment::new() .insert("fold", Prelude::Fold) .process_statements(&block) .unwrap(); assert_eq!(output, Type::NUM); } #[test] fn function_passed_as_arg() { let code = r#" mapper = |(x, y), map| (map(x), map(y)); tuple = mapper((1, 2), |x| x + 3); create_fn = |x| { || x }; tuple_of_fns = mapper((1, 2), create_fn); "#; let block = F32Grammar::parse_statements(code).unwrap(); let mut type_env = TypeEnvironment::new(); type_env.process_statements(&block).unwrap(); assert_eq!(type_env.get("tuple").unwrap().to_string(), "(Num, Num)"); assert_eq!( type_env.get("tuple_of_fns").unwrap().to_string(), "(() -> Num, () -> Num)" ); } #[test] fn curried_function_passed_as_arg() { let code = r#" mapper = |(x, y), map| (map(x), map(y)); concat = |x| { |y| (x, y) }; r = mapper((1, 2), concat(1 == 1)); "#; let block = F32Grammar::parse_statements(code).unwrap(); let mut type_env = TypeEnvironment::new(); type_env.process_statements(&block).unwrap(); assert_eq!( type_env.get("r").unwrap().to_string(), "((Bool, Num), (Bool, Num))" ); } #[test] fn parametric_fn_passed_as_arg_with_different_constraints() { let code = r#" concat = |x| { |y| (x, y) }; partial = concat(3); // (U) -> (Num, U) first = |fun| fun(5); r = first(partial); // (Num, Num) second = |fun, b| fun(b) == (3, b); second(partial, 1 == 1); "#; let block = F32Grammar::parse_statements(code).unwrap(); let mut type_env = TypeEnvironment::new(); type_env.process_statements(&block).unwrap(); assert_eq!(type_env.get("r").unwrap().to_string(), "(Num, Num)"); } #[test] fn type_param_is_placed_correctly_with_fn_arg() { let code = "foo = |fun| { |x| fun(x) == x };"; let block = F32Grammar::parse_statements(code).unwrap(); let mut type_env = TypeEnvironment::new(); type_env.process_statements(&block).unwrap(); assert_eq!( type_env.get("foo").unwrap().to_string(), "(('T) -> 'T) -> ('T) -> Bool" ); } #[test] fn type_params_in_fn_with_multiple_fn_args() { let code = "test = |x, foo, bar| foo(x) == bar(x * x);"; let block = F32Grammar::parse_statements(code).unwrap(); let mut type_env = TypeEnvironment::new(); type_env.process_statements(&block).unwrap(); assert_eq!( type_env.get("test").unwrap().to_string(), "for<'T: Ops> ('T, ('T) -> 'U, ('T) -> 'U) -> Bool" ); } #[test] fn unifying_slice_and_tuple() { let code = "xs = (1, 2).map(|x| x + 5);"; let block = F32Grammar::parse_statements(code).unwrap(); let mut type_env = TypeEnvironment::new(); type_env.insert("map", Prelude::Map); type_env.process_statements(&block).unwrap(); assert_eq!(type_env["xs"], Type::Tuple(vec![Type::NUM; 2].into())); } #[test] fn function_accepting_slices() { let code = "inc = |xs| xs.map(|x| x + 5); z = (1, 2, 3).inc();"; let block = F32Grammar::parse_statements(code).unwrap(); let mut type_env = TypeEnvironment::new(); type_env.insert("map", Prelude::Map); type_env.process_statements(&block).unwrap(); assert_eq!(type_env["inc"].to_string(), "([Num; N]) -> [Num; N]"); assert_eq!(type_env["z"], Type::slice(Type::NUM, TupleLen::from(3))); } #[test] fn slice_narrowed_to_tuple() { let code = "foo = |xs, fn| { (x, y, _) = xs.map(fn); y - x };"; let block = F32Grammar::parse_statements(code).unwrap(); let mut type_env = TypeEnvironment::new(); type_env.insert("map", Prelude::Map); type_env.process_statements(&block).unwrap(); assert_eq!( type_env["foo"].to_string(), "for<'U: Ops> (('T, 'T, 'T), ('T) -> 'U) -> 'U" ); } #[test] fn unifying_length_vars() { let code = "foo = |xs, ys| xs.zip_with(ys).map(|(x, y)| x + y);"; let block = F32Grammar::parse_statements(code).unwrap(); let mut type_env = TypeEnvironment::new(); type_env.insert("map", Prelude::Map); type_env.insert("zip_with", zip_fn_type()); type_env.process_statements(&block).unwrap(); assert_eq!( type_env["foo"].to_string(), "for (['T; N], ['T; N]) -> ['T; N]" ); } #[test] fn dynamically_sized_slices_basics() { let code = "filtered = (1, 2, 3).filter(|x| x != 1);"; let block = F32Grammar::parse_statements(code).unwrap(); let mut type_env = TypeEnvironment::new(); type_env.insert("filter", Prelude::Filter); type_env.process_statements(&block).unwrap(); assert_eq!(type_env["filtered"].to_string(), "[Num]"); let new_code = r#" mapped = filtered.map(|x| x + 3); sum = mapped.fold(0, |acc, x| acc + x); "#; type_env .insert("map", Prelude::Map) .insert("fold", Prelude::Fold); for line in new_code.lines() { let line = line.trim(); if line.is_empty() { continue; } let block = F32Grammar::parse_statements(line).unwrap(); type_env.process_statements(&block).unwrap(); } assert_eq!(type_env["mapped"], type_env["filtered"]); assert_eq!(type_env["sum"], Type::NUM); } #[test] fn dynamically_sized_slices_with_map() { let code = r#" foo = |xs| xs.filter(|x| x != 1).map(|x| x / 2); // `foo` must be callable both with tuples and dynamically sized slices. (1, 2, 3).foo(); (5, 6, 7).filter(|x| x != 6 && x != 7).foo(); "#; let block = F32Grammar::parse_statements(code).unwrap(); let mut type_env = TypeEnvironment::new(); type_env.insert("filter", Prelude::Filter); type_env.insert("map", Prelude::Map); type_env.process_statements(&block).unwrap(); assert_eq!(type_env["foo"].to_string(), "([Num; N]) -> [Num]"); } #[test] fn mix_of_static_and_dynamic_slices() { let code = "|xs| { _unused: [_] = xs; xs + (1, 2) }"; let block = F32Grammar::parse_statements(code).unwrap(); let mut type_env = TypeEnvironment::new(); let output = type_env.process_statements(&block).unwrap(); assert_eq!(output.to_string(), "((Num, Num)) -> (Num, Num)"); } #[test] fn mix_of_static_and_dynamic_slices_via_fn() { let code = "|xs| { xs.filter(|x| x != 1); xs + (1, 2) }"; let block = F32Grammar::parse_statements(code).unwrap(); let mut type_env = TypeEnvironment::new(); let output = type_env .insert("filter", Prelude::Filter) .process_statements(&block) .unwrap(); assert_eq!(output.to_string(), "((Num, Num)) -> (Num, Num)"); } #[test] fn comparisons_when_switched_on() { let code = "(1, 2, 3).filter(|x| x > 1)"; let block = F32Grammar::parse_statements(code).unwrap(); let mut type_env = TypeEnvironment::new(); type_env.insert("filter", Prelude::Filter); let output = type_env .process_with_arithmetic(&NumArithmetic::with_comparisons(), &block) .unwrap(); let slice = match &output { Type::Tuple(tuple) => tuple.as_slice().unwrap(), _ => panic!("Unexpected output: {output:?}"), }; assert_eq!(*slice.element(), Type::NUM); assert_matches!(slice.len().components(), (Some(UnknownLen::Dynamic), 0)); } #[test] fn comparison_type_errors() { let code = "(2 <= 1) + 3"; let block = F32Grammar::parse_statements(code).unwrap(); let mut type_env = TypeEnvironment::new(); let err = type_env .process_with_arithmetic(&NumArithmetic::with_comparisons(), &block) .unwrap_err() .single(); assert_eq!(err.main_location().span(code), "(2 <= 1)"); assert_eq!(err.path(), [ErrorPathFragment::Lhs]); assert_matches!(err.context(), ErrorContext::BinaryOp(_)); assert_matches!( err.kind(), ErrorKind::FailedConstraint { ty, .. } if *ty == Type::BOOL ); } #[test] fn constraint_passed_to_wrapping_fn() { let code = "add = |x, y| x + y; |x| add(x, x)"; let block = F32Grammar::parse_statements(code).unwrap(); let mut type_env = TypeEnvironment::new(); let double_fn = type_env .process_with_arithmetic(&NumArithmetic::with_comparisons(), &block) .unwrap(); assert_eq!(double_fn.to_string(), "for<'T: Ops> ('T) -> 'T"); } #[test] fn any_can_be_unified_with_anything() { let code = r#" any == 5 && any == (2, 3); any(2, (3, 5)) == any(7); any + 2; any * (3, 5); any.map(|x| x + 3) == (2, 4, 5); // could be valid any.map(|x| x + 3) == (1 == 2); // cannot be valid; the output is a slice of numbers "#; let block = F32Grammar::parse_statements(code).unwrap(); let mut type_env = TypeEnvironment::new(); let err = type_env .insert("any", Type::Any) .insert("map", Prelude::Map) .process_statements(&block) .unwrap_err() .single(); let lhs_slice = match err.kind() { ErrorKind::TypeMismatch(Type::Tuple(tuple), bool) if *bool == Type::BOOL => { tuple.as_slice().unwrap() } _ => panic!("Unexpected error: {err:?}"), }; assert_eq!(*lhs_slice.element(), Type::NUM); } #[test] fn any_propagates_via_fn_params() { let code = r#" identity = |x| x; identity(any) == 5 && identity(any) == (2, 3, 1 == 1); val = identity(any); val == 5 && val == (7, 8) // works: `val: any` "#; let block = F32Grammar::parse_statements(code).unwrap(); let mut type_env = TypeEnvironment::new(); type_env .insert("any", Type::Any) .process_statements(&block) .unwrap(); assert_eq!(type_env["val"], Type::Any); } #[test] fn any_can_be_copied_and_unified_with_anything() { let code = r#" any = any_other; any == 5 && any == (2, 3); any + 3; any(2, (3, 5)) == any(7); "#; let block = F32Grammar::parse_statements(code).unwrap(); let mut type_env = TypeEnvironment::new(); type_env .insert("any_other", Type::Any) .process_statements(&block) .unwrap(); } #[test] fn any_can_be_destructured_and_unified_with_anything() { let code = r#" (any, ...) = some_tuple; any == 5 && any == (2, 3); any + 3; any(2, (3, 5)) == any(7); "#; let block = F32Grammar::parse_statements(code).unwrap(); let mut type_env = TypeEnvironment::new(); type_env .insert("some_tuple", Type::Any.repeat(3)) .process_statements(&block) .unwrap(); } #[test] fn unifying_types_containing_any() { let code = "digest(1, (2, 3), |x| x + 1)"; let block = F32Grammar::parse_statements(code).unwrap(); let digest_type = Function::builder() .with_arg(Type::NUM) .with_varargs(Type::Any, UnknownLen::param(0)) .returning(Type::NUM); let mut type_env = TypeEnvironment::new(); let output = type_env .insert("digest", digest_type) .process_statements(&block) .unwrap(); assert_eq!(output, Type::NUM); let bogus_code = "digest(2 == 3, 5)"; let bogus_block = F32Grammar::parse_statements(bogus_code).unwrap(); let err = type_env .process_statements(&bogus_block) .unwrap_err() .single(); assert_incompatible_types(err.kind(), &Type::NUM, &Type::BOOL); } #[test] fn indexing_basics() { let code = "((1, 2, 3).0, ((1, 2), (3, 4)).0.1)"; let block = F32Grammar::parse_statements(code).unwrap(); let output = TypeEnvironment::new().process_statements(&block).unwrap(); assert_eq!(output, Type::from((Type::NUM, Type::NUM))); } #[test] fn indexing_after_narrowing_type() { let code = r#" |xs| { (_, ...ys) = xs; ys.fold(xs.0, |acc, y| acc + (y, y)) } "#; let block = F32Grammar::parse_statements(code).unwrap(); let output = TypeEnvironment::new() .insert("fold", Prelude::Fold) .process_statements(&block) .unwrap(); assert_eq!( output.to_string(), "for<'T: Ops> ((('T, 'T), ...['T; N])) -> ('T, 'T)" ); } #[test] fn wildcard_var_is_not_assigned() { let code = "(_, x) = (1, 2);"; let block = F32Grammar::parse_statements(code).unwrap(); let mut type_env = TypeEnvironment::new(); type_env.process_statements(&block).unwrap(); assert_eq!(type_env["x"].to_string(), "Num"); assert!(type_env.get("_").is_none()); } #[test] fn multiple_wildcard_vars_in_assignment_are_fine() { let code = "(_, _, x) = (1, 2, 3); x == 3;"; let block = F32Grammar::parse_statements(code).unwrap(); let mut type_env = TypeEnvironment::new(); type_env.process_statements(&block).unwrap(); assert_eq!(type_env["x"].to_string(), "Num"); assert!(type_env.get("_").is_none()); } #[test] fn multiple_wildcard_vars_in_fn_def_are_fine() { let code = "|_: Num, { x -> _: Num, y }| y == 1"; let block = F32Grammar::parse_statements(code).unwrap(); let output = TypeEnvironment::new().process_statements(&block).unwrap(); assert_eq!( output.to_string(), "for<'T: { x: Num, y: Num }> (Num, 'T) -> Bool" ); } #[test] fn defer_function_basics() { let code = "defer(|x| x + 1)"; let block = F32Grammar::parse_statements(code).unwrap(); let mut type_env = TypeEnvironment::new(); type_env .insert("defer", Prelude::Defer) .insert("if", Prelude::If); let output = type_env.process_statements(&block).unwrap(); assert_eq!(output, Type::NUM); let code = "defer(|fib| { |x| fib(x - 1) as Num + fib(x - 2) })"; let block = F32Grammar::parse_statements(code).unwrap(); let output = type_env.process_statements(&block).unwrap(); assert_eq!(output.to_string(), "(Num) -> Num"); let code = "defer(|fib| { |x| if(x < 2, || x, || fib(x - 1) + fib(x - 2))() })"; let block = F32Grammar::parse_statements(code).unwrap(); let output = type_env .process_with_arithmetic(&NumArithmetic::with_comparisons(), &block) .unwrap(); assert_eq!(output.to_string(), "(Num) -> Num"); }