Las macros procedurales son una de las herramientas más potentes de Rust, permitiendo realizar metaprogramación mediante la manipulación de TokenStreams durante la fase de compilación. A diferencia de las macros declarativas (macro_rules!), que se basan en el patrón de sustitución de texto, las macros procedurales son funciones de Rust que operan sobre el código fuente, pudiendo analizarlo sintácticamente y transformarlo de forma arbitraria.

Existen dos tipos principales: las macros de atributo y las macros tipo función.

Las macros de atributo se aplican a un ítem (como una función o una estructura) usando la sintaxis #[nombre_macro(argumentos)]. Reciben dos parámetros: el TokenStream de los argumentos del atributo y el TokenStream del ítem sobre el que se aplican. Su característica distintiva es que pueden reemplazar completamente el ítem original por un código totalmente distinto. Son ideales para inyectar lógica de logging, implementar rasgos (traits) automáticamente o validar invariantes en tiempo de compilación.

Las macros tipo función se invocan como nombre_macro!(...). Aunque su sintaxis es similar a las macros declarativas, su potencia es mayor, ya que pueden utilizar toda la lógica de Rust para generar código. A diferencia de los derive (que solo pueden añadir código al ítem), estas macros pueden tomar cualquier secuencia de tokens y transformarla en un bloque de código nuevo.

Un aspecto crítico es el manejo de errores. Debido a que las macros se ejecutan durante la compilación, un error mal gestionado puede resultar en mensajes crípticos para el usuario. Para mitigar esto, se utiliza el módulo Span, que permite asociar un error de compilación a una posición exacta del código original del usuario, en lugar de apuntar al interior de la macro.

// Nota: Este código debe residir en un crate con `proc-macro = true` en su Cargo.toml
extern crate proc_macro;
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, ItemFn, TokenStream as SynTokenStream};

/// Macro de atributo: #[trace_call]
/// Envuelve el cuerpo de una función con un mensaje de log.
#[proc_macro_attribute]
pub fn trace_call(_attr: TokenStream, item: TokenStream) -> TokenStream {
    // Parseamos el ítem (la función) como una estructura ItemFn de `syn`
    let input = parse_macro_input!(item as ItemFn);
    
    let name = &input.sig.ident; // Identificador de la función
    let vis = &input.vis;        // Visibilidad (pub, etc.)
    let sig = &input.sig;        // Firma de la función
    let body = &input.block;     // Cuerpo de la función

    // Generamos un nuevo TokenStream que reemplaza la función original
    let expanded = quote! {
        #vis #sig {
            println!("[LOG] Ejecutando: {}", stringify!(#name));
            // Usamos un bloque para ejecutar el cuerpo original sin interferir con el retorno
            let _ = (|| #body)(); 
            println!("[LOG] Finalizado: {}", stringify!(#name));
        }
    };

    expanded.into()
}

/// Macro tipo función: make_array![1, 2, 3]
/// Transforma una lista de elementos en un array de Rust de tamaño fijo.
#[proc_macro]
pub fn make_array(input: TokenStream) -> TokenStream {
    // El input es la lista de tokens dentro de los corchetes
    let elements = input;

    // Construimos el nuevo código: envuelve los elementos en corchetes []
    let expanded = quote! {
        [#elements]
    };

    expanded.into()
}

Explicación del Código

1. Macro de Atributo trace_call:
_attr: TokenStream: Recibe los argumentos pasados al atributo (en este caso, ignorados con _).
item: TokenStream: Contiene el código de la función que se está anotando.
parse_macro_input!(item as ItemFn): Utiliza la librería syn para transformar los tokens genéricos en una estructura ItemFn, permitiéndonos acceder a campos como .sig (firma) o .block (cuerpo).
name, vis, sig, body: Se extraen estas propiedades de la función original para reconstruirla.
quote! { ... }: La macro quote permite escribir código Rust con la sintaxis estándar. Aquí, se reconstruye la función (#vis #sig) pero insertando llamadas a println! antes y después de la ejecución del bloque original (#body). El bloque se envuelve en una clausura (|| #body)() para asegurar que el flujo de control se mantenga limpio.

2. Macro tipo función make_array:
input: TokenStream: A diferencia de la macro de atributo, solo recibe un flujo de tokens (lo que está dentro de make_array!(...)).
elements: Representa los tokens crudos pasados por el usuario (por ejemplo, 1, 2, 3).
expanded: La macro quote! toma esos tokens y los encapsula entre corchetes [], transformando una lista separada por comas en un array válido de Rust.

102