numbat 1.23.0

A statically typed programming language for scientific computations with first class support for physical dimensions and units.
Documentation
mod type_checking;
mod type_inference;

use crate::Statement;
use crate::parser::parse;
use crate::prefix_transformer::Transformer;
use crate::typechecker::{Result, TypeCheckError};
use crate::typed_ast::{self, DType};

use super::TypeChecker;
use super::type_scheme::TypeScheme;

const TEST_PRELUDE: &str = "
    dimension Scalar = 1
    dimension A
    dimension B
    dimension C = A * B
    unit a: A
    unit b: B
    unit c: C = a * b

    fn returns_a() -> A = a
    fn takes_a_returns_a(x: A) -> A = x
    fn takes_a_returns_b(x: A) -> B = b
    fn takes_a_and_b_returns_c(x: A, y: B) -> C = x * y

    struct SomeStruct { a: A, b: B }

    let callable = takes_a_returns_b

    fn atan2<T>(x: T, y: T) -> Scalar

    fn len<T>(x: List<T>) -> Scalar
    fn head<T>(x: List<T>) -> T

    fn id<T>(x: T) -> T = x
    fn id_for_dim<T: Dim>(x: T) -> T = x
    ";

fn type_a() -> DType {
    DType::base_dimension("A")
}

fn type_b() -> DType {
    DType::base_dimension("B")
}

fn type_c() -> DType {
    DType::base_dimension("A").multiply(&DType::base_dimension("B"))
}

fn run_typecheck(input: &str) -> Result<typed_ast::Statement<'_>> {
    let statements = parse(TEST_PRELUDE, 0)
        .expect("No parse errors for inputs in this test suite")
        .into_iter()
        .chain(parse(input, 0).expect("No parse errors for inputs in this test suite"));

    let transformed_statements = Transformer::new()
        .transform(statements)
        .map_err(|err| Box::new(err.into()))?;

    TypeChecker::default()
        .check(&transformed_statements)
        .map(|mut statements_checked| statements_checked.pop().unwrap())
}

fn assert_successful_typecheck(input: &str) {
    if let Err(err) = dbg!(run_typecheck(input)) {
        panic!("Input was expected to typecheck successfully, but failed with: {err:?}")
    }
}

fn get_inferred_fn_type(input: &str) -> TypeScheme {
    let statement = run_typecheck(input).expect("Input was expected to type-check");
    match statement {
        Statement::DefineFunction { fn_type, .. } => fn_type,
        _ => {
            unreachable!();
        }
    }
}

#[track_caller]
fn get_typecheck_error(input: &str) -> TypeCheckError {
    if let Err(err) = dbg!(run_typecheck(input)) {
        *err
    } else {
        panic!("Input was expected to yield a type check error");
    }
}