//! Procedural macro backing `alloc_once!`.
//!
//! `alloc_once!(v1, v2, ...)` packs the values into a single tuple, allocates
//! that tuple once inside a freshly created bump arena, and returns an
//! `alloc_once::AllocOnce` guard that owns the arena. All bumpalo details are
//! hidden: the expansion only names paths under `::alloc_once`, so callers do
//! not need a bumpalo dependency of their own.

use proc_macro::TokenStream;
use quote::quote;
use syn::parse::{Parse, ParseStream};
use syn::punctuated::Punctuated;
use syn::{Expr, Token};

/// The parsed macro input: a comma-separated list of value expressions.
struct Values {
    values: Punctuated<Expr, Token![,]>,
}

impl Parse for Values {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let values = Punctuated::parse_terminated(input)?;
        if values.is_empty() {
            return Err(syn::Error::new(
                input.span(),
                "alloc_once! needs at least one value to allocate",
            ));
        }
        Ok(Values { values })
    }
}

/// Allocate several (possibly different-typed) values in a single bump
/// allocation and return a guard that owns the backing arena.
///
/// ```ignore
/// let mut guard = alloc_once!(1i32, "hi", [0u8; 4]);
/// let (a, b, c) = &mut *guard; // a: &mut i32, b: &mut &str, c: &mut [u8; 4]
/// ```
#[proc_macro]
pub fn alloc_once(input: TokenStream) -> TokenStream {
    let Values { values } = syn::parse_macro_input!(input as Values);
    let values = values.iter();

    let expanded = quote! {
        {
            // Build the bundle first. The trailing comma keeps the single-value
            // case a 1-tuple `(v,)`.
            let __alloc_once_value = ( #(#values,)* );
            // Size the arena to hold exactly this bundle and nothing more. For a
            // sized tuple this is a compile-time constant, so the arena allocates
            // a single chunk of precisely the bytes needed.
            let __alloc_once_cap = ::core::mem::size_of_val(&__alloc_once_value);
            let __alloc_once_arena =
                ::alloc_once::__private::Bump::with_capacity(__alloc_once_cap);
            let __alloc_once_ptr: *mut _ = __alloc_once_arena.alloc(__alloc_once_value);
            unsafe { ::alloc_once::AllocOnce::__from_parts(__alloc_once_arena, __alloc_once_ptr) }
        }
    };

    expanded.into()
}
