enum-typer 0.1.1

Type-indexed enums and GADTs for Rust via procedural macros
Documentation
use enum_typer::{match_t, type_enum};

#[test]
fn test_data() {
    type_enum! {
        enum Product {
            Book { title: String, author: String },
            Electronics { name: String, brand: String },
        }

        fn describe(&self) -> String {
            Book { title, author } => format!("Book: '{}' by {}", title, author),
            Electronics { name, brand } => format!("Electronics: '{}' from {}", name, brand),
        }
    }

    let item1 = Book {
        title: "The Rust Programming Language".to_string(),
        author: "Steve Klabnik".to_string(),
    };
    let item2 = Electronics {
        name: "Smartphone".to_string(),
        brand: "TechBrand".to_string(),
    };

    println!("{}", item1.describe());
    println!("{}", item2.describe());
}

#[test]
fn test_inductive() {
    type_enum! {
        enum Sum<A, B> {
            Inl(A),
            Inr(B),
        }
    }

    type SumRef<A, B> = Box<dyn Sum<A, B>>;

    fn fold_sum<A, B, R>(sum: SumRef<A, B>, f_inl: fn(A) -> R, f_inr: fn(B) -> R) -> R
    where
        A: 'static,
        B: 'static,
    {
        match_t!(move sum {
            Inl<A>(a) => f_inl(a),
            Inr<B>(b) => f_inr(b),
        })
    }

    let val1 = Box::new(Inl(42));
    let val2 = Box::new(Inr(true));

    let result1 = fold_sum(val1, |a| a, |b| if b { 1 } else { 0 });
    let result2 = fold_sum(val2, |a| a, |b| if b { 1 } else { 0 });

    assert_eq!(result1, 42);
    assert_eq!(result2, 1);
}

#[test]
fn test_arith() {
    type_enum! {
        enum Arith<T> {
            Num(i32) : Arith<i32>,
            Bool(bool) : Arith<bool>,
            Add(ArithRef<i32>, ArithRef<i32>) : Arith<i32>,
            Mul(ArithRef<i32>, ArithRef<i32>) : Arith<i32>,
            And(ArithRef<bool>, ArithRef<bool>) : Arith<bool>,
            Or(ArithRef<bool>, ArithRef<bool>) : Arith<bool>,
            ToBool(ArithRef<i32>) : Arith<bool>,
            ToNum(ArithRef<bool>) : Arith<i32>,
        }

        fn eval(&self) -> T {
            Num(i) => *i,
            Bool(b) => *b,
            Add(lhs, rhs) => lhs.eval() + rhs.eval(),
            Mul(lhs, rhs) => lhs.eval() * rhs.eval(),
            And(lhs, rhs) => lhs.eval() && rhs.eval(),
            Or(lhs, rhs) => lhs.eval() || rhs.eval(),
            ToBool(expr) => expr.eval() != 0,
            ToNum(expr) => if expr.eval() { 1 } else { 0 },
        }
    }

    type ArithRef<T> = Box<dyn Arith<T>>;

    fn eval2(expr: ArithRef<i32>) -> i32 {
        match_t!(move expr {
            Add(lhs, rhs) => eval2(lhs) + eval2(rhs),
            Mul(lhs, rhs) => eval2(lhs) * eval2(rhs),
            ToNum(e) => if eval1(e) { 1 } else { 0 },
            Num(i) => i,
        })
    }

    fn eval1(expr: ArithRef<bool>) -> bool {
        match_t!(move expr {
            And(lhs, rhs) => eval1(lhs) && eval1(rhs),
            Or(lhs, rhs) => eval1(lhs) || eval1(rhs),
            ToBool(e) => eval2(e) != 0,
            Bool(b) => b,
        })
    }

    let expr: ArithRef<_> = Box::new(Add(
        Box::new(Num(10)),
        Box::new(Mul(Box::new(Num(2)), Box::new(Num(3)))),
    ));

    let expr2: ArithRef<_> = Box::new(And(
        Box::new(ToBool(Box::new(Num(0)))),
        Box::new(ToBool(Box::new(Num(5)))),
    ));

    assert_eq!(expr.eval(), 16);

    assert_eq!(expr2.eval(), false);

    assert_eq!(eval2(expr), 16);

    assert_eq!(eval1(expr2), false);
}

#[test]
fn test_safe_list() {
    struct Empty;
    struct NonEmpty;

    type_enum! {
        enum SafeList<T, E> {
            Nil : SafeList<T, Empty>,
            Cons(T, SafeListRef<T, E>) : SafeList<T, NonEmpty>,
        }
    }

    fn safe_head<T: 'static>(list: SafeListRef<T, NonEmpty>) -> T {
        match_t!(move list {
            Cons<T, NonEmpty>(head, _tail) => head,
        })
    }

    type SafeListRef<T, E> = Box<dyn SafeList<T, E>>;

    let list: SafeListRef<_, _> = Box::new(Cons(1, Box::new(Cons(2, Box::new(Nil)))));

    let head = safe_head(list);
    assert_eq!(head, 1);
}

#[test]
fn test_field_generics() {
    type_enum! {
        enum Nat {
            Zero,
            Succ<N: Nat>(N) : Nat,
        }

        fn to_u32(&self) -> u32 {
            Zero => 0,
            Succ<N: Nat>(n) => 1 + n.to_u32(),
        }
    }

    let three = Succ(Succ(Succ(Zero)));
    assert_eq!(three.to_u32(), 3);
}

#[test]
fn test_field_generics_arith() {
    type_enum! {
        enum Arith<T> {
            Num(i32) : Arith<i32>,
            Bool(bool) : Arith<bool>,
            Add<A: Arith<i32>, B: Arith<i32>>(A, B) : Arith<i32>,
            Or<A: Arith<bool>, B: Arith<bool>>(A, B) : Arith<bool>,
        }

        fn eval(&self) -> T {
            Num(i) => *i,
            Bool(b) => *b,
            Add<A, B>(a, b) => a.eval() + b.eval(),
            Or<A, B>(a, b) => a.eval() || b.eval(),
        }
    }

    let expr = Add(Num(10), Add(Num(20), Num(5)));

    let expr2 = Or(Bool(false), Or(Bool(true), Bool(false)));

    assert_eq!(expr.eval(), 35);

    assert_eq!(expr2.eval(), true);
}