Skip to main content

ryo_source/pure/to_syn/
function.rs

1//! ToSyn implementations for functions, blocks, and statements.
2
3use proc_macro2::Span;
4use syn::punctuated::Punctuated;
5use syn::token;
6
7use super::helpers::{ident, make_abi};
8use super::{ToSyn, ToSynError};
9use crate::pure::ast::{PureBlock, PureFn, PureGenericParam, PureGenerics, PureParam, PureStmt};
10
11impl ToSyn for PureFn {
12    type Output = syn::ItemFn;
13
14    fn to_syn(&self) -> Result<syn::ItemFn, ToSynError> {
15        Ok(syn::ItemFn {
16            attrs: self
17                .attrs
18                .iter()
19                .map(|a| a.to_syn())
20                .collect::<Result<Vec<_>, _>>()?,
21            vis: self.vis.to_syn()?,
22            sig: syn::Signature {
23                constness: if self.is_const {
24                    Some(token::Const::default())
25                } else {
26                    None
27                },
28                asyncness: if self.is_async {
29                    Some(token::Async::default())
30                } else {
31                    None
32                },
33                unsafety: if self.is_unsafe {
34                    Some(token::Unsafe::default())
35                } else {
36                    None
37                },
38                abi: self.abi.as_ref().map(|a| make_abi(a)),
39                fn_token: token::Fn::default(),
40                ident: ident(&self.name),
41                generics: self.generics.to_syn()?,
42                paren_token: token::Paren::default(),
43                inputs: self
44                    .params
45                    .iter()
46                    .map(|p| p.to_syn())
47                    .collect::<Result<_, _>>()?,
48                variadic: None,
49                output: match &self.ret {
50                    Some(ty) => {
51                        syn::ReturnType::Type(token::RArrow::default(), Box::new(ty.to_syn()?))
52                    }
53                    None => syn::ReturnType::Default,
54                },
55            },
56            block: Box::new(self.body.to_syn()?),
57        })
58    }
59}
60
61impl ToSyn for PureParam {
62    type Output = syn::FnArg;
63
64    fn to_syn(&self) -> Result<syn::FnArg, ToSynError> {
65        Ok(match self {
66            PureParam::SelfValue { is_ref, is_mut } => {
67                // Build the proper self type based on reference/mutability
68                // For colon_token: None, the type should represent the implicit self type
69                let self_ty = if *is_ref {
70                    if *is_mut {
71                        // &mut self -> &mut Self
72                        syn::Type::Reference(syn::TypeReference {
73                            and_token: token::And::default(),
74                            lifetime: None,
75                            mutability: Some(token::Mut::default()),
76                            elem: Box::new(syn::Type::Path(syn::TypePath {
77                                qself: None,
78                                path: syn::Path::from(ident("Self")),
79                            })),
80                        })
81                    } else {
82                        // &self -> &Self
83                        syn::Type::Reference(syn::TypeReference {
84                            and_token: token::And::default(),
85                            lifetime: None,
86                            mutability: None,
87                            elem: Box::new(syn::Type::Path(syn::TypePath {
88                                qself: None,
89                                path: syn::Path::from(ident("Self")),
90                            })),
91                        })
92                    }
93                } else {
94                    // self -> Self
95                    syn::Type::Path(syn::TypePath {
96                        qself: None,
97                        path: syn::Path::from(ident("Self")),
98                    })
99                };
100
101                syn::FnArg::Receiver(syn::Receiver {
102                    attrs: vec![],
103                    reference: if *is_ref {
104                        Some((token::And::default(), None))
105                    } else {
106                        None
107                    },
108                    mutability: if *is_mut {
109                        Some(token::Mut::default())
110                    } else {
111                        None
112                    },
113                    self_token: token::SelfValue::default(),
114                    colon_token: None,
115                    ty: Box::new(self_ty),
116                })
117            }
118            PureParam::Typed { name, ty } => syn::FnArg::Typed(syn::PatType {
119                attrs: vec![],
120                pat: Box::new(syn::Pat::Ident(syn::PatIdent {
121                    attrs: vec![],
122                    by_ref: None,
123                    mutability: None,
124                    ident: ident(name),
125                    subpat: None,
126                })),
127                colon_token: token::Colon::default(),
128                ty: Box::new(ty.to_syn()?),
129            }),
130        })
131    }
132}
133
134impl ToSyn for PureGenerics {
135    type Output = syn::Generics;
136
137    fn to_syn(&self) -> Result<syn::Generics, ToSynError> {
138        let params: Punctuated<syn::GenericParam, token::Comma> = self
139            .params
140            .iter()
141            .map(|p| p.to_syn())
142            .collect::<Result<_, _>>()?;
143
144        let where_clause = if self.where_clause.is_empty() {
145            None
146        } else {
147            Some(syn::WhereClause {
148                where_token: token::Where::default(),
149                predicates: self
150                    .where_clause
151                    .iter()
152                    .filter_map(|s| syn::parse_str::<syn::WherePredicate>(s).ok())
153                    .collect(),
154            })
155        };
156
157        Ok(syn::Generics {
158            lt_token: if params.is_empty() {
159                None
160            } else {
161                Some(token::Lt::default())
162            },
163            params,
164            gt_token: if self.params.is_empty() {
165                None
166            } else {
167                Some(token::Gt::default())
168            },
169            where_clause,
170        })
171    }
172}
173
174impl ToSyn for PureGenericParam {
175    type Output = syn::GenericParam;
176
177    fn to_syn(&self) -> Result<syn::GenericParam, ToSynError> {
178        Ok(match self {
179            PureGenericParam::Type { name, bounds } => syn::GenericParam::Type(syn::TypeParam {
180                attrs: vec![],
181                ident: ident(name),
182                colon_token: if bounds.is_empty() {
183                    None
184                } else {
185                    Some(token::Colon::default())
186                },
187                bounds: bounds
188                    .iter()
189                    .filter_map(|b| syn::parse_str::<syn::TypeParamBound>(b).ok())
190                    .collect(),
191                eq_token: None,
192                default: None,
193            }),
194            PureGenericParam::Lifetime { name, bounds } => {
195                syn::GenericParam::Lifetime(syn::LifetimeParam {
196                    attrs: vec![],
197                    lifetime: syn::Lifetime::new(name, Span::call_site()),
198                    colon_token: if bounds.is_empty() {
199                        None
200                    } else {
201                        Some(token::Colon::default())
202                    },
203                    bounds: bounds
204                        .iter()
205                        .map(|b| syn::Lifetime::new(b, Span::call_site()))
206                        .collect(),
207                })
208            }
209            PureGenericParam::Const { name, ty } => syn::GenericParam::Const(syn::ConstParam {
210                attrs: vec![],
211                const_token: token::Const::default(),
212                ident: ident(name),
213                colon_token: token::Colon::default(),
214                ty: syn::parse_str(ty).map_err(|e| ToSynError::ParseType {
215                    input: ty.clone(),
216                    message: e.to_string(),
217                })?,
218                eq_token: None,
219                default: None,
220            }),
221        })
222    }
223}
224
225impl ToSyn for PureBlock {
226    type Output = syn::Block;
227
228    fn to_syn(&self) -> Result<syn::Block, ToSynError> {
229        Ok(syn::Block {
230            brace_token: token::Brace::default(),
231            stmts: self
232                .stmts
233                .iter()
234                .map(|s| s.to_syn())
235                .collect::<Result<Vec<_>, _>>()?,
236        })
237    }
238}
239
240impl ToSyn for PureStmt {
241    type Output = syn::Stmt;
242
243    fn to_syn(&self) -> Result<syn::Stmt, ToSynError> {
244        match self {
245            PureStmt::Local { pattern, ty, init } => {
246                let pat = if let Some(ty) = ty {
247                    syn::Pat::Type(syn::PatType {
248                        attrs: vec![],
249                        pat: Box::new(pattern.to_syn()?),
250                        colon_token: token::Colon::default(),
251                        ty: Box::new(ty.to_syn()?),
252                    })
253                } else {
254                    pattern.to_syn()?
255                };
256                let local_init = init
257                    .as_ref()
258                    .map(|e| {
259                        Ok(syn::LocalInit {
260                            eq_token: token::Eq::default(),
261                            expr: Box::new(e.to_syn()?),
262                            diverge: None,
263                        })
264                    })
265                    .transpose()?;
266                Ok(syn::Stmt::Local(syn::Local {
267                    attrs: vec![],
268                    let_token: token::Let::default(),
269                    pat,
270                    init: local_init,
271                    semi_token: token::Semi::default(),
272                }))
273            }
274            PureStmt::Semi(expr) => Ok(syn::Stmt::Expr(
275                expr.to_syn()?,
276                Some(token::Semi::default()),
277            )),
278            PureStmt::Expr(expr) => Ok(syn::Stmt::Expr(expr.to_syn()?, None)),
279            PureStmt::Item(item) => Ok(syn::Stmt::Item(item.to_syn()?)),
280        }
281    }
282}
283
284#[cfg(test)]
285mod tests {
286    use super::*;
287    use crate::pure::ast::{PureExpr, PureType, PureVis};
288    use quote::ToTokens;
289
290    #[test]
291    fn test_pure_fn_simple() {
292        let f = PureFn {
293            attrs: vec![],
294            vis: PureVis::Public,
295            is_async: false,
296            is_async_inferred: false,
297            is_const: false,
298            is_unsafe: false,
299            abi: None,
300            name: "foo".to_string(),
301            generics: PureGenerics::default(),
302            params: vec![],
303            ret: None,
304            body: PureBlock::default(),
305        };
306        let syn_fn = f.to_syn().unwrap();
307        let output = syn_fn.to_token_stream().to_string();
308        assert!(output.contains("pub"), "Output: {}", output);
309        assert!(output.contains("foo"), "Output: {}", output);
310    }
311
312    #[test]
313    fn test_pure_fn_with_params() {
314        let f = PureFn {
315            attrs: vec![],
316            vis: PureVis::Private,
317            is_async: true,
318            is_async_inferred: false,
319            is_const: false,
320            is_unsafe: false,
321            abi: None,
322            name: "bar".to_string(),
323            generics: PureGenerics::default(),
324            params: vec![PureParam::Typed {
325                name: "x".to_string(),
326                ty: PureType::Path("i32".to_string()),
327            }],
328            ret: Some(PureType::Path("i32".to_string())),
329            body: PureBlock::default(),
330        };
331        let syn_fn = f.to_syn().unwrap();
332        let output = syn_fn.to_token_stream().to_string();
333        assert!(output.contains("async"), "Output: {}", output);
334        assert!(output.contains("i32"), "Output: {}", output);
335    }
336
337    #[test]
338    fn test_pure_block_with_stmts() {
339        let block = PureBlock {
340            stmts: vec![PureStmt::Local {
341                pattern: crate::pure::ast::PurePattern::Ident {
342                    name: "x".to_string(),
343                    is_mut: false,
344                },
345                ty: None,
346                init: Some(PureExpr::Lit("42".to_string())),
347            }],
348        };
349        let syn_block = block.to_syn().unwrap();
350        let output = syn_block.to_token_stream().to_string();
351        assert!(output.contains("let"), "Output: {}", output);
352        assert!(output.contains("42"), "Output: {}", output);
353    }
354
355    #[test]
356    fn test_pure_fn_with_extern_c_abi() {
357        let f = PureFn {
358            attrs: vec![],
359            vis: PureVis::Public,
360            is_async: false,
361            is_async_inferred: false,
362            is_const: false,
363            is_unsafe: false,
364            abi: Some("C".to_string()),
365            name: "c_func".to_string(),
366            generics: PureGenerics::default(),
367            params: vec![PureParam::Typed {
368                name: "x".to_string(),
369                ty: PureType::Path("i32".to_string()),
370            }],
371            ret: Some(PureType::Path("i32".to_string())),
372            body: PureBlock::default(),
373        };
374        let syn_fn = f.to_syn().unwrap();
375        let output = syn_fn.to_token_stream().to_string();
376        assert!(
377            output.contains(r#"extern "C""#),
378            r#"Output should contain 'extern "C"': {}"#,
379            output
380        );
381        assert!(output.contains("c_func"), "Output: {}", output);
382    }
383
384    #[test]
385    fn test_pure_fn_with_extern_system_abi() {
386        let f = PureFn {
387            attrs: vec![],
388            vis: PureVis::Private,
389            is_async: false,
390            is_async_inferred: false,
391            is_const: false,
392            is_unsafe: false,
393            abi: Some("system".to_string()),
394            name: "system_func".to_string(),
395            generics: PureGenerics::default(),
396            params: vec![],
397            ret: None,
398            body: PureBlock::default(),
399        };
400        let syn_fn = f.to_syn().unwrap();
401        let output = syn_fn.to_token_stream().to_string();
402        assert!(
403            output.contains(r#"extern "system""#),
404            r#"Output should contain 'extern "system"': {}"#,
405            output
406        );
407    }
408
409    #[test]
410    fn test_pure_fn_with_extern_rust_abi() {
411        let f = PureFn {
412            attrs: vec![],
413            vis: PureVis::Public,
414            is_async: false,
415            is_async_inferred: false,
416            is_const: false,
417            is_unsafe: false,
418            abi: Some("Rust".to_string()),
419            name: "rust_abi_func".to_string(),
420            generics: PureGenerics::default(),
421            params: vec![],
422            ret: None,
423            body: PureBlock::default(),
424        };
425        let syn_fn = f.to_syn().unwrap();
426        let output = syn_fn.to_token_stream().to_string();
427        assert!(
428            output.contains(r#"extern "Rust""#),
429            r#"Output should contain 'extern "Rust"': {}"#,
430            output
431        );
432    }
433
434    #[test]
435    fn test_pure_fn_no_abi() {
436        let f = PureFn {
437            attrs: vec![],
438            vis: PureVis::Public,
439            is_async: false,
440            is_async_inferred: false,
441            is_const: false,
442            is_unsafe: false,
443            abi: None,
444            name: "normal_func".to_string(),
445            generics: PureGenerics::default(),
446            params: vec![],
447            ret: None,
448            body: PureBlock::default(),
449        };
450        let syn_fn = f.to_syn().unwrap();
451        let output = syn_fn.to_token_stream().to_string();
452        assert!(
453            !output.contains("extern"),
454            "Output should NOT contain 'extern': {}",
455            output
456        );
457    }
458
459    #[test]
460    fn test_pure_fn_unsafe_extern_c() {
461        let f = PureFn {
462            attrs: vec![],
463            vis: PureVis::Public,
464            is_async: false,
465            is_async_inferred: false,
466            is_const: false,
467            is_unsafe: true,
468            abi: Some("C".to_string()),
469            name: "unsafe_c_func".to_string(),
470            generics: PureGenerics::default(),
471            params: vec![],
472            ret: None,
473            body: PureBlock::default(),
474        };
475        let syn_fn = f.to_syn().unwrap();
476        let output = syn_fn.to_token_stream().to_string();
477        assert!(
478            output.contains("unsafe"),
479            "Output should contain 'unsafe': {}",
480            output
481        );
482        assert!(
483            output.contains(r#"extern "C""#),
484            r#"Output should contain 'extern "C"': {}"#,
485            output
486        );
487    }
488}