prebindgen 0.4.1

Separate FFI implementation and language-specific binding into different crates
Documentation
//! Strip the specified derive attributes from the items in the source.

use std::collections::HashSet;

use quote::ToTokens;
use roxygen::roxygen;
use syn::Item;

use crate::api::record::SourceLocation;

/// Builder for configuring StripDerives instances
///
/// Configures which derive attributes should be stripped from types
/// to prevent compilation issues in FfiConverter output.
///
/// # Example
///
/// ```
/// let builder = prebindgen::map::strip_derive::Builder::new()
///     .strip_derive("Debug")
///     .strip_derive("Clone")
///     .strip_derive("Default")
///     .build();
/// ```
pub struct Builder {
    derive_attrs: HashSet<String>,
}

impl Builder {
    /// Create a new Builder for configuring StripDerives
    ///
    /// # Example
    ///
    /// ```
    /// let builder = prebindgen::map::strip_derive::Builder::new();
    /// ```
    pub fn new() -> Self {
        Self {
            derive_attrs: HashSet::new(),
        }
    }

    /// Add a derive attribute to strip from the items.
    ///
    /// # Example
    ///
    /// ```
    /// let strip = prebindgen::map::strip_derive::Builder::new()
    ///     .strip_derive("Debug")
    ///     .strip_derive("Clone")
    ///     .build();
    /// ```
    #[roxygen]
    pub fn strip_derive<S: Into<String>>(
        mut self,
        /// The derive attribute to strip
        derive: S,
    ) -> Self {
        self.derive_attrs.insert(derive.into());
        self
    }

    /// Build the StripDerives instance with the configured options
    ///
    /// # Example
    ///
    /// ```
    /// let strip_derives = prebindgen::map::strip_derive::Builder::new()
    ///     .strip_derive("Debug")
    ///     .build();
    /// ```
    pub fn build(self) -> StripDerives {
        StripDerives { builder: self }
    }
}

impl Default for Builder {
    fn default() -> Self {
        Self::new()
    }
}

/// Strips derive attributes from types to prevent compilation issues in FfiConverter output
///
/// Types generated by `FfiConverter` are size- and alignment-equal copies of original types,
/// designed for FFI compatibility rather than full functionality. Automatic derives can cause
/// compilation failures when the copied types don't include all necessary trait implementations.
///
/// # Problem
///
/// When `FfiConverter` copies types, it may encounter situations like:
/// - Type A has `#[derive(Clone)]` and contains field of type B
/// - Type B has an explicit `Clone` implementation in the source crate
/// - The copied type B doesn't include the explicit `Clone` implementation
/// - Result: `#[derive(Clone)]` on copied type A fails to compile
///
/// # Solution
///
/// Strip problematic derive attributes from copied types. Since these types are only used
/// for memory layout compatibility and FFI calls, most derives are unnecessary.
///
/// # Example
/// ```
/// # prebindgen::Source::init_doctest_simulate();
/// let source = prebindgen::Source::new("source_ffi");
///
/// let strip_derives = prebindgen::map::StripDerives::builder()
///     .strip_derive("Debug")
///     .strip_derive("Clone")
///     .strip_derive("Default")
///     .build();
///
/// // Apply to items before FfiConverter processing
/// let processed_items: Vec<_> = source
///     .items_all()
///     .map(strip_derives.into_closure())
///     .take(0) // Take 0 for doctest
///     .collect();
/// ```
pub struct StripDerives {
    builder: Builder,
}

impl StripDerives {
    /// Create a builder for configuring a strip derive instance
    ///
    /// # Example
    ///
    /// ```
    /// let strip_derives = prebindgen::map::StripDerives::builder()
    ///     .strip_derive("Debug")
    ///     .strip_derive("Clone")
    ///     .build();
    /// ```
    pub fn builder() -> Builder {
        Builder::new()
    }

    /// Process a single item to strip specified derive attributes
    ///
    /// Removes the configured derive attributes from struct and enum items.
    /// Used internally by `into_closure()` for integration with `map`.
    ///
    /// # Parameters
    ///
    /// * `item` - A `(syn::Item, SourceLocation)` pair to process
    ///
    /// # Returns
    ///
    /// The same item with specified derive attributes removed
    pub fn call(&self, item: (Item, SourceLocation)) -> (Item, SourceLocation) {
        let (mut item, source_location) = item;
        match &mut item {
            Item::Struct(s) => {
                self.strip_derives_from_attrs(&mut s.attrs);
            }
            Item::Enum(e) => {
                self.strip_derives_from_attrs(&mut e.attrs);
            }
            _ => {}
        }
        (item, source_location)
    }

    fn strip_derives_from_attrs(&self, attrs: &mut Vec<syn::Attribute>) {
        for attr in attrs.iter_mut() {
            if attr.path().is_ident("derive") {
                if let Ok(list) = attr.parse_args_with(
                    syn::punctuated::Punctuated::<syn::Path, syn::Token![,]>::parse_terminated,
                ) {
                    let filtered: syn::punctuated::Punctuated<syn::Path, syn::Token![,]> = list
                        .into_iter()
                        .filter(|path| {
                            !self
                                .builder
                                .derive_attrs
                                .contains(&path.get_ident().unwrap().to_string())
                        })
                        .collect();

                    attr.meta = syn::Meta::List(syn::MetaList {
                        path: attr.path().clone(),
                        delimiter: syn::MacroDelimiter::Paren(Default::default()),
                        tokens: filtered.to_token_stream(),
                    });
                }
            }
        }

        attrs.retain(|attr| {
            !attr.path().is_ident("derive")
                || attr
                    .parse_args_with(
                        syn::punctuated::Punctuated::<syn::Path, syn::Token![,]>::parse_terminated,
                    )
                    .map_or(
                        true,
                        |list: syn::punctuated::Punctuated<syn::Path, syn::Token![,]>| {
                            !list.is_empty()
                        },
                    )
        });
    }

    /// Convert to closure compatible with `map`
    ///
    /// This is the primary method for using `StripDerives` in processing pipelines.
    /// The returned closure can be passed to `map()` to remove derive attributes
    /// from items before further processing.
    ///
    /// # Example
    ///
    /// ```
    /// # prebindgen::Source::init_doctest_simulate();
    /// let source = prebindgen::Source::new("source_ffi");
    /// let strip_derives = prebindgen::map::StripDerives::builder()
    ///     .strip_derive("Debug")
    ///     .build();
    ///
    /// // Use with map
    /// let processed_items: Vec<_> = source
    ///     .items_all()
    ///     .map(strip_derives.into_closure())
    ///     .take(0) // Take 0 for doctest
    ///     .collect();
    /// ```
    pub fn into_closure(self) -> impl FnMut((Item, SourceLocation)) -> (Item, SourceLocation) {
        move |item| self.call(item)
    }
}