1use 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 let self_ty = if *is_ref {
70 if *is_mut {
71 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 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 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 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 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}