ryo-source 0.1.0

High-speed Rust AST manipulation engine
Documentation
//! ToSyn implementations for impl blocks.

use syn::token;

use super::helpers::{ident, make_abi, try_parse_path};
use super::{ToSyn, ToSynError};
use crate::pure::ast::{PureImpl, PureImplItem};

impl ToSyn for PureImpl {
    type Output = syn::ItemImpl;

    fn to_syn(&self) -> Result<syn::ItemImpl, ToSynError> {
        Ok(syn::ItemImpl {
            attrs: self
                .attrs
                .iter()
                .map(|a| a.to_syn())
                .collect::<Result<Vec<_>, _>>()?,
            defaultness: None,
            unsafety: if self.is_unsafe {
                Some(token::Unsafe::default())
            } else {
                None
            },
            impl_token: token::Impl::default(),
            generics: self.generics.to_syn()?,
            trait_: self
                .trait_
                .as_ref()
                .map(|t| Ok((None, try_parse_path(t)?, token::For::default())))
                .transpose()?,
            self_ty: Box::new(syn::parse_str(&self.self_ty).map_err(|e| {
                ToSynError::ParseType {
                    input: self.self_ty.clone(),
                    message: e.to_string(),
                }
            })?),
            brace_token: token::Brace::default(),
            items: self
                .items
                .iter()
                .map(|i| i.to_syn())
                .collect::<Result<Vec<_>, _>>()?,
        })
    }
}

impl ToSyn for PureImplItem {
    type Output = syn::ImplItem;

    fn to_syn(&self) -> Result<syn::ImplItem, ToSynError> {
        Ok(match self {
            PureImplItem::Fn(f) => syn::ImplItem::Fn(syn::ImplItemFn {
                attrs: f
                    .attrs
                    .iter()
                    .map(|a| a.to_syn())
                    .collect::<Result<Vec<_>, _>>()?,
                vis: f.vis.to_syn()?,
                defaultness: None,
                sig: syn::Signature {
                    constness: if f.is_const {
                        Some(token::Const::default())
                    } else {
                        None
                    },
                    asyncness: if f.is_async {
                        Some(token::Async::default())
                    } else {
                        None
                    },
                    unsafety: if f.is_unsafe {
                        Some(token::Unsafe::default())
                    } else {
                        None
                    },
                    abi: f.abi.as_ref().map(|a| make_abi(a)),
                    fn_token: token::Fn::default(),
                    ident: ident(&f.name),
                    generics: f.generics.to_syn()?,
                    paren_token: token::Paren::default(),
                    inputs: f
                        .params
                        .iter()
                        .map(|p| p.to_syn())
                        .collect::<Result<_, _>>()?,
                    variadic: None,
                    output: match &f.ret {
                        Some(ty) => {
                            syn::ReturnType::Type(token::RArrow::default(), Box::new(ty.to_syn()?))
                        }
                        None => syn::ReturnType::Default,
                    },
                },
                block: f.body.to_syn()?,
            }),
            PureImplItem::Const(c) => syn::ImplItem::Const(syn::ImplItemConst {
                attrs: c
                    .attrs
                    .iter()
                    .map(|a| a.to_syn())
                    .collect::<Result<Vec<_>, _>>()?,
                vis: c.vis.to_syn()?,
                defaultness: None,
                const_token: token::Const::default(),
                ident: ident(&c.name),
                generics: syn::Generics::default(),
                colon_token: token::Colon::default(),
                ty: c.ty.to_syn()?,
                eq_token: token::Eq::default(),
                expr: c
                    .value
                    .as_ref()
                    .ok_or_else(|| ToSynError::MissingValue {
                        context: format!("ImplItemConst '{}' must have a value", c.name),
                    })?
                    .to_syn()?,
                semi_token: token::Semi::default(),
            }),
            PureImplItem::Type(t) => syn::ImplItem::Type(syn::ImplItemType {
                attrs: t
                    .attrs
                    .iter()
                    .map(|a| a.to_syn())
                    .collect::<Result<Vec<_>, _>>()?,
                vis: t.vis.to_syn()?,
                defaultness: None,
                type_token: token::Type::default(),
                ident: ident(&t.name),
                generics: t.generics.to_syn()?,
                eq_token: token::Eq::default(),
                ty: t.ty.to_syn()?,
                semi_token: token::Semi::default(),
            }),
            PureImplItem::Other(s) => syn::parse_str(s).map_err(|e| ToSynError::Other {
                message: format!("Failed to parse impl item '{}': {}", s, e),
            })?,
        })
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::pure::ast::{PureBlock, PureFn, PureGenerics, PureType, PureVis};
    use quote::ToTokens;

    #[test]
    fn test_pure_impl_inherent() {
        let impl_block = PureImpl {
            attrs: vec![],
            generics: PureGenerics::default(),
            is_unsafe: false,
            trait_: None,
            self_ty: "Foo".to_string(),
            items: vec![],
        };
        let syn_impl = impl_block.to_syn().unwrap();
        let output = syn_impl.to_token_stream().to_string();
        assert!(output.contains("impl"), "Output: {}", output);
        assert!(output.contains("Foo"), "Output: {}", output);
    }

    #[test]
    fn test_pure_impl_trait() {
        let impl_block = PureImpl {
            attrs: vec![],
            generics: PureGenerics::default(),
            is_unsafe: false,
            trait_: Some("Clone".to_string()),
            self_ty: "Foo".to_string(),
            items: vec![],
        };
        let syn_impl = impl_block.to_syn().unwrap();
        let output = syn_impl.to_token_stream().to_string();
        assert!(output.contains("Clone"), "Output: {}", output);
        assert!(output.contains("for"), "Output: {}", output);
    }

    #[test]
    fn test_pure_impl_with_method() {
        let impl_block = PureImpl {
            attrs: vec![],
            generics: PureGenerics::default(),
            is_unsafe: false,
            trait_: None,
            self_ty: "Foo".to_string(),
            items: vec![PureImplItem::Fn(PureFn {
                attrs: vec![],
                vis: PureVis::Public,
                is_async: false,
                is_async_inferred: false,
                is_const: false,
                is_unsafe: false,
                abi: None,
                name: "new".to_string(),
                generics: PureGenerics::default(),
                params: vec![],
                ret: Some(PureType::Path("Self".to_string())),
                body: PureBlock::default(),
            })],
        };
        let syn_impl = impl_block.to_syn().unwrap();
        let output = syn_impl.to_token_stream().to_string();
        assert!(output.contains("new"), "Output: {}", output);
        assert!(output.contains("Self"), "Output: {}", output);
    }
}