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 {
119                name,
120                ty,
121                is_mut,
122                pat,
123            } => {
124                // Prefer the original pattern when it was preserved
125                // (`pat: Some(...)`); fall back to synthesising a
126                // `Pat::Ident { name }` for bare-identifier params.
127                // This re-emits source like
128                // `fn execute_group(SpecGroup { x, .. }: SpecGroup)`
129                // verbatim, instead of collapsing to the lossy
130                // `SpecGroup: SpecGroup` form that cargo flags as
131                // "patterns aren't allowed in functions without bodies".
132                let pat_syn = match pat {
133                    Some(p) => p.to_syn()?,
134                    None => syn::Pat::Ident(syn::PatIdent {
135                        attrs: vec![],
136                        by_ref: None,
137                        mutability: if *is_mut {
138                            Some(token::Mut::default())
139                        } else {
140                            None
141                        },
142                        ident: ident(name),
143                        subpat: None,
144                    }),
145                };
146                syn::FnArg::Typed(syn::PatType {
147                    attrs: vec![],
148                    pat: Box::new(pat_syn),
149                    colon_token: token::Colon::default(),
150                    ty: Box::new(ty.to_syn()?),
151                })
152            }
153        })
154    }
155}
156
157impl ToSyn for PureGenerics {
158    type Output = syn::Generics;
159
160    fn to_syn(&self) -> Result<syn::Generics, ToSynError> {
161        let params: Punctuated<syn::GenericParam, token::Comma> = self
162            .params
163            .iter()
164            .map(|p| p.to_syn())
165            .collect::<Result<_, _>>()?;
166
167        let where_clause = if self.where_clause.is_empty() {
168            None
169        } else {
170            Some(syn::WhereClause {
171                where_token: token::Where::default(),
172                predicates: self
173                    .where_clause
174                    .iter()
175                    .filter_map(|s| syn::parse_str::<syn::WherePredicate>(s).ok())
176                    .collect(),
177            })
178        };
179
180        Ok(syn::Generics {
181            lt_token: if params.is_empty() {
182                None
183            } else {
184                Some(token::Lt::default())
185            },
186            params,
187            gt_token: if self.params.is_empty() {
188                None
189            } else {
190                Some(token::Gt::default())
191            },
192            where_clause,
193        })
194    }
195}
196
197impl ToSyn for PureGenericParam {
198    type Output = syn::GenericParam;
199
200    fn to_syn(&self) -> Result<syn::GenericParam, ToSynError> {
201        Ok(match self {
202            PureGenericParam::Type { name, bounds } => syn::GenericParam::Type(syn::TypeParam {
203                attrs: vec![],
204                ident: ident(name),
205                colon_token: if bounds.is_empty() {
206                    None
207                } else {
208                    Some(token::Colon::default())
209                },
210                bounds: bounds
211                    .iter()
212                    .filter_map(|b| syn::parse_str::<syn::TypeParamBound>(b).ok())
213                    .collect(),
214                eq_token: None,
215                default: None,
216            }),
217            PureGenericParam::Lifetime { name, bounds } => {
218                syn::GenericParam::Lifetime(syn::LifetimeParam {
219                    attrs: vec![],
220                    lifetime: syn::Lifetime::new(name, Span::call_site()),
221                    colon_token: if bounds.is_empty() {
222                        None
223                    } else {
224                        Some(token::Colon::default())
225                    },
226                    bounds: bounds
227                        .iter()
228                        .map(|b| syn::Lifetime::new(b, Span::call_site()))
229                        .collect(),
230                })
231            }
232            PureGenericParam::Const { name, ty } => syn::GenericParam::Const(syn::ConstParam {
233                attrs: vec![],
234                const_token: token::Const::default(),
235                ident: ident(name),
236                colon_token: token::Colon::default(),
237                ty: syn::parse_str(ty).map_err(|e| ToSynError::ParseType {
238                    input: ty.clone(),
239                    message: e.to_string(),
240                })?,
241                eq_token: None,
242                default: None,
243            }),
244        })
245    }
246}
247
248impl ToSyn for PureBlock {
249    type Output = syn::Block;
250
251    fn to_syn(&self) -> Result<syn::Block, ToSynError> {
252        Ok(syn::Block {
253            brace_token: token::Brace::default(),
254            stmts: self
255                .stmts
256                .iter()
257                .map(|s| s.to_syn())
258                .collect::<Result<Vec<_>, _>>()?,
259        })
260    }
261}
262
263impl ToSyn for PureStmt {
264    type Output = syn::Stmt;
265
266    fn to_syn(&self) -> Result<syn::Stmt, ToSynError> {
267        match self {
268            PureStmt::Local {
269                pattern,
270                ty,
271                init,
272                else_branch,
273            } => {
274                let pat = if let Some(ty) = ty {
275                    syn::Pat::Type(syn::PatType {
276                        attrs: vec![],
277                        pat: Box::new(pattern.to_syn()?),
278                        colon_token: token::Colon::default(),
279                        ty: Box::new(ty.to_syn()?),
280                    })
281                } else {
282                    pattern.to_syn()?
283                };
284                let local_init = init
285                    .as_ref()
286                    .map(|e| {
287                        let diverge = else_branch
288                            .as_ref()
289                            .map(|eb| {
290                                Ok::<_, ToSynError>((
291                                    syn::token::Else::default(),
292                                    Box::new(eb.to_syn()?),
293                                ))
294                            })
295                            .transpose()?;
296                        Ok(syn::LocalInit {
297                            eq_token: token::Eq::default(),
298                            expr: Box::new(e.to_syn()?),
299                            diverge,
300                        })
301                    })
302                    .transpose()?;
303                Ok(syn::Stmt::Local(syn::Local {
304                    attrs: vec![],
305                    let_token: token::Let::default(),
306                    pat,
307                    init: local_init,
308                    semi_token: token::Semi::default(),
309                }))
310            }
311            PureStmt::Semi(expr) => Ok(syn::Stmt::Expr(
312                expr.to_syn()?,
313                Some(token::Semi::default()),
314            )),
315            PureStmt::Expr(expr) => Ok(syn::Stmt::Expr(expr.to_syn()?, None)),
316            PureStmt::Item(item) => Ok(syn::Stmt::Item(item.to_syn()?)),
317            PureStmt::Verbatim(raw) => {
318                // B-3-cont: register raw bytes and emit a unique
319                // expression-stmt stub `__ryo_verbatim_stmt_<idx>;`.
320                // After prettyplease lays out the file, the to_source
321                // splice step replaces the **entire line** containing
322                // the identifier with the raw bytes (line-splice like
323                // PureItem::Verbatim, because a Stmt occupies its own
324                // line in prettyplease output).
325                let idx = super::register_verbatim_stmt(raw.clone());
326                let stub_ident = format!("__ryo_verbatim_stmt_{}", idx);
327                let stub_expr = syn::parse_str::<syn::Expr>(&stub_ident).map_err(|e| {
328                    ToSynError::ParseExpr {
329                        input: stub_ident,
330                        message: e.to_string(),
331                    }
332                })?;
333                Ok(syn::Stmt::Expr(stub_expr, Some(token::Semi::default())))
334            }
335        }
336    }
337}
338
339#[cfg(test)]
340mod tests {
341    use super::*;
342    use crate::pure::ast::{PureExpr, PureType, PureVis};
343    use quote::ToTokens;
344
345    #[test]
346    fn test_pure_fn_simple() {
347        let f = PureFn {
348            attrs: vec![],
349            vis: PureVis::Public,
350            is_async: false,
351            is_async_inferred: false,
352            is_const: false,
353            is_unsafe: false,
354            abi: None,
355            name: "foo".to_string(),
356            generics: PureGenerics::default(),
357            params: vec![],
358            ret: None,
359            body: PureBlock::default(),
360        };
361        let syn_fn = f.to_syn().unwrap();
362        let output = syn_fn.to_token_stream().to_string();
363        assert!(output.contains("pub"), "Output: {}", output);
364        assert!(output.contains("foo"), "Output: {}", output);
365    }
366
367    #[test]
368    fn test_pure_fn_with_params() {
369        let f = PureFn {
370            attrs: vec![],
371            vis: PureVis::Private,
372            is_async: true,
373            is_async_inferred: false,
374            is_const: false,
375            is_unsafe: false,
376            abi: None,
377            name: "bar".to_string(),
378            generics: PureGenerics::default(),
379            params: vec![PureParam::Typed {
380                name: "x".to_string(),
381                ty: PureType::Path("i32".to_string()),
382                is_mut: false,
383                pat: None,
384            }],
385            ret: Some(PureType::Path("i32".to_string())),
386            body: PureBlock::default(),
387        };
388        let syn_fn = f.to_syn().unwrap();
389        let output = syn_fn.to_token_stream().to_string();
390        assert!(output.contains("async"), "Output: {}", output);
391        assert!(output.contains("i32"), "Output: {}", output);
392    }
393
394    #[test]
395    fn test_pure_block_with_stmts() {
396        let block = PureBlock {
397            stmts: vec![PureStmt::Local {
398                pattern: crate::pure::ast::PurePattern::Ident {
399                    name: "x".to_string(),
400                    is_mut: false,
401                    by_ref: false,
402                },
403                ty: None,
404                init: Some(PureExpr::Lit("42".to_string())),
405                else_branch: None,
406            }],
407        };
408        let syn_block = block.to_syn().unwrap();
409        let output = syn_block.to_token_stream().to_string();
410        assert!(output.contains("let"), "Output: {}", output);
411        assert!(output.contains("42"), "Output: {}", output);
412    }
413
414    #[test]
415    fn test_pure_fn_with_extern_c_abi() {
416        let f = PureFn {
417            attrs: vec![],
418            vis: PureVis::Public,
419            is_async: false,
420            is_async_inferred: false,
421            is_const: false,
422            is_unsafe: false,
423            abi: Some("C".to_string()),
424            name: "c_func".to_string(),
425            generics: PureGenerics::default(),
426            params: vec![PureParam::Typed {
427                name: "x".to_string(),
428                ty: PureType::Path("i32".to_string()),
429                is_mut: false,
430                pat: None,
431            }],
432            ret: Some(PureType::Path("i32".to_string())),
433            body: PureBlock::default(),
434        };
435        let syn_fn = f.to_syn().unwrap();
436        let output = syn_fn.to_token_stream().to_string();
437        assert!(
438            output.contains(r#"extern "C""#),
439            r#"Output should contain 'extern "C"': {}"#,
440            output
441        );
442        assert!(output.contains("c_func"), "Output: {}", output);
443    }
444
445    #[test]
446    fn test_pure_fn_with_extern_system_abi() {
447        let f = PureFn {
448            attrs: vec![],
449            vis: PureVis::Private,
450            is_async: false,
451            is_async_inferred: false,
452            is_const: false,
453            is_unsafe: false,
454            abi: Some("system".to_string()),
455            name: "system_func".to_string(),
456            generics: PureGenerics::default(),
457            params: vec![],
458            ret: None,
459            body: PureBlock::default(),
460        };
461        let syn_fn = f.to_syn().unwrap();
462        let output = syn_fn.to_token_stream().to_string();
463        assert!(
464            output.contains(r#"extern "system""#),
465            r#"Output should contain 'extern "system"': {}"#,
466            output
467        );
468    }
469
470    #[test]
471    fn test_pure_fn_with_extern_rust_abi() {
472        let f = PureFn {
473            attrs: vec![],
474            vis: PureVis::Public,
475            is_async: false,
476            is_async_inferred: false,
477            is_const: false,
478            is_unsafe: false,
479            abi: Some("Rust".to_string()),
480            name: "rust_abi_func".to_string(),
481            generics: PureGenerics::default(),
482            params: vec![],
483            ret: None,
484            body: PureBlock::default(),
485        };
486        let syn_fn = f.to_syn().unwrap();
487        let output = syn_fn.to_token_stream().to_string();
488        assert!(
489            output.contains(r#"extern "Rust""#),
490            r#"Output should contain 'extern "Rust"': {}"#,
491            output
492        );
493    }
494
495    #[test]
496    fn test_pure_fn_no_abi() {
497        let f = PureFn {
498            attrs: vec![],
499            vis: PureVis::Public,
500            is_async: false,
501            is_async_inferred: false,
502            is_const: false,
503            is_unsafe: false,
504            abi: None,
505            name: "normal_func".to_string(),
506            generics: PureGenerics::default(),
507            params: vec![],
508            ret: None,
509            body: PureBlock::default(),
510        };
511        let syn_fn = f.to_syn().unwrap();
512        let output = syn_fn.to_token_stream().to_string();
513        assert!(
514            !output.contains("extern"),
515            "Output should NOT contain 'extern': {}",
516            output
517        );
518    }
519
520    #[test]
521    fn test_pure_fn_unsafe_extern_c() {
522        let f = PureFn {
523            attrs: vec![],
524            vis: PureVis::Public,
525            is_async: false,
526            is_async_inferred: false,
527            is_const: false,
528            is_unsafe: true,
529            abi: Some("C".to_string()),
530            name: "unsafe_c_func".to_string(),
531            generics: PureGenerics::default(),
532            params: vec![],
533            ret: None,
534            body: PureBlock::default(),
535        };
536        let syn_fn = f.to_syn().unwrap();
537        let output = syn_fn.to_token_stream().to_string();
538        assert!(
539            output.contains("unsafe"),
540            "Output should contain 'unsafe': {}",
541            output
542        );
543        assert!(
544            output.contains(r#"extern "C""#),
545            r#"Output should contain 'extern "C"': {}"#,
546            output
547        );
548    }
549}