ryo-source 0.1.0

High-speed Rust AST manipulation engine
Documentation
//! ToSyn implementations for structs and enums.

use syn::token;

use super::helpers::ident;
use super::{ToSyn, ToSynError};
use crate::pure::ast::{PureEnum, PureField, PureFields, PureStruct, PureVariant};

impl ToSyn for PureStruct {
    type Output = syn::ItemStruct;

    fn to_syn(&self) -> Result<syn::ItemStruct, ToSynError> {
        Ok(syn::ItemStruct {
            attrs: self
                .attrs
                .iter()
                .map(|a| a.to_syn())
                .collect::<Result<Vec<_>, _>>()?,
            vis: self.vis.to_syn()?,
            struct_token: token::Struct::default(),
            ident: ident(&self.name),
            generics: self.generics.to_syn()?,
            fields: self.fields.to_syn()?,
            semi_token: matches!(self.fields, PureFields::Unit | PureFields::Tuple(_))
                .then(token::Semi::default),
        })
    }
}

impl ToSyn for PureFields {
    type Output = syn::Fields;

    fn to_syn(&self) -> Result<syn::Fields, ToSynError> {
        match self {
            PureFields::Named(fields) => Ok(syn::Fields::Named(syn::FieldsNamed {
                brace_token: token::Brace::default(),
                named: fields
                    .iter()
                    .map(|f| f.to_syn())
                    .collect::<Result<_, _>>()?,
            })),
            PureFields::Tuple(types) => Ok(syn::Fields::Unnamed(syn::FieldsUnnamed {
                paren_token: token::Paren::default(),
                unnamed: types
                    .iter()
                    .map(|ty| {
                        Ok(syn::Field {
                            attrs: vec![],
                            vis: syn::Visibility::Inherited,
                            mutability: syn::FieldMutability::None,
                            ident: None,
                            colon_token: None,
                            ty: ty.to_syn()?,
                        })
                    })
                    .collect::<Result<_, ToSynError>>()?,
            })),
            PureFields::Unit => Ok(syn::Fields::Unit),
        }
    }
}

impl ToSyn for PureField {
    type Output = syn::Field;

    fn to_syn(&self) -> Result<syn::Field, ToSynError> {
        Ok(syn::Field {
            attrs: self
                .attrs
                .iter()
                .map(|a| a.to_syn())
                .collect::<Result<Vec<_>, _>>()?,
            vis: self.vis.to_syn()?,
            mutability: syn::FieldMutability::None,
            ident: Some(ident(&self.name)),
            colon_token: Some(token::Colon::default()),
            ty: self.ty.to_syn()?,
        })
    }
}

impl ToSyn for PureEnum {
    type Output = syn::ItemEnum;

    fn to_syn(&self) -> Result<syn::ItemEnum, ToSynError> {
        Ok(syn::ItemEnum {
            attrs: self
                .attrs
                .iter()
                .map(|a| a.to_syn())
                .collect::<Result<Vec<_>, _>>()?,
            vis: self.vis.to_syn()?,
            enum_token: token::Enum::default(),
            ident: ident(&self.name),
            generics: self.generics.to_syn()?,
            brace_token: token::Brace::default(),
            variants: self
                .variants
                .iter()
                .map(|v| v.to_syn())
                .collect::<Result<_, _>>()?,
        })
    }
}

impl ToSyn for PureVariant {
    type Output = syn::Variant;

    fn to_syn(&self) -> Result<syn::Variant, ToSynError> {
        let discriminant = self
            .discriminant
            .as_ref()
            .map(|d| {
                let expr: syn::Expr = syn::parse_str(d).map_err(|e| ToSynError::ParseExpr {
                    input: d.to_string(),
                    message: e.to_string(),
                })?;
                Ok((token::Eq::default(), expr))
            })
            .transpose()?;

        Ok(syn::Variant {
            attrs: self
                .attrs
                .iter()
                .map(|a| a.to_syn())
                .collect::<Result<Vec<_>, _>>()?,
            ident: ident(&self.name),
            fields: self.fields.to_syn()?,
            discriminant,
        })
    }
}

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

    #[test]
    fn test_pure_struct_simple() {
        let s = PureStruct {
            attrs: vec![],
            vis: PureVis::Public,
            name: "Foo".to_string(),
            generics: PureGenerics::default(),
            fields: PureFields::Named(vec![PureField {
                attrs: vec![],
                vis: PureVis::Private,
                name: "x".to_string(),
                ty: PureType::Path("i32".to_string()),
            }]),
        };
        let syn_struct = s.to_syn().unwrap();
        let output = syn_struct.to_token_stream().to_string();
        assert!(output.contains("pub"), "Output: {}", output);
        assert!(output.contains("Foo"), "Output: {}", output);
        assert!(output.contains("x"), "Output: {}", output);
    }

    #[test]
    fn test_pure_struct_tuple() {
        let s = PureStruct {
            attrs: vec![],
            vis: PureVis::Private,
            name: "Point".to_string(),
            generics: PureGenerics::default(),
            fields: PureFields::Tuple(vec![
                PureType::Path("i32".to_string()),
                PureType::Path("i32".to_string()),
            ]),
        };
        let syn_struct = s.to_syn().unwrap();
        let output = syn_struct.to_token_stream().to_string();
        assert!(output.contains("Point"), "Output: {}", output);
        assert!(output.contains("i32"), "Output: {}", output);
    }

    #[test]
    fn test_pure_enum_simple() {
        let e = PureEnum {
            attrs: vec![],
            vis: PureVis::Public,
            name: "Status".to_string(),
            generics: PureGenerics::default(),
            variants: vec![
                PureVariant {
                    attrs: vec![],
                    name: "Active".to_string(),
                    fields: PureFields::Unit,
                    discriminant: None,
                },
                PureVariant {
                    attrs: vec![],
                    name: "Inactive".to_string(),
                    fields: PureFields::Unit,
                    discriminant: None,
                },
            ],
        };
        let syn_enum = e.to_syn().unwrap();
        let output = syn_enum.to_token_stream().to_string();
        assert!(output.contains("Status"), "Output: {}", output);
        assert!(output.contains("Active"), "Output: {}", output);
        assert!(output.contains("Inactive"), "Output: {}", output);
    }

    #[test]
    fn test_pure_enum_with_discriminant() {
        let e = PureEnum {
            attrs: vec![],
            vis: PureVis::Private,
            name: "Code".to_string(),
            generics: PureGenerics::default(),
            variants: vec![PureVariant {
                attrs: vec![],
                name: "A".to_string(),
                fields: PureFields::Unit,
                discriminant: Some("1".to_string()),
            }],
        };
        let syn_enum = e.to_syn().unwrap();
        let output = syn_enum.to_token_stream().to_string();
        assert!(output.contains("= 1"), "Output: {}", output);
    }
}