Skip to main content

ryo_source/pure/to_syn/
attr.rs

1//! ToSyn implementations for attributes and visibility.
2
3use syn::token;
4
5use super::helpers::{ident, try_parse_path};
6use super::{ToSyn, ToSynError};
7use crate::pure::ast::{PureAttrMeta, PureAttribute, PureVis};
8
9impl ToSyn for PureAttribute {
10    type Output = syn::Attribute;
11
12    fn to_syn(&self) -> Result<syn::Attribute, ToSynError> {
13        let meta = match &self.meta {
14            PureAttrMeta::Path => syn::Meta::Path(try_parse_path(&self.path)?),
15            PureAttrMeta::List(args) => {
16                let tokens: proc_macro2::TokenStream =
17                    args.parse()
18                        .map_err(|e: proc_macro2::LexError| ToSynError::Other {
19                            message: format!("Failed to parse attribute args '{}': {}", args, e),
20                        })?;
21                syn::Meta::List(syn::MetaList {
22                    path: try_parse_path(&self.path)?,
23                    delimiter: syn::MacroDelimiter::Paren(token::Paren::default()),
24                    tokens,
25                })
26            }
27            PureAttrMeta::NameValue(value) => {
28                let value_expr: syn::Expr =
29                    syn::parse_str(value).map_err(|e| ToSynError::Other {
30                        message: format!("Failed to parse attribute value '{}': {}", value, e),
31                    })?;
32                syn::Meta::NameValue(syn::MetaNameValue {
33                    path: try_parse_path(&self.path)?,
34                    eq_token: token::Eq::default(),
35                    value: value_expr,
36                })
37            }
38        };
39
40        Ok(syn::Attribute {
41            pound_token: token::Pound::default(),
42            style: if self.is_inner {
43                syn::AttrStyle::Inner(token::Not::default())
44            } else {
45                syn::AttrStyle::Outer
46            },
47            bracket_token: token::Bracket::default(),
48            meta,
49        })
50    }
51}
52
53impl ToSyn for PureVis {
54    type Output = syn::Visibility;
55
56    fn to_syn(&self) -> Result<syn::Visibility, ToSynError> {
57        match self {
58            PureVis::Private => Ok(syn::Visibility::Inherited),
59            PureVis::Public => Ok(syn::Visibility::Public(token::Pub::default())),
60            PureVis::Crate => Ok(syn::Visibility::Restricted(syn::VisRestricted {
61                pub_token: token::Pub::default(),
62                paren_token: token::Paren::default(),
63                in_token: None,
64                path: Box::new(syn::Path {
65                    leading_colon: None,
66                    segments: std::iter::once(syn::PathSegment {
67                        ident: ident("crate"),
68                        arguments: syn::PathArguments::None,
69                    })
70                    .collect(),
71                }),
72            })),
73            PureVis::Super => Ok(syn::Visibility::Restricted(syn::VisRestricted {
74                pub_token: token::Pub::default(),
75                paren_token: token::Paren::default(),
76                in_token: None,
77                path: Box::new(syn::Path {
78                    leading_colon: None,
79                    segments: std::iter::once(syn::PathSegment {
80                        ident: ident("super"),
81                        arguments: syn::PathArguments::None,
82                    })
83                    .collect(),
84                }),
85            })),
86            PureVis::In(path) => Ok(syn::Visibility::Restricted(syn::VisRestricted {
87                pub_token: token::Pub::default(),
88                paren_token: token::Paren::default(),
89                in_token: Some(token::In::default()),
90                path: Box::new(try_parse_path(path)?),
91            })),
92        }
93    }
94}
95
96#[cfg(test)]
97mod tests {
98    use super::*;
99    use quote::ToTokens;
100
101    #[test]
102    fn test_pure_attribute_path() {
103        let attr = PureAttribute {
104            path: "test".to_string(),
105            meta: PureAttrMeta::Path,
106            is_inner: false,
107        };
108        let syn_attr = attr.to_syn().unwrap();
109        let output = syn_attr.to_token_stream().to_string();
110        assert!(output.contains("test"), "Output: {}", output);
111        assert!(!output.contains("("), "Should not have parens: {}", output);
112    }
113
114    #[test]
115    fn test_pure_attribute_list() {
116        let attr = PureAttribute {
117            path: "derive".to_string(),
118            meta: PureAttrMeta::List("Debug, Clone".to_string()),
119            is_inner: false,
120        };
121        let syn_attr = attr.to_syn().unwrap();
122        let output = syn_attr.to_token_stream().to_string();
123        assert!(output.contains("derive"), "Output: {}", output);
124        assert!(output.contains("Debug"), "Output: {}", output);
125        assert!(output.contains("Clone"), "Output: {}", output);
126    }
127
128    #[test]
129    fn test_pure_attribute_name_value() {
130        let attr = PureAttribute {
131            path: "doc".to_string(),
132            meta: PureAttrMeta::NameValue("\"This is a comment\"".to_string()),
133            is_inner: false,
134        };
135        let syn_attr = attr.to_syn().unwrap();
136        let output = syn_attr.to_token_stream().to_string();
137        assert!(output.contains("doc"), "Output: {}", output);
138        assert!(output.contains("="), "Output: {}", output);
139        assert!(output.contains("comment"), "Output: {}", output);
140    }
141
142    #[test]
143    fn test_pure_attribute_inner() {
144        let attr = PureAttribute {
145            path: "allow".to_string(),
146            meta: PureAttrMeta::List("unused".to_string()),
147            is_inner: true,
148        };
149        let syn_attr = attr.to_syn().unwrap();
150        let output = syn_attr.to_token_stream().to_string();
151        // Inner attributes have # ! [ ... ] (with spaces in token stream)
152        assert!(
153            output.contains("# !") || output.contains("#!"),
154            "Should be inner: {}",
155            output
156        );
157    }
158
159    #[test]
160    fn test_pure_vis_public() {
161        let vis = PureVis::Public;
162        let syn_vis = vis.to_syn().unwrap();
163        let output = syn_vis.to_token_stream().to_string();
164        assert_eq!(output.trim(), "pub");
165    }
166
167    #[test]
168    fn test_pure_vis_crate() {
169        let vis = PureVis::Crate;
170        let syn_vis = vis.to_syn().unwrap();
171        let output = syn_vis.to_token_stream().to_string();
172        assert!(output.contains("crate"), "Output: {}", output);
173    }
174}