state-macro 0.1.1

Syntax sugar for stateful functions
Documentation
use state_macro::{stateful, with_state};
use std::cell::RefCell;
use std::rc::Rc;

// Define a simple state type
type State<T> = Rc<RefCell<T>>;

// A simple state structure for testing
pub struct TestState {
    counter: i32,
    values: Vec<i32>,
}

impl TestState {
    fn new() -> Self {
        TestState {
            counter: 0,
            values: Vec::new(),
        }
    }

    fn increment(&mut self) -> i32 {
        self.counter += 1;
        self.counter
    }

    fn add_value(&mut self, value: i32) {
        self.values.push(value);
    }
}

// Helper functions for tests
fn param(state: &State<TestState>) -> i32 {
    state.borrow_mut().increment()
}

fn store(state: &State<TestState>, value: i32) {
    state.borrow_mut().add_value(value);
}

// Function to access the state itself
fn get(state: &State<TestState>) -> &State<TestState> {
    state
}

// Module for namespaced functions - these should NOT be affected by the macros
pub mod foo {
    use super::*;

    pub fn bar(state: &State<TestState>, x: i32) -> i32 {
        state.borrow_mut().increment();
        x * 2
    }

    pub fn baz() -> i32 {
        42
    }
}

// Test the with_state! macro with different patterns
#[test]
fn test_with_state_basic() {
    let state = Rc::new(RefCell::new(TestState::new()));

    // Basic usage of ::prefixed functions
    with_state! { &state;
        let x = ::param();
        let y = ::param();
        ::store(x + y);
    }

    // The counter should be incremented twice
    assert_eq!(state.borrow().counter, 2);
    // The sum of 1 and 2 should be stored
    assert_eq!(state.borrow().values, vec![3]);
}

#[test]
fn test_with_state_namespaced_functions() {
    let state = Rc::new(RefCell::new(TestState::new()));

    // Namespaced functions without :: prefix should not be affected
    with_state! { &state;
        let x = ::param();
        // foo::bar should not be transformed
        let y = foo::bar(&state, x);
        let z = foo::baz(); // This doesn't use state at all
        ::store(y + z);
    }

    // The counter should be 2 (one from param() and one from foo::bar())
    assert_eq!(state.borrow().counter, 2);
    // The stored value should be 1*2 + 42 = 44
    assert_eq!(state.borrow().values, vec![44]);
}

#[test]
fn test_with_state_nested_calls() {
    let state = Rc::new(RefCell::new(TestState::new()));

    // Test with nested function calls
    with_state! { &state;
        let x = ::param();
        let y = ::param() + foo::bar(&state, ::param());
        ::store(x + y);
    }

    // The counter should be 4 (three from param() calls and one from foo::bar())
    assert_eq!(state.borrow().counter, 4);

    // The stored value should be 1 + (2 + 3*2) = 9
    assert_eq!(state.borrow().values, vec![9]);
}

#[test]
fn test_with_state_expression_blocks() {
    let state = Rc::new(RefCell::new(TestState::new()));

    // Test with expression blocks
    with_state! { &state;
        let x = {
            let a = ::param();
            let b = ::param();
            a + b
        };
        ::store(x);
    }

    // The counter should be 2
    assert_eq!(state.borrow().counter, 2);
    // The stored value should be 1 + 2 = 3
    assert_eq!(state.borrow().values, vec![3]);
}

// Test the stateful attribute macro
#[stateful(&State<TestState>)]
fn process_data(x: i32, y: i32) -> i32 {
    // Get the state by its generated name using the ::get() helper
    let state = ::get();

    let a = ::param();
    let b = ::param();
    let c = foo::bar(state, x); // Now we can use the state variable
    ::store(a + b + c + y);
    a + b + c + y
}

#[test]
fn test_stateful_attribute() {
    let state = Rc::new(RefCell::new(TestState::new()));

    let result = process_data(&state, 10, 20);

    // The function should return 1 + 2 + (10*2) + 20 = 43
    assert_eq!(result, 43);

    // The counter should be 3 (two from param() calls and one from foo::bar())
    assert_eq!(state.borrow().counter, 3);

    // The stored value should be 1 + 2 + 20 + 20 = 43
    assert_eq!(state.borrow().values, vec![43]);
}

#[test]
fn test_multiple_stateful_calls() {
    let state = Rc::new(RefCell::new(TestState::new()));

    let result1 = process_data(&state, 5, 10);
    let result2 = process_data(&state, 15, 25);

    // First call: 1 + 2 + (5*2) + 10 = 23
    assert_eq!(result1, 23);

    // Second call: 4 + 5 + (15*2) + 25 = 64
    assert_eq!(result2, 64);

    // The counter should be 6 (two calls with 3 increments each)
    assert_eq!(state.borrow().counter, 6);

    // The stored values should be 23 and 64
    assert_eq!(state.borrow().values, vec![23, 64]);
}

// Edge case: Test double-colon within a string literal
#[test]
fn test_string_with_double_colon() {
    let state = Rc::new(RefCell::new(TestState::new()));

    with_state! { &state;
        let _s = "This::should::not::be::transformed"; // Using _ prefix to avoid unused variable warning
        let x = ::param();
        ::store(x);
    }

    // The counter should be 1
    assert_eq!(state.borrow().counter, 1);
    // The stored value should be 1
    assert_eq!(state.borrow().values, vec![1]);
}

// This tests the state parameter name collision avoidance
// The variable name 'state' is already used in the function,
// so the macro should choose a different name for the state parameter
#[stateful(&State<TestState>)]
fn function_with_state_variable(x: i32) -> i32 {
    // This variable would conflict with the default state parameter name
    let state = "this is a local variable";

    // Get the state reference with a different name to avoid collisions
    let state_ref = ::get();

    // Use the state reference explicitly to demonstrate it works
    let result = param(state_ref) + x;

    // Verify local variable is preserved
    assert_eq!(state, "this is a local variable");

    result
}

#[test]
fn test_state_param_collision_avoidance() {
    let test_state = Rc::new(RefCell::new(TestState::new()));

    // Call the function that has a local 'state' variable
    let result = function_with_state_variable(&test_state, 10);

    // The result should be 1 + 10 = 11
    assert_eq!(result, 11);

    // The counter should be 1
    assert_eq!(test_state.borrow().counter, 1);
}

// Tests more complex collision scenarios with state and state-prefixed variables
#[stateful(&State<TestState>)]
fn function_with_multiple_state_variables(x: i32) -> i32 {
    // Multiple variables that would cause collisions with naive suffixing
    let state = "state variable";
    let state1 = "state1 variable";
    let state2 = "state2 variable";

    // The macro should find a different name for the state parameter
    // that doesn't conflict with any of the above variables
    let result = ::param() + x;

    // Verify all local variables are intact
    assert_eq!(state, "state variable");
    assert_eq!(state1, "state1 variable");
    assert_eq!(state2, "state2 variable");

    result
}

#[test]
fn test_complex_state_param_collision() {
    let test_state = Rc::new(RefCell::new(TestState::new()));

    // Call the function with multiple state-prefixed variables
    let result = function_with_multiple_state_variables(&test_state, 20);

    // The result should be 1 + 20 = 21
    assert_eq!(result, 21);

    // The counter should be 1
    assert_eq!(test_state.borrow().counter, 1);
}