murf-macros 0.2.0

Murf's proc macros
Documentation
#![allow(clippy::arc_with_non_send_sync)]

use std::ops::Deref;
use std::sync::Arc;

use convert_case::{Case, Casing};
use proc_macro2::{Ident, Span, TokenStream};
use quote::{format_ident, quote};
use syn::{
    token::Colon, FnArg, Generics, ImplItem, ImplItemFn, ItemImpl, Lifetime, Pat, PatType, Path,
    ReturnType, Type,
};

use crate::misc::{
    format_expect_call, format_expect_module, format_expectations_field, ident_murf, AttribsEx,
    GenericsEx, ItemImplEx, LifetimeReplaceMode, MethodEx, ReturnTypeEx, TempLifetimes, TypeEx,
};

use super::parsed::Parsed;

/* Context */

#[derive(Clone)]
pub(crate) struct Context(Arc<ContextData>);

pub(crate) struct ContextData {
    pub ident_murf: Ident,
    pub ident_module: Ident,
    pub ident_mock: Ident,
    pub ident_handle: Ident,
    pub ident_state: Ident,

    pub ga_mock: Generics,
    pub ga_state: Generics,
    pub ga_handle: Generics,

    pub derive_clone: bool,
    pub derive_default: bool,

    pub trait_send: Option<TokenStream>,
    pub trait_sync: Option<TokenStream>,

    pub extern_mock_lifetime: bool,
}

impl Context {
    pub(crate) fn new(parsed: &Parsed) -> Self {
        let ident_murf = ident_murf();

        let ident = parsed.ty.ident().to_string();
        let ident_mock = format_ident!("{}Mock", ident);
        let ident_handle = format_ident!("{}Handle", ident);

        let ident = ident.to_case(Case::Snake);
        let ident_module = format_ident!("mock_impl_{}", ident);

        let ident_state = parsed.ty.ident().clone();

        let ga_state = parsed.ty.generics().clone();
        let ga_mock = ga_state.clone().add_lifetime("'mock");

        let (_ga_mock_impl, ga_mock_types, _ga_mock_where) = ga_mock.split_for_impl();
        let type_mock = Type::Verbatim(quote!( Mock #ga_mock_types ));

        let mut changed = false;
        let ga_handle = ga_mock.clone().replace_self_type(&type_mock, &mut changed);

        let derive_send = parsed.derive_send;
        let derive_sync = parsed.derive_sync;
        let derive_clone = parsed.ty.derives("Clone");
        let derive_default = parsed.ty.derives("Default");

        let trait_send = derive_send.then(|| quote!(+ Send));
        let trait_sync = derive_sync.then(|| quote!(+ Sync));

        let extern_mock_lifetime = ga_state.lifetimes().any(|lt| lt.lifetime.ident == "mock");

        Self(Arc::new(ContextData {
            ident_murf,
            ident_module,
            ident_mock,
            ident_handle,
            ident_state,

            ga_mock,
            ga_state,
            ga_handle,

            derive_clone,
            derive_default,

            trait_send,
            trait_sync,

            extern_mock_lifetime,
        }))
    }
}

impl Deref for Context {
    type Target = ContextData;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

/* ImplContext */

#[derive(Clone)]
pub(crate) struct ImplContext(Arc<ImplContextData>);

impl ImplContext {
    pub(crate) fn new(context: Context, impl_: &ItemImpl) -> Self {
        let ga_impl = impl_.generics.clone();
        let (impl_, _lts_temp) = impl_.clone().split_off_temp_lifetimes();

        let need_static_lt = impl_.items.iter().any(|i| {
            if let ImplItem::Fn(f) = i {
                f.is_associated_fn()
                    && matches!(&f.sig.output, ReturnType::Type(_, t) if t.contains_self_type())
            } else {
                false
            }
        });

        let mut ga_impl_mock = ga_impl.clone().add_lifetime("'mock");
        if need_static_lt {
            ga_impl_mock
                .get_lifetime_mut("'mock")
                .unwrap()
                .bounds
                .push(Lifetime::new("'static", Span::call_site()));
        }

        let trait_ = impl_.trait_.as_ref().map(|(_, p, _)| p).cloned();

        Self(Arc::new(ImplContextData {
            context,

            trait_,

            ga_impl,
            ga_impl_mock,
        }))
    }
}

impl Deref for ImplContext {
    type Target = ImplContextData;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

pub(crate) struct ImplContextData {
    pub context: Context,

    pub trait_: Option<Path>,

    pub ga_impl: Generics,
    pub ga_impl_mock: Generics,
}

impl Deref for ImplContextData {
    type Target = Context;

    fn deref(&self) -> &Self::Target {
        &self.context
    }
}

/* MethodContext */

#[derive(Clone)]
pub(crate) struct MethodContext(Arc<MethodContextData>);

impl MethodContext {
    #[allow(clippy::too_many_lines)]
    pub(crate) fn new(context: ImplContext, impl_: &ItemImpl, method: &ImplItemFn) -> Self {
        let is_associated = method.is_associated_fn();
        let no_default_impl = method.has_murf_attr("no_default_impl");

        let (impl_, lts_temp) = impl_.clone().split_off_temp_lifetimes();
        let trait_ = impl_.trait_.as_ref().map(|(_, x, _)| x).cloned();

        let args = method.sig.inputs.iter().cloned().collect::<Vec<_>>();
        let ret = method.sig.output.clone();

        let (_ga_mock_impl, ga_mock_types, _ga_mock_where) = context.ga_mock.split_for_impl();
        let type_mock = Type::Verbatim(quote!( Mock #ga_mock_types ));

        let mut has_self_arg = false;
        let mut lts_mock = lts_temp.clone();

        let args_prepared = args
            .iter()
            .map(|arg| match arg {
                FnArg::Receiver(t) => PatType {
                    attrs: t.attrs.clone(),
                    pat: Box::new(Pat::Verbatim(quote!(this))),
                    colon_token: Colon::default(),
                    ty: Box::new(
                        t.ty.clone()
                            .replace_self_type(&type_mock, &mut has_self_arg),
                    ),
                },
                FnArg::Typed(t) => PatType {
                    attrs: t.attrs.clone(),
                    pat: t.pat.clone(),
                    colon_token: t.colon_token,
                    ty: Box::new(
                        t.ty.clone()
                            .replace_self_type(&type_mock, &mut has_self_arg),
                    ),
                },
            })
            .collect::<Vec<_>>();
        let args_prepared_lt = args_prepared
            .iter()
            .cloned()
            .map(|mut t| {
                t.ty = Box::new(
                    t.ty.clone()
                        .replace_default_lifetime(LifetimeReplaceMode::Temp(&mut lts_mock)),
                );

                t
            })
            .collect::<Vec<_>>();

        let mut has_self_ret = false;
        let return_type = ret.to_action_return_type(&type_mock, &mut has_self_ret);

        let type_signature = args_prepared
            .iter()
            .map(|t| t.ty.deref().clone())
            .chain(Some(return_type.clone()))
            .map(TypeEx::make_static)
            .collect();

        let ident_method = method.sig.ident.clone();
        let ident_expect_method = format_expect_call(&ident_method, trait_.as_ref());
        let ident_expectation_module = format_expect_module(&ident_method, trait_.as_ref());
        let ident_expectation_field = format_expectations_field(&ident_expectation_module);

        let mut ga_expectation = context
            .ga_impl
            .clone()
            .merge(&method.sig.generics)
            .replace_self_type(&type_mock, &mut has_self_arg);
        let ga_method = ga_expectation.clone().remove_other(&context.ga_state);

        if !is_associated || has_self_arg || has_self_ret {
            ga_expectation = ga_expectation.add_lifetime("'mock");
            if is_associated && has_self_ret {
                ga_expectation
                    .get_lifetime_mut("'mock")
                    .unwrap()
                    .bounds
                    .push(Lifetime::new("'static", Span::call_site()));
            }
        };
        let ga_expectation = ga_expectation.remove_lifetimes(&lts_temp);

        let mut ga_expectation_builder = ga_expectation
            .clone()
            .add_lifetime_bounds("'mock")
            .add_lifetime("'mock")
            .add_lifetime("'mock_exp")
            .remove_lifetimes(&lts_temp);
        if is_associated && has_self_ret {
            ga_expectation_builder
                .get_lifetime_mut("'mock")
                .unwrap()
                .bounds
                .push(Lifetime::new("'static", Span::call_site()));
        }

        Self(Arc::new(MethodContextData {
            context,

            is_associated,
            no_default_impl,

            impl_,
            trait_,

            ga_method,
            ga_expectation,
            ga_expectation_builder,

            args,
            ret,

            lts_temp,
            lts_mock,

            args_prepared,
            args_prepared_lt,
            return_type,
            type_signature,

            ident_method,
            ident_expect_method,
            ident_expectation_module,
            ident_expectation_field,
        }))
    }
}

impl Deref for MethodContext {
    type Target = MethodContextData;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

pub(crate) struct MethodContextData {
    pub context: ImplContext,

    pub is_associated: bool,
    pub no_default_impl: bool,

    pub impl_: ItemImpl,
    pub trait_: Option<Path>,

    pub ga_method: Generics,
    pub ga_expectation: Generics,
    pub ga_expectation_builder: Generics,

    pub args: Vec<FnArg>,
    pub ret: ReturnType,

    pub lts_temp: TempLifetimes,
    pub lts_mock: TempLifetimes,

    pub args_prepared: Vec<PatType>,
    pub args_prepared_lt: Vec<PatType>,
    pub return_type: Type,
    pub type_signature: Vec<Type>,

    pub ident_method: Ident,
    pub ident_expect_method: Ident,
    pub ident_expectation_module: Ident,
    pub ident_expectation_field: Ident,
}

impl Deref for MethodContextData {
    type Target = ImplContext;

    fn deref(&self) -> &Self::Target {
        &self.context
    }
}