flense 0.4.1

Purpose-oriented lensing
Documentation
//! Tests for the `#[derive(Adapter)]` macro (the `derive` feature).

#![cfg(feature = "derive")]
#![allow(clippy::float_cmp)]

use core::mem::offset_of;

use assert2::assert;
use flense::prelude::*;

use super::{
    Color,
    Other,
    Position,
};

// (a) Reference existing, purpose-oriented `Field` markers. `Position` and
// `Color` are defined in the integration crate root with `Type = [f32; 3]`.
#[derive(Adapter)]
struct Reuse {
    #[field(Position)]
    pos: [f32; 3],
    #[field(Color)]
    col: [f32; 3],
}

#[test]
fn derive_existing_markers_offsets_match() {
    assert!(<Reuse as Adapter<Position>>::OFFSET == offset_of!(Reuse, pos));
    assert!(<Reuse as Adapter<Color>>::OFFSET == offset_of!(Reuse, col));
}

#[test]
fn derive_existing_markers_lens_roundtrip() {
    let mut r = Reuse {
        pos: [1.0, 2.0, 3.0],
        col: [4.0, 5.0, 6.0],
    };
    {
        let mut lens: LensMut<'_, (Position, Color)> = r.lens_mut();
        *lens.as_mut::<Position, _>() = [10.0, 20.0, 30.0];
        assert!(*lens.as_ref::<Color, _>() == [4.0, 5.0, 6.0]);
    }
    assert!(r.pos == [10.0, 20.0, 30.0]);
    assert!(r.col == [4.0, 5.0, 6.0]);
}

// (b) Mint fresh marker types. `Hp` and `Mana` are generated by the derive,
// with `Field::Type` taken from the annotated field.
#[derive(Adapter)]
struct Stats {
    #[field(new Hp)]
    hp: u32,
    #[field(new Mana)]
    mana: u16,
}

#[test]
fn derive_new_markers() {
    // The generated `Field::Type`s are the field types.
    let _: <Hp as Field>::Type = 0u32;
    let _: <Mana as Field>::Type = 0u16;

    let mut s = Stats { hp: 100, mana: 50 };
    {
        let mut lens: LensMut<'_, (Hp, Mana)> = s.lens_mut();
        *lens.as_mut::<Hp, _>() = 999;
        *lens.as_mut::<Mana, _>() = 7;
    }
    assert!(s.hp == 999);
    assert!(s.mana == 7);
    assert!(<Stats as Adapter<Hp>>::OFFSET == offset_of!(Stats, hp));
    assert!(<Stats as Adapter<Mana>>::OFFSET == offset_of!(Stats, mana));
}

// (c) Several entries in one attribute, plus a repeated attribute mixing a new
// and an existing marker. `Other` (crate root) has `Type = u64`.
#[derive(Adapter)]
struct Multi {
    #[field(new First, new Second)]
    a: u64,
    #[field(new Third)]
    #[field(Other)]
    b: u64,
}

#[test]
fn derive_multiple_entries() {
    assert!(<Multi as Adapter<First>>::OFFSET == offset_of!(Multi, a));
    assert!(<Multi as Adapter<Second>>::OFFSET == offset_of!(Multi, a));
    assert!(<Multi as Adapter<Third>>::OFFSET == offset_of!(Multi, b));
    assert!(<Multi as Adapter<Other>>::OFFSET == offset_of!(Multi, b));

    let mut m = Multi { a: 1, b: 2 };
    {
        let mut lens: LensMut<'_, (First, Other)> = m.lens_mut();
        *lens.as_mut::<First, _>() = 11;
        *lens.as_mut::<Other, _>() = 22;
    }
    assert!(m.a == 11);
    assert!(m.b == 22);
}

// (d) Generic struct: the Adapter impl must carry the struct's generics. The
// lensed field is concrete (a minted marker has a fixed `Field::Type`).
#[derive(Adapter)]
struct Wrapper<T> {
    payload: T,
    #[field(new Tag)]
    tag: u32,
}

#[test]
fn derive_generic_struct() {
    assert!(<Wrapper<String> as Adapter<Tag>>::OFFSET == offset_of!(Wrapper<String>, tag));
    assert!(<Wrapper<[u64; 32]> as Adapter<Tag>>::OFFSET == offset_of!(Wrapper<[u64; 32]>, tag));
    assert!(<Wrapper<[u8; 3]> as Adapter<Tag>>::OFFSET == offset_of!(Wrapper<[u8; 3]>, tag));

    let mut w = Wrapper::<[u8; 3]> {
        payload: [1, 2, 3],
        tag: 5,
    };
    {
        let mut lens: LensMut<'_, (Tag,)> = w.lens_mut();
        *lens.as_mut::<Tag, _>() = 42;
    }
    assert!(w.tag == 42);
    assert!(w.payload == [1, 2, 3]);
}

// (e) `new reflexive` mints the marker and also adapts the underlying type, so
// the bare `[f32; 3]` can be lensed as `Spin` with no wrapping struct.
#[derive(Adapter)]
struct Reflexive {
    #[field(new reflexive Spin)]
    spin: [f32; 3],
}

#[test]
fn derive_reflexive_marker() {
    assert!(<Reflexive as Adapter<Spin>>::OFFSET == offset_of!(Reflexive, spin));
    // The reflexive impl: the field type adapts itself at offset zero.
    assert!(<<Spin as Field>::Type as Adapter<Spin>>::OFFSET == 0);

    let mut bare = [1.0f32, 2.0, 3.0];
    {
        let mut lens: LensMut<'_, (Spin,)> = bare.lens_mut();
        *lens.as_mut::<Spin, _>() = [4.0, 5.0, 6.0];
    }
    assert!(bare == [4.0, 5.0, 6.0]);
}

// A field with no `#[field(...)]` is fine, and just doesn't have any adapters.
#[derive(Adapter)]
struct Sparse {
    _ignored: [u8; 7],
    #[field(new Lensed)]
    lensed: u64,
}

#[test]
fn derive_ignores_unannotated_fields() {
    let mut s = Sparse {
        _ignored: [0; 7],
        lensed: 0,
    };
    {
        let mut lens: LensMut<'_, (Lensed,)> = s.lens_mut();
        *lens.as_mut::<Lensed, _>() = 0xABCD;
    }
    assert!(s.lensed == 0xABCD);
}