Coverage Report

Created: 2025-09-05 15:26

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/home/noah/src/ruchy/src/backend/transpiler/types.rs
Line
Count
Source
1
//! Type transpilation and struct/trait definitions
2
3
#![allow(clippy::missing_errors_doc)]
4
#![allow(clippy::wildcard_imports)]
5
#![allow(clippy::collapsible_else_if)]
6
#![allow(clippy::only_used_in_recursion)]
7
8
use super::*;
9
use crate::frontend::ast::{EnumVariant, ImplMethod, StructField, TraitMethod, Type};
10
use anyhow::{bail, Result};
11
use proc_macro2::TokenStream;
12
use quote::{format_ident, quote};
13
14
impl Transpiler {
15
    /// Transpiles type annotations
16
    ///
17
    /// # Examples
18
    ///
19
    /// ```
20
    /// use ruchy::{Transpiler, Parser};
21
    /// 
22
    /// // Basic types
23
    /// let transpiler = Transpiler::new();
24
    /// let mut parser = Parser::new("let x: i32 = 42");
25
    /// let ast = parser.parse().unwrap();
26
    /// 
27
    /// let result = transpiler.transpile(&ast).unwrap();
28
    /// let code = result.to_string();
29
    /// assert!(code.contains("i32"));
30
    /// assert!(code.contains("42"));
31
    /// ```
32
    ///
33
    /// ```
34
    /// use ruchy::{Transpiler, Parser};
35
    /// 
36
    /// // Generic types
37
    /// let transpiler = Transpiler::new();
38
    /// let mut parser = Parser::new("let v: Vec<i32> = vec![1, 2, 3]");
39
    /// let ast = parser.parse().unwrap();
40
    /// 
41
    /// let result = transpiler.transpile(&ast).unwrap();
42
    /// let code = result.to_string();
43
    /// assert!(code.contains("Vec"));
44
    /// assert!(code.contains("i32"));
45
    /// ```
46
    ///
47
    /// ```
48
    /// use ruchy::{Transpiler, Parser};
49
    /// 
50
    /// // Optional types
51
    /// let transpiler = Transpiler::new();
52
    /// let mut parser = Parser::new("let opt: Option<i32> = Some(42)");
53
    /// let ast = parser.parse().unwrap();
54
    /// 
55
    /// let result = transpiler.transpile(&ast).unwrap();
56
    /// let code = result.to_string();
57
    /// assert!(code.contains("Option"));
58
    /// assert!(code.contains("Some"));
59
    /// ```
60
31
    pub fn transpile_type(&self, ty: &Type) -> Result<TokenStream> {
61
        use crate::frontend::ast::TypeKind;
62
63
31
        match &ty.kind {
64
31
            TypeKind::Named(name) => {
65
                // Map common Ruchy types to Rust types
66
31
                let rust_type = match name.as_str() {
67
31
                    "int" => 
quote!0
{ i64 },
68
31
                    "float" => 
quote!0
{ f64 },
69
31
                    "bool" => 
quote!0
{ bool },
70
31
                    "str" => 
quote!0
{ str }, // Plain str type (will be used with & for references)
71
31
                    "string" | "String" => 
quote!0
{ String },
72
31
                    "char" => 
quote!0
{ char },
73
                    // PERFORMANCE OPTIMIZATION: Use Rust type inference instead of Any
74
31
                    "_" | "Any" => 
quote!14
{ _ },
75
                    _ => {
76
17
                        let type_ident = format_ident!("{}", name);
77
17
                        quote! { #type_ident }
78
                    }
79
                };
80
31
                Ok(rust_type)
81
            }
82
0
            TypeKind::Generic { base, params } => {
83
0
                let base_ident = format_ident!("{}", base);
84
0
                let param_tokens: Result<Vec<_>> =
85
0
                    params.iter().map(|p| self.transpile_type(p)).collect();
86
0
                let param_tokens = param_tokens?;
87
0
                Ok(quote! { #base_ident<#(#param_tokens),*> })
88
            }
89
0
            TypeKind::Optional(inner) => {
90
0
                let inner_tokens = self.transpile_type(inner)?;
91
0
                Ok(quote! { Option<#inner_tokens> })
92
            }
93
0
            TypeKind::List(elem_type) => {
94
0
                let elem_tokens = self.transpile_type(elem_type)?;
95
0
                Ok(quote! { Vec<#elem_tokens> })
96
            }
97
0
            TypeKind::Tuple(types) => {
98
0
                let type_tokens: Result<Vec<_>> =
99
0
                    types.iter().map(|t| self.transpile_type(t)).collect();
100
0
                let type_tokens = type_tokens?;
101
0
                Ok(quote! { (#(#type_tokens),*) })
102
            }
103
0
            TypeKind::Function { params, ret } => {
104
0
                let param_tokens: Result<Vec<_>> =
105
0
                    params.iter().map(|p| self.transpile_type(p)).collect();
106
0
                let param_tokens = param_tokens?;
107
0
                let ret_tokens = self.transpile_type(ret)?;
108
109
                // Rust function type syntax
110
0
                Ok(quote! { fn(#(#param_tokens),*) -> #ret_tokens })
111
            }
112
            TypeKind::DataFrame { .. } => {
113
                // DataFrames map to Polars DataFrame type
114
0
                Ok(quote! { polars::prelude::DataFrame })
115
            }
116
            TypeKind::Series { .. } => {
117
                // Series maps to Polars Series type
118
0
                Ok(quote! { polars::prelude::Series })
119
            }
120
0
            TypeKind::Reference { is_mut, inner } => {
121
                // Special case: &str should not become &&str
122
0
                if let TypeKind::Named(name) = &inner.kind {
123
0
                    if name == "str" {
124
                        // str is already a reference type in Rust
125
0
                        return if *is_mut {
126
0
                            Ok(quote! { &mut str })
127
                        } else {
128
0
                            Ok(quote! { &str })
129
                        };
130
0
                    }
131
0
                }
132
                
133
0
                let inner_tokens = self.transpile_type(inner)?;
134
0
                if *is_mut {
135
0
                    Ok(quote! { &mut #inner_tokens })
136
                } else {
137
0
                    Ok(quote! { &#inner_tokens })
138
                }
139
            }
140
        }
141
31
    }
142
143
    /// Transpiles struct definitions
144
5
    pub fn transpile_struct(
145
5
        &self,
146
5
        name: &str,
147
5
        type_params: &[String],
148
5
        fields: &[StructField],
149
5
        is_pub: bool,
150
5
    ) -> Result<TokenStream> {
151
5
        let struct_name = format_ident!("{}", name);
152
153
5
        let type_param_tokens: Vec<_> =
154
5
            type_params.iter().map(|p| format_ident!(
"{}"3
, p)).collect();
155
156
5
        let field_tokens: Vec<TokenStream> = fields
157
5
            .iter()
158
5
            .map(|field| {
159
5
                let field_name = format_ident!("{}", field.name);
160
5
                let field_type = self
161
5
                    .transpile_type(&field.ty)
162
5
                    .unwrap_or_else(|_| quote! { _ 
}0
);
163
164
5
                if field.is_pub {
165
0
                    quote! { pub #field_name: #field_type }
166
                } else {
167
5
                    quote! { #field_name: #field_type }
168
                }
169
5
            })
170
5
            .collect();
171
172
5
        let visibility = if is_pub { 
quote!0
{ pub } } else { quote! {} };
173
174
5
        if type_params.is_empty() {
175
3
            Ok(quote! {
176
                #visibility struct #struct_name {
177
                    #(#field_tokens,)*
178
                }
179
            })
180
        } else {
181
2
            Ok(quote! {
182
                #visibility struct #struct_name<#(#type_param_tokens),*> {
183
                    #(#field_tokens,)*
184
                }
185
            })
186
        }
187
5
    }
188
189
    /// Transpiles trait definitions
190
1
    pub fn transpile_enum(
191
1
        &self,
192
1
        name: &str,
193
1
        type_params: &[String],
194
1
        variants: &[EnumVariant],
195
1
        is_pub: bool,
196
1
    ) -> Result<TokenStream> {
197
1
        let enum_name = format_ident!("{}", name);
198
199
1
        let type_param_tokens: Vec<_> =
200
2
            
type_params1
.
iter1
().
map1
(|p| format_ident!("{}", p)).
collect1
();
201
202
        // Check if any variant has discriminant values
203
2
        let 
has_discriminants1
=
variants.iter()1
.
any1
(|v| v.discriminant.is_some());
204
        
205
1
        let variant_tokens: Vec<TokenStream> = variants
206
1
            .iter()
207
2
            .
map1
(|variant| {
208
2
                let variant_name = format_ident!("{}", variant.name);
209
210
2
                if let Some(fields) = &variant.fields {
211
                    // Tuple variant (can't have discriminant)
212
2
                    let field_types: Vec<TokenStream> = fields
213
2
                        .iter()
214
2
                        .map(|ty| self.transpile_type(ty).unwrap_or_else(|_| quote! { _ 
}0
))
215
2
                        .collect();
216
2
                    quote! { #variant_name(#(#field_types),*) }
217
0
                } else if let Some(disc_value) = variant.discriminant {
218
                    // Unit variant with explicit discriminant
219
                    // Convert to i32 literal without suffix for cleaner output
220
0
                    let disc_literal = proc_macro2::Literal::i32_unsuffixed(disc_value as i32);
221
0
                    quote! { #variant_name = #disc_literal }
222
                } else {
223
                    // Unit variant without discriminant
224
0
                    quote! { #variant_name }
225
                }
226
2
            })
227
1
            .collect();
228
229
1
        let visibility = if is_pub { 
quote!0
{ pub } } else { quote! {} };
230
        
231
        // Add #[repr(i32)] attribute if enum has discriminant values
232
1
        let repr_attr = if has_discriminants {
233
0
            quote! { #[repr(i32)] }
234
        } else {
235
1
            quote! {}
236
        };
237
238
1
        if type_params.is_empty() {
239
0
            Ok(quote! {
240
                #repr_attr
241
                #visibility enum #enum_name {
242
                    #(#variant_tokens,)*
243
                }
244
            })
245
        } else {
246
1
            Ok(quote! {
247
                #repr_attr
248
                #visibility enum #enum_name<#(#type_param_tokens),*> {
249
                    #(#variant_tokens,)*
250
                }
251
            })
252
        }
253
1
    }
254
255
1
    pub fn transpile_trait(
256
1
        &self,
257
1
        name: &str,
258
1
        type_params: &[String],
259
1
        methods: &[TraitMethod],
260
1
        is_pub: bool,
261
1
    ) -> Result<TokenStream> {
262
1
        let trait_name = format_ident!("{}", name);
263
264
1
        let method_tokens: Result<Vec<_>> = methods
265
1
            .iter()
266
1
            .map(|method| {
267
1
                let method_name = format_ident!("{}", method.name);
268
269
                // Process parameters
270
1
                let param_tokens: Vec<TokenStream> = method
271
1
                    .params
272
1
                    .iter()
273
1
                    .enumerate()
274
1
                    .map(|(i, param)| 
{0
275
0
                        if i == 0 && (param.name() == "self" || param.name() == "&self") {
276
                            // Handle self parameter - check if it's &self or self
277
0
                            if param.name().starts_with('&') {
278
0
                                quote! { &self }
279
                            } else {
280
0
                                quote! { self }
281
                            }
282
                        } else {
283
0
                            let param_name = format_ident!("{}", param.name());
284
0
                            let type_tokens = self
285
0
                                .transpile_type(&param.ty)
286
0
                                .unwrap_or_else(|_| quote! { _ });
287
0
                            quote! { #param_name: #type_tokens }
288
                        }
289
0
                    })
290
1
                    .collect();
291
292
                // Process return type
293
1
                let return_type_tokens = if let Some(
ref ty0
) = method.return_type {
294
0
                    let ty_tokens = self.transpile_type(ty)?;
295
0
                    quote! { -> #ty_tokens }
296
                } else {
297
1
                    quote! {}
298
                };
299
300
                // Process method visibility
301
1
                let visibility = if method.is_pub { quote! { pub } } else { 
quote!0
{} };
302
303
                // Process method body (if default implementation)
304
1
                if let Some(
ref body0
) = method.body {
305
0
                    let body_tokens = self.transpile_expr(body)?;
306
0
                    Ok(quote! {
307
                        #visibility fn #method_name(#(#param_tokens),*) #return_type_tokens {
308
                            #body_tokens
309
                        }
310
                    })
311
                } else {
312
1
                    Ok(quote! {
313
                        #visibility fn #method_name(#(#param_tokens),*) #return_type_tokens;
314
                    })
315
                }
316
1
            })
317
1
            .collect();
318
319
1
        let method_tokens = method_tokens
?0
;
320
321
1
        let type_param_tokens: Vec<_> =
322
1
            type_params.iter().map(|p| format_ident!(
"{}"0
, p)).collect();
323
324
1
        let visibility = if is_pub { 
quote!0
{ pub } } else { quote! {} };
325
326
1
        if type_params.is_empty() {
327
1
            Ok(quote! {
328
                #visibility trait #trait_name {
329
                    #(#method_tokens)*
330
                }
331
            })
332
        } else {
333
0
            Ok(quote! {
334
                #visibility trait #trait_name<#(#type_param_tokens),*> {
335
                    #(#method_tokens)*
336
                }
337
            })
338
        }
339
1
    }
340
341
    /// Transpiles impl blocks
342
1
    pub fn transpile_impl(
343
1
        &self,
344
1
        for_type: &str,
345
1
        type_params: &[String],
346
1
        trait_name: Option<&str>,
347
1
        methods: &[ImplMethod],
348
1
        _is_pub: bool,
349
1
    ) -> Result<TokenStream> {
350
1
        let type_ident = format_ident!("{}", for_type);
351
352
1
        let method_tokens: Result<Vec<_>> = methods
353
1
            .iter()
354
1
            .map(|method| 
{0
355
0
                let method_name = format_ident!("{}", method.name);
356
357
                // Process parameters
358
0
                let param_tokens: Vec<TokenStream> = method
359
0
                    .params
360
0
                    .iter()
361
0
                    .enumerate()
362
0
                    .map(|(i, param)| {
363
0
                        if i == 0 && (param.name() == "self" || param.name() == "&self") {
364
                            // Handle self parameter
365
0
                            if param.name().starts_with('&') {
366
0
                                quote! { &self }
367
                            } else {
368
0
                                quote! { self }
369
                            }
370
                        } else {
371
0
                            let param_name = format_ident!("{}", param.name());
372
0
                            let type_tokens = self
373
0
                                .transpile_type(&param.ty)
374
0
                                .unwrap_or_else(|_| quote! { _ });
375
0
                            quote! { #param_name: #type_tokens }
376
                        }
377
0
                    })
378
0
                    .collect();
379
380
                // Process return type
381
0
                let return_type_tokens = if let Some(ref ty) = method.return_type {
382
0
                    let ty_tokens = self.transpile_type(ty)?;
383
0
                    quote! { -> #ty_tokens }
384
                } else {
385
0
                    quote! {}
386
                };
387
388
                // Process method body (always present in ImplMethod)
389
0
                let body_tokens = self.transpile_expr(&method.body)?;
390
391
                // Process method visibility
392
0
                let visibility = if method.is_pub { quote! { pub } } else { quote! {} };
393
394
0
                Ok(quote! {
395
                    #visibility fn #method_name(#(#param_tokens),*) #return_type_tokens {
396
                        #body_tokens
397
                    }
398
                })
399
0
            })
400
1
            .collect();
401
402
1
        let method_tokens = method_tokens
?0
;
403
404
1
        let type_param_tokens: Vec<_> =
405
1
            type_params.iter().map(|p| format_ident!(
"{}"0
, p)).collect();
406
407
1
        if let Some(
trait_name0
) = trait_name {
408
0
            let trait_ident = format_ident!("{}", trait_name);
409
0
            if type_params.is_empty() {
410
0
                Ok(quote! {
411
                    impl #trait_ident for #type_ident {
412
                        #(#method_tokens)*
413
                    }
414
                })
415
            } else {
416
0
                Ok(quote! {
417
                    impl<#(#type_param_tokens),*> #trait_ident for #type_ident<#(#type_param_tokens),*> {
418
                        #(#method_tokens)*
419
                    }
420
                })
421
            }
422
        } else {
423
1
            if type_params.is_empty() {
424
1
                Ok(quote! {
425
                    impl #type_ident {
426
                        #(#method_tokens)*
427
                    }
428
                })
429
            } else {
430
0
                Ok(quote! {
431
                    impl<#(#type_param_tokens),*> #type_ident<#(#type_param_tokens),*> {
432
                        #(#method_tokens)*
433
                    }
434
                })
435
            }
436
        }
437
1
    }
438
439
    /// Transpiles property test attributes
440
0
    pub fn transpile_property_test(&self, expr: &Expr, _attr: &Attribute) -> Result<TokenStream> {
441
        // Property tests in Rust typically use proptest or quickcheck
442
        // We'll generate proptest-compatible code
443
444
        if let ExprKind::Function {
445
0
            name, params, body, ..
446
0
        } = &expr.kind
447
        {
448
0
            let fn_name = format_ident!("{}", name);
449
450
            // Generate property test parameters
451
0
            let param_tokens: Vec<TokenStream> = params
452
0
                .iter()
453
0
                .map(|p| {
454
0
                    let param_name = format_ident!("{}", p.name());
455
0
                    let type_tokens = self
456
0
                        .transpile_type(&p.ty)
457
0
                        .unwrap_or_else(|_| quote! { i32 });
458
0
                    quote! { #param_name: #type_tokens }
459
0
                })
460
0
                .collect();
461
462
0
            let body_tokens = self.transpile_expr(body)?;
463
464
            // Generate the proptest macro invocation
465
0
            Ok(quote! {
466
                #[cfg(test)]
467
                mod #fn_name {
468
                    use super::*;
469
                    use proptest::prelude::*;
470
471
                    proptest! {
472
                        #[test]
473
                        fn #fn_name(#(#param_tokens),*) {
474
                            #body_tokens
475
                        }
476
                    }
477
                }
478
            })
479
        } else {
480
0
            bail!("Property test attribute can only be applied to functions");
481
        }
482
0
    }
483
484
    /// Transpiles extension methods into trait + impl
485
    ///
486
    /// Generates both a trait definition and an implementation according to the specification:
487
    /// ```rust
488
    /// // Ruchy: extend String { fun is_palindrome(&self) -> bool { ... } }
489
    /// // Rust:  trait StringExt { fn is_palindrome(&self) -> bool; }
490
    /// //        impl StringExt for String { fn is_palindrome(&self) -> bool { ... } }
491
    /// ```
492
0
    pub fn transpile_extend(
493
0
        &self,
494
0
        target_type: &str,
495
0
        methods: &[ImplMethod],
496
0
    ) -> Result<TokenStream> {
497
0
        let target_ident = format_ident!("{}", target_type);
498
0
        let trait_name = format_ident!("{}Ext", target_type); // e.g., StringExt
499
500
        // Generate trait definition
501
0
        let trait_method_tokens: Result<Vec<_>> = methods
502
0
            .iter()
503
0
            .map(|method| {
504
0
                let method_name = format_ident!("{}", method.name);
505
506
                // Process parameters
507
0
                let param_tokens: Vec<TokenStream> = method
508
0
                    .params
509
0
                    .iter()
510
0
                    .enumerate()
511
0
                    .map(|(i, param)| {
512
0
                        if i == 0 && (param.name() == "self" || param.name() == "&self") {
513
                            // Handle self parameter
514
0
                            if param.name().starts_with('&') {
515
0
                                quote! { &self }
516
                            } else {
517
0
                                quote! { self }
518
                            }
519
                        } else {
520
0
                            let param_name = format_ident!("{}", param.name());
521
0
                            let type_tokens = self
522
0
                                .transpile_type(&param.ty)
523
0
                                .unwrap_or_else(|_| quote! { _ });
524
0
                            quote! { #param_name: #type_tokens }
525
                        }
526
0
                    })
527
0
                    .collect();
528
529
                // Process return type
530
0
                let return_type_tokens = if let Some(ref ty) = method.return_type {
531
0
                    let ty_tokens = self.transpile_type(ty)?;
532
0
                    quote! { -> #ty_tokens }
533
                } else {
534
0
                    quote! {}
535
                };
536
537
                // Trait methods are just signatures (no body)
538
0
                Ok(quote! {
539
                    fn #method_name(#(#param_tokens),*) #return_type_tokens;
540
                })
541
0
            })
542
0
            .collect();
543
544
0
        let trait_method_tokens = trait_method_tokens?;
545
546
        // Generate impl definition
547
0
        let impl_method_tokens: Result<Vec<_>> = methods
548
0
            .iter()
549
0
            .map(|method| {
550
0
                let method_name = format_ident!("{}", method.name);
551
552
                // Process parameters (same as trait)
553
0
                let param_tokens: Vec<TokenStream> = method
554
0
                    .params
555
0
                    .iter()
556
0
                    .enumerate()
557
0
                    .map(|(i, param)| {
558
0
                        if i == 0 && (param.name() == "self" || param.name() == "&self") {
559
0
                            if param.name().starts_with('&') {
560
0
                                quote! { &self }
561
                            } else {
562
0
                                quote! { self }
563
                            }
564
                        } else {
565
0
                            let param_name = format_ident!("{}", param.name());
566
0
                            let type_tokens = self
567
0
                                .transpile_type(&param.ty)
568
0
                                .unwrap_or_else(|_| quote! { _ });
569
0
                            quote! { #param_name: #type_tokens }
570
                        }
571
0
                    })
572
0
                    .collect();
573
574
                // Process return type
575
0
                let return_type_tokens = if let Some(ref ty) = method.return_type {
576
0
                    let ty_tokens = self.transpile_type(ty)?;
577
0
                    quote! { -> #ty_tokens }
578
                } else {
579
0
                    quote! {}
580
                };
581
582
                // Impl methods have bodies
583
0
                let body_tokens = self.transpile_expr(&method.body)?;
584
585
0
                Ok(quote! {
586
                    fn #method_name(#(#param_tokens),*) #return_type_tokens {
587
                        #body_tokens
588
                    }
589
                })
590
0
            })
591
0
            .collect();
592
593
0
        let impl_method_tokens = impl_method_tokens?;
594
595
        // Generate both trait and impl
596
0
        Ok(quote! {
597
            trait #trait_name {
598
                #(#trait_method_tokens)*
599
            }
600
601
            impl #trait_name for #target_ident {
602
                #(#impl_method_tokens)*
603
            }
604
        })
605
0
    }
606
}
607
608
#[cfg(test)]
609
mod tests {
610
    use super::*;
611
    
612
    #[test]
613
1
    fn test_transpile_result_helpers() {
614
1
        let helpers = Transpiler::generate_result_helpers();
615
1
        let code = helpers.to_string();
616
        
617
        // Check that the ResultExt trait is generated
618
1
        assert!(code.contains("trait ResultExt"));
619
1
        assert!(code.contains("map_err_with"));
620
1
        assert!(code.contains("unwrap_or_else_with"));
621
1
        assert!(code.contains("and_then_with"));
622
1
        assert!(code.contains("or_else_with"));
623
1
    }
624
}