rs_schema/
lib.rs

1use std::fmt::Display;
2
3use quote::quote;
4use syn::{punctuated::Punctuated, token::Comma};
5// #[allow(unused_imports)]
6// use crate as rs_schema;
7
8#[derive(Debug, Clone)]
9pub struct GenericParamAnnotations {
10    pub cffi_type: Option<String>,
11}
12
13impl Into<proc_macro2::TokenStream> for GenericParamAnnotations {
14    /// Convert generic parameter annotations into tokens suitable for code generation.
15    fn into(self) -> proc_macro2::TokenStream {
16        let cffi_type = self
17            .cffi_type
18            .as_ref()
19            .map(|s| proc_macro2::Literal::string(s))
20            .map(|lit| quote! { Some(::std::string::String::from(#lit)) })
21            .unwrap_or(quote! { None });
22        quote! {
23            rs_schema::GenericParamAnnotations {
24                cffi_type: #cffi_type,
25            }
26        }
27    }
28}
29
30impl GenericParamAnnotations {
31    /// Create empty generic parameter annotations with no CFFI type override.
32    pub fn new() -> Self {
33        GenericParamAnnotations { cffi_type: None }
34    }
35}
36
37#[derive(Debug, Clone)]
38pub struct GenericParamSchema {
39    pub name: String,
40    pub annotations: Option<GenericParamAnnotations>,
41}
42
43impl Into<proc_macro2::TokenStream> for GenericParamSchema {
44    /// Convert a generic parameter schema into tokens for embedding in generated code.
45    fn into(self) -> proc_macro2::TokenStream {
46        let name_lit = proc_macro2::Literal::string(&self.name);
47        let annotations_tokens = if let Some(annotations) = self.annotations {
48            let annotations_ts: proc_macro2::TokenStream = annotations.into();
49            quote! { Some(#annotations_ts) }
50        } else {
51            quote! { None }
52        };
53
54        quote! {
55            rs_schema::GenericParamSchema {
56                name: ::std::string::String::from(#name_lit),
57                annotations: #annotations_tokens,
58            }
59        }
60    }
61}
62
63#[derive(Debug, Clone)]
64pub struct TraitSchema {
65    pub name: String,
66    pub functions: Vec<FunctionSchema>,
67    pub generics: Vec<GenericParamSchema>,
68    pub supertraits: Vec<TypeSchema>,
69}
70
71impl Into<proc_macro2::TokenStream> for TraitSchema {
72    /// Convert a trait schema into tokens that instantiate the schema at compile time.
73    fn into(self) -> proc_macro2::TokenStream {
74        let name_lit = proc_macro2::Literal::string(&self.name);
75        let field_tokens: Punctuated<proc_macro2::TokenStream, Comma> = self
76            .functions
77            .into_iter()
78            .map(|f| Into::<proc_macro2::TokenStream>::into(f))
79            .collect::<Punctuated<_, Comma>>();
80
81        let generics_tokens: Punctuated<proc_macro2::TokenStream, Comma> = self
82            .generics
83            .into_iter()
84            .map(|g| Into::<proc_macro2::TokenStream>::into(g))
85            .collect::<Punctuated<_, Comma>>();
86
87        let supertraits_tokens: Punctuated<proc_macro2::TokenStream, Comma> = self
88            .supertraits
89            .into_iter()
90            .map(|s| Into::<proc_macro2::TokenStream>::into(s))
91            .collect::<Punctuated<_, Comma>>();
92
93        quote! {
94            {
95                let functions = ::std::vec![
96                    #field_tokens
97                ];
98                let generics = ::std::vec![
99                    #generics_tokens
100                ];
101                let supertraits = ::std::vec![
102                    #supertraits_tokens
103                ];
104                    rs_schema::TraitSchema {
105                        name: ::std::string::String::from(#name_lit),
106                        functions: functions,
107                        generics: generics,
108                        supertraits: supertraits,
109                    }
110            }
111        }
112    }
113}
114
115#[derive(Debug, Clone)]
116pub struct FunctionSchema {
117    pub name: String,
118    pub args: Vec<FunctionArgSchema>,
119    pub return_type: TypeSchema,
120    pub body: Option<String>,
121    pub extern_layout: Option<String>,
122    pub annotations: Option<FunctionAnnotations>,
123}
124
125impl Into<proc_macro2::TokenStream> for FunctionSchema {
126    /// Convert a function schema into tokens for inclusion in a trait schema.
127    fn into(self) -> proc_macro2::TokenStream {
128        let name_lit = proc_macro2::Literal::string(&self.name);
129        let args_tokens: Punctuated<proc_macro2::TokenStream, Comma> = self
130            .args
131            .into_iter()
132            .map(|arg| Into::<proc_macro2::TokenStream>::into(arg))
133            .collect::<Punctuated<_, Comma>>();
134        let return_type_tokens: proc_macro2::TokenStream = self.return_type.into();
135
136        let body = if let Some(body) = self.body {
137            let body_lit = proc_macro2::Literal::string(&body);
138            quote! {
139                Some(::std::string::String::from(#body_lit))
140            }
141        } else {
142            quote! {
143                None
144            }
145        };
146
147        let extern_layout = if let Some(extern_layout) = self.extern_layout {
148            let extern_layout = proc_macro2::Literal::string(&extern_layout);
149            quote! {
150                Some(::std::string::String::from(#extern_layout))
151            }
152        } else {
153            quote! {
154                None
155            }
156        };
157
158        let annotations_tokens = if let Some(annotations) = self.annotations {
159            let annotations_ts: proc_macro2::TokenStream = annotations.into();
160            quote! { Some(#annotations_ts) }
161        } else {
162            quote! { None }
163        };
164
165        quote! {
166            rs_schema::FunctionSchema {
167                name: ::std::string::String::from(#name_lit),
168                args: ::std::vec![
169                    #args_tokens
170                ],
171                return_type: #return_type_tokens,
172                body: #body,
173                extern_layout: #extern_layout,
174                annotations: #annotations_tokens,
175            }
176        }
177    }
178}
179
180impl Display for FunctionSchema {
181    /// Render the function signature (and optional body) as a human-readable string.
182    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
183        if let Some(extern_layout) = &self.extern_layout {
184            write!(f, "extern \"{}\" ", extern_layout)?;
185        }
186        write!(f, "fn {}(", self.name)?;
187        for (i, arg) in self.args.iter().enumerate() {
188            if i > 0 {
189                write!(f, ", ")?;
190            }
191            if let Some(ty) = &arg.ty {
192                write!(f, "{}: {}", arg.name, ty)?;
193            } else {
194                // Only self param lack an explicit type
195                // TODO: Assuming self param is reference
196                write!(f, "&{}", arg.name)?;
197            }
198        }
199        write!(f, ")")?;
200        write!(f, " -> {}", self.return_type)?;
201        if let Some(body) = &self.body {
202            write!(f, " {{\n{}\n}}", body)?;
203        } else {
204            write!(f, ";")?;
205        }
206        Ok(())
207    }
208}
209
210#[derive(Debug, Clone)]
211pub struct FunctionAnnotations {
212    pub cffi_impl_no_op: bool,
213}
214
215impl Into<proc_macro2::TokenStream> for FunctionAnnotations {
216    /// Convert function-level annotations into tokens for generated code.
217    fn into(self) -> proc_macro2::TokenStream {
218        let cffi_impl_no_op = self.cffi_impl_no_op;
219        quote! {
220            rs_schema::FunctionAnnotations {
221                cffi_impl_no_op: #cffi_impl_no_op,
222            }
223        }
224    }
225}
226
227impl FunctionAnnotations {
228    /// Construct function annotations with defaults (no CFFI flags set).
229    pub fn new() -> Self {
230        FunctionAnnotations {
231            cffi_impl_no_op: false,
232        }
233    }
234}
235
236/// Schema for a parsed type, including any generic arguments.
237#[derive(Debug, Clone)]
238pub struct TypeSchema {
239    pub ty: String,
240    pub generic_ty_args: Vec<TypeSchema>,
241}
242
243impl TypeSchema {
244    pub fn new_simple(ty: String) -> Self {
245        TypeSchema {
246            ty,
247            generic_ty_args: vec![],
248        }
249    }
250}
251
252impl Into<proc_macro2::TokenStream> for TypeSchema {
253    /// Convert a type schema into tokens suitable for embedding in generated code.
254    fn into(self) -> proc_macro2::TokenStream {
255        let ty_lit = proc_macro2::Literal::string(&self.ty);
256        let generic_ty_args_tokens: Punctuated<proc_macro2::TokenStream, Comma> = self
257            .generic_ty_args
258            .into_iter()
259            .map(|arg| Into::<proc_macro2::TokenStream>::into(arg))
260            .collect::<Punctuated<_, Comma>>();
261
262        quote! {
263            rs_schema::TypeSchema {
264                ty: ::std::string::String::from(#ty_lit),
265                generic_ty_args: ::std::vec![
266                    #generic_ty_args_tokens
267                ],
268            }
269        }
270    }
271}
272
273impl Display for TypeSchema {
274    /// Render the type as a human-readable string.
275    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
276        write!(f, "{}", self.ty)?;
277        if !self.generic_ty_args.is_empty() {
278            write!(f, "<")?;
279            for (i, arg) in self.generic_ty_args.iter().enumerate() {
280                if i > 0 {
281                    write!(f, ", ")?;
282                }
283                write!(f, "{}", arg)?;
284            }
285            write!(f, ">")?;
286        }
287        Ok(())
288    }
289}
290
291#[derive(Debug, Clone)]
292pub struct FunctionArgSchema {
293    pub name: String,
294    pub ty: Option<TypeSchema>,
295    pub annotations: Option<FnArgAnnotations>,
296}
297
298impl Into<proc_macro2::TokenStream> for FunctionArgSchema {
299    /// Convert a function argument schema into tokens used by the procedural macro.
300    fn into(self) -> proc_macro2::TokenStream {
301        let name_lit = proc_macro2::Literal::string(&self.name);
302        let ty_tokens = self.ty.map(|ty| Into::<proc_macro2::TokenStream>::into(ty));
303        let annotations_tokens = if let Some(annotations) = self.annotations {
304            let annotations_ts: proc_macro2::TokenStream = annotations.into();
305            quote! { Some(#annotations_ts) }
306        } else {
307            quote! { None }
308        };
309
310        let ty_tokens: proc_macro2::TokenStream = if let Some(ty_tokens) = ty_tokens {
311            quote! { ty: ::std::option::Option::Some(#ty_tokens), }
312        } else {
313            quote! { ty: ::std::option::Option::None, }
314        };
315
316        quote! {
317            rs_schema::FunctionArgSchema {
318            name: ::std::string::String::from(#name_lit),
319            #ty_tokens
320            annotations: #annotations_tokens,
321            }
322        }
323    }
324}
325
326#[derive(Debug, Clone)]
327pub struct FnArgAnnotations {
328    // Examples of supported args:
329    //   #[arg(collection_as_item, assert_len = 1)]
330    pub collection_as_item: bool,
331    pub assert_len: Option<usize>, // e.g. 10
332    pub cffi_type: Option<String>, // ptr<i32>, opt_ptr<f32>, etc.
333}
334
335impl Into<proc_macro2::TokenStream> for FnArgAnnotations {
336    /// Convert argument annotations into tokens that can be embedded in generated code.
337    fn into(self) -> proc_macro2::TokenStream {
338        let collection_as_item = self.collection_as_item;
339        let cffi_type = self
340            .cffi_type
341            .as_ref()
342            .map(|s| proc_macro2::Literal::string(s))
343            .map(|lit| quote! { Some(::std::string::String::from(#lit)) })
344            .unwrap_or(quote! { None });
345        let assert_len = match self.assert_len {
346            Some(len) => quote! { Some(#len) },
347            None => quote! { None },
348        };
349        quote! {
350            rs_schema::FnArgAnnotations {
351                collection_as_item: #collection_as_item,
352                assert_len: #assert_len,
353                cffi_type: #cffi_type,
354            }
355        }
356    }
357}
358
359impl FnArgAnnotations {
360    /// Create empty argument annotations with defaults for collection and assertion flags.
361    pub fn new() -> Self {
362        FnArgAnnotations {
363            collection_as_item: false,
364            assert_len: None,
365            cffi_type: None,
366        }
367    }
368}
369
370#[cfg(test)]
371mod tests {
372    use super::*;
373
374    #[test]
375    fn test_fn_arg_annotations_new() {
376        let annotations = FnArgAnnotations::new();
377        assert!(!annotations.collection_as_item);
378        assert!(annotations.assert_len.is_none());
379    }
380
381    #[test]
382    fn test_fn_arg_annotations_creation_with_values() {
383        let annotations = FnArgAnnotations {
384            collection_as_item: true,
385            assert_len: Some(42),
386            cffi_type: Some("ptr<i32>".to_string()),
387        };
388        assert!(annotations.collection_as_item);
389        assert_eq!(annotations.assert_len, Some(42));
390        assert_eq!(annotations.cffi_type, Some("ptr<i32>".to_string()));
391    }
392
393    #[test]
394    fn test_function_arg_schema_creation() {
395        let arg = FunctionArgSchema {
396            name: "test_arg".to_string(),
397            ty: Some(TypeSchema {
398                ty: "String".to_string(),
399                generic_ty_args: vec![],
400            }),
401            annotations: Some(FnArgAnnotations {
402                collection_as_item: false,
403                assert_len: Some(10),
404                cffi_type: None,
405            }),
406        };
407
408        assert_eq!(arg.name, "test_arg");
409        assert_eq!(arg.ty.as_ref().unwrap().ty, "String");
410        assert!(arg.annotations.is_some());
411        if let Some(ann) = arg.annotations {
412            assert_eq!(ann.assert_len, Some(10));
413        }
414    }
415
416    #[test]
417    fn test_function_arg_schema_without_annotations() {
418        let arg = FunctionArgSchema {
419            name: "simple_arg".to_string(),
420            ty: Some(TypeSchema {
421                ty: "i32".to_string(),
422                generic_ty_args: vec![],
423            }),
424            annotations: None,
425        };
426
427        assert_eq!(arg.name, "simple_arg");
428        assert_eq!(arg.ty.as_ref().unwrap().ty, "i32");
429        assert!(arg.annotations.is_none());
430    }
431
432    #[test]
433    fn test_function_schema_creation() {
434        let args = vec![
435            FunctionArgSchema {
436                name: "arg1".to_string(),
437                ty: Some(TypeSchema {
438                    ty: "String".to_string(),
439                    generic_ty_args: vec![],
440                }),
441                annotations: None,
442            },
443            FunctionArgSchema {
444                name: "arg2".to_string(),
445                ty: Some(TypeSchema {
446                    ty: "i32".to_string(),
447                    generic_ty_args: vec![],
448                }),
449                annotations: None,
450            },
451        ];
452
453        let func = FunctionSchema {
454            name: "test_fn".to_string(),
455            args,
456            return_type: TypeSchema {
457                ty: "bool".to_string(),
458                generic_ty_args: vec![],
459            },
460            body: None,
461            extern_layout: None,
462            annotations: None,
463        };
464
465        assert_eq!(func.name, "test_fn");
466        assert_eq!(func.args.len(), 2);
467        assert_eq!(func.return_type.ty, "bool");
468        assert!(func.body.is_none());
469    }
470
471    #[test]
472    fn test_function_schema_display() {
473        let args = vec![
474            FunctionArgSchema {
475                name: "x".to_string(),
476                ty: Some(TypeSchema {
477                    ty: "i32".to_string(),
478                    generic_ty_args: vec![],
479                }),
480                annotations: None,
481            },
482            FunctionArgSchema {
483                name: "y".to_string(),
484                ty: Some(TypeSchema {
485                    ty: "String".to_string(),
486                    generic_ty_args: vec![],
487                }),
488                annotations: None,
489            },
490        ];
491
492        let func = FunctionSchema {
493            name: "my_function".to_string(),
494            args,
495            return_type: TypeSchema {
496                ty: "bool".to_string(),
497                generic_ty_args: vec![],
498            },
499            body: None,
500            extern_layout: None,
501            annotations: None,
502        };
503
504        let display_str = format!("{}", func);
505        assert!(display_str.contains("my_function"));
506        assert!(display_str.contains("x: i32"));
507        assert!(display_str.contains("y: String"));
508        assert!(display_str.contains("-> bool"));
509        assert!(display_str.contains(";"));
510    }
511
512    #[test]
513    fn test_function_schema_display_with_body() {
514        let args = vec![];
515        let func = FunctionSchema {
516            name: "with_body".to_string(),
517            args,
518            return_type: TypeSchema {
519                ty: "String".to_string(),
520                generic_ty_args: vec![],
521            },
522            body: Some("return \"hello\".to_string();".to_string()),
523            extern_layout: None,
524            annotations: None,
525        };
526
527        let display_str = format!("{}", func);
528        assert!(display_str.contains("with_body"));
529        assert!(display_str.contains("hello"));
530        assert!(display_str.contains("{"));
531        assert!(display_str.contains("}"));
532    }
533
534    #[test]
535    fn test_trait_schema_creation() {
536        let functions = vec![
537            FunctionSchema {
538                name: "method1".to_string(),
539                args: vec![],
540                return_type: TypeSchema {
541                    ty: "()".to_string(),
542                    generic_ty_args: vec![],
543                },
544                body: None,
545                extern_layout: None,
546                annotations: None,
547            },
548            FunctionSchema {
549                name: "method2".to_string(),
550                args: vec![FunctionArgSchema {
551                    name: "arg".to_string(),
552                    ty: Some(TypeSchema {
553                        ty: "String".to_string(),
554                        generic_ty_args: vec![],
555                    }),
556                    annotations: None,
557                }],
558                return_type: TypeSchema {
559                    ty: "String".to_string(),
560                    generic_ty_args: vec![],
561                },
562                body: None,
563                extern_layout: None,
564                annotations: None,
565            },
566        ];
567
568        let schema = TraitSchema {
569            name: "MyTrait".to_string(),
570            functions,
571            generics: vec![],
572            supertraits: vec![],
573        };
574
575        assert_eq!(schema.name, "MyTrait");
576        assert_eq!(schema.functions.len(), 2);
577        assert_eq!(schema.functions[0].name, "method1");
578        assert_eq!(schema.functions[1].name, "method2");
579        assert_eq!(schema.generics.len(), 0);
580        assert_eq!(schema.supertraits.len(), 0);
581    }
582
583    #[test]
584    fn test_function_schema_no_args() {
585        let func = FunctionSchema {
586            name: "no_args".to_string(),
587            args: vec![],
588            return_type: TypeSchema {
589                ty: "()".to_string(),
590                generic_ty_args: vec![],
591            },
592            body: None,
593            extern_layout: None,
594            annotations: None,
595        };
596
597        assert_eq!(func.args.len(), 0);
598        let display_str = format!("{}", func);
599        assert!(display_str.contains("no_args()"));
600    }
601
602    #[test]
603    fn test_function_arg_schema_no_type() {
604        let arg = FunctionArgSchema {
605            name: "self".to_string(),
606            ty: None,
607            annotations: None,
608        };
609
610        assert_eq!(arg.name, "self");
611        assert!(arg.ty.is_none());
612    }
613
614    #[test]
615    fn test_generic_param_annotations_new() {
616        let annotations = GenericParamAnnotations::new();
617        assert!(annotations.cffi_type.is_none());
618    }
619
620    #[test]
621    fn test_generic_param_annotations_with_cffi_type() {
622        let annotations = GenericParamAnnotations {
623            cffi_type: Some("ptr<void>".to_string()),
624        };
625        assert_eq!(annotations.cffi_type, Some("ptr<void>".to_string()));
626    }
627
628    #[test]
629    fn test_generic_param_schema_creation() {
630        let param = GenericParamSchema {
631            name: "T".to_string(),
632            annotations: Some(GenericParamAnnotations {
633                cffi_type: Some("ptr<i32>".to_string()),
634            }),
635        };
636
637        assert_eq!(param.name, "T");
638        assert!(param.annotations.is_some());
639        if let Some(ann) = param.annotations {
640            assert_eq!(ann.cffi_type, Some("ptr<i32>".to_string()));
641        }
642    }
643
644    #[test]
645    fn test_generic_param_schema_without_annotations() {
646        let param = GenericParamSchema {
647            name: "U".to_string(),
648            annotations: None,
649        };
650
651        assert_eq!(param.name, "U");
652        assert!(param.annotations.is_none());
653    }
654
655    #[test]
656    fn test_trait_schema_with_generics() {
657        let generics = vec![GenericParamSchema {
658            name: "T".to_string(),
659            annotations: Some(GenericParamAnnotations {
660                cffi_type: Some("ptr<void>".to_string()),
661            }),
662        }];
663
664        let schema = TraitSchema {
665            name: "SpecializedTrait".to_string(),
666            functions: vec![],
667            generics,
668            supertraits: vec![],
669        };
670
671        assert_eq!(schema.name, "SpecializedTrait");
672        assert_eq!(schema.generics.len(), 1);
673        assert_eq!(schema.generics[0].name, "T");
674        assert_eq!(schema.supertraits.len(), 0);
675    }
676
677    #[test]
678    fn test_trait_schema_with_supertraits() {
679        let schema = TraitSchema {
680            name: "MyTraitWithBase".to_string(),
681            functions: vec![],
682            generics: vec![],
683            supertraits: vec![
684                TypeSchema {
685                    ty: "BaseTrait".to_string(),
686                    generic_ty_args: vec![],
687                },
688                TypeSchema {
689                    ty: "OtherTrait".to_string(),
690                    generic_ty_args: vec![],
691                },
692            ],
693        };
694
695        assert_eq!(schema.name, "MyTraitWithBase");
696        assert_eq!(schema.supertraits.len(), 2);
697        assert_eq!(schema.supertraits[0].ty, "BaseTrait");
698        assert_eq!(schema.supertraits[1].ty, "OtherTrait");
699    }
700}