Skip to main content

oxilean_codegen/csharp_backend/
functions.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use crate::lcnf::*;
6use std::fmt::Write as FmtWrite;
7
8use super::types::{
9    CSharpBackend, CSharpClass, CSharpEnum, CSharpExpr, CSharpInterface, CSharpInterpolationPart,
10    CSharpLit, CSharpMethod, CSharpModule, CSharpProperty, CSharpRecord, CSharpStmt,
11    CSharpSwitchArm, CSharpType,
12};
13
14/// Map an LCNF type to a C# type.
15pub(super) fn lcnf_type_to_csharp(ty: &LcnfType) -> CSharpType {
16    match ty {
17        LcnfType::Nat => CSharpType::Long,
18        LcnfType::LcnfString => CSharpType::String,
19        LcnfType::Unit => CSharpType::Void,
20        LcnfType::Erased | LcnfType::Irrelevant => CSharpType::Object,
21        LcnfType::Object => CSharpType::Object,
22        LcnfType::Var(name) => CSharpType::Custom(name.clone()),
23        LcnfType::Fun(params, ret) => {
24            let cs_params: Vec<CSharpType> = params.iter().map(lcnf_type_to_csharp).collect();
25            let cs_ret = lcnf_type_to_csharp(ret);
26            CSharpType::Func(cs_params, Box::new(cs_ret))
27        }
28        LcnfType::Ctor(name, _args) => CSharpType::Custom(name.clone()),
29    }
30}
31/// Emit a block of statements into a `String` buffer with indentation.
32pub(super) fn emit_stmts(stmts: &[CSharpStmt], indent: &str, out: &mut std::string::String) {
33    for stmt in stmts {
34        emit_stmt(stmt, indent, out);
35    }
36}
37/// Emit a single statement into a `String` buffer.
38pub(super) fn emit_stmt(stmt: &CSharpStmt, indent: &str, out: &mut std::string::String) {
39    let inner = format!("{}    ", indent);
40    match stmt {
41        CSharpStmt::Expr(expr) => {
42            let _ = writeln!(out, "{}{};", indent, expr);
43        }
44        CSharpStmt::Assign { target, value } => {
45            let _ = writeln!(out, "{}{} = {};", indent, target, value);
46        }
47        CSharpStmt::LocalVar {
48            name,
49            ty,
50            init,
51            is_const,
52        } => {
53            let kw = if *is_const { "const" } else { "var" };
54            match (ty, init) {
55                (Some(t), Some(v)) => {
56                    let _ = writeln!(out, "{}{} {} {} = {};", indent, kw, t, name, v);
57                }
58                (Some(t), None) => {
59                    let _ = writeln!(out, "{}{} {};", indent, t, name);
60                }
61                (None, Some(v)) => {
62                    let _ = writeln!(out, "{}{} {} = {};", indent, kw, name, v);
63                }
64                (None, None) => {
65                    let _ = writeln!(out, "{}{} {};", indent, kw, name);
66                }
67            }
68        }
69        CSharpStmt::Return(None) => {
70            let _ = writeln!(out, "{}return;", indent);
71        }
72        CSharpStmt::Return(Some(expr)) => {
73            let _ = writeln!(out, "{}return {};", indent, expr);
74        }
75        CSharpStmt::Break => {
76            let _ = writeln!(out, "{}break;", indent);
77        }
78        CSharpStmt::Continue => {
79            let _ = writeln!(out, "{}continue;", indent);
80        }
81        CSharpStmt::YieldBreak => {
82            let _ = writeln!(out, "{}yield break;", indent);
83        }
84        CSharpStmt::YieldReturn(expr) => {
85            let _ = writeln!(out, "{}yield return {};", indent, expr);
86        }
87        CSharpStmt::Throw(expr) => {
88            let _ = writeln!(out, "{}throw {};", indent, expr);
89        }
90        CSharpStmt::If {
91            cond,
92            then_stmts,
93            else_stmts,
94        } => {
95            let _ = writeln!(out, "{}if ({})", indent, cond);
96            let _ = writeln!(out, "{}{{", indent);
97            emit_stmts(then_stmts, &inner, out);
98            let _ = writeln!(out, "{}}}", indent);
99            if !else_stmts.is_empty() {
100                let _ = writeln!(out, "{}else", indent);
101                let _ = writeln!(out, "{}{{", indent);
102                emit_stmts(else_stmts, &inner, out);
103                let _ = writeln!(out, "{}}}", indent);
104            }
105        }
106        CSharpStmt::Switch {
107            expr,
108            cases,
109            default,
110        } => {
111            let _ = writeln!(out, "{}switch ({})", indent, expr);
112            let _ = writeln!(out, "{}{{", indent);
113            for case in cases {
114                let _ = writeln!(out, "{}    case {}:", indent, case.label);
115                emit_stmts(&case.stmts, &format!("{}        ", indent), out);
116            }
117            if !default.is_empty() {
118                let _ = writeln!(out, "{}    default:", indent);
119                emit_stmts(default, &format!("{}        ", indent), out);
120            }
121            let _ = writeln!(out, "{}}}", indent);
122        }
123        CSharpStmt::While { cond, body } => {
124            let _ = writeln!(out, "{}while ({})", indent, cond);
125            let _ = writeln!(out, "{}{{", indent);
126            emit_stmts(body, &inner, out);
127            let _ = writeln!(out, "{}}}", indent);
128        }
129        CSharpStmt::For {
130            init,
131            cond,
132            step,
133            body,
134        } => {
135            let init_str = match init {
136                None => std::string::String::new(),
137                Some(s) => stmt_to_inline_str(s),
138            };
139            let cond_str = cond.as_ref().map(|c| format!("{}", c)).unwrap_or_default();
140            let step_str = step.as_ref().map(|s| format!("{}", s)).unwrap_or_default();
141            let _ = writeln!(
142                out,
143                "{}for ({}; {}; {})",
144                indent, init_str, cond_str, step_str
145            );
146            let _ = writeln!(out, "{}{{", indent);
147            emit_stmts(body, &inner, out);
148            let _ = writeln!(out, "{}}}", indent);
149        }
150        CSharpStmt::ForEach {
151            var_name,
152            var_ty,
153            collection,
154            body,
155        } => {
156            let ty_str = var_ty
157                .as_ref()
158                .map(|t| format!("{} ", t))
159                .unwrap_or_else(|| "var ".to_string());
160            let _ = writeln!(
161                out,
162                "{}foreach ({}{} in {})",
163                indent, ty_str, var_name, collection
164            );
165            let _ = writeln!(out, "{}{{", indent);
166            emit_stmts(body, &inner, out);
167            let _ = writeln!(out, "{}}}", indent);
168        }
169        CSharpStmt::TryCatch {
170            try_stmts,
171            catches,
172            finally_stmts,
173        } => {
174            let _ = writeln!(out, "{}try", indent);
175            let _ = writeln!(out, "{}{{", indent);
176            emit_stmts(try_stmts, &inner, out);
177            let _ = writeln!(out, "{}}}", indent);
178            for catch in catches {
179                let _ = writeln!(
180                    out,
181                    "{}catch ({} {})",
182                    indent, catch.exception_type, catch.var_name
183                );
184                let _ = writeln!(out, "{}{{", indent);
185                emit_stmts(&catch.stmts, &inner, out);
186                let _ = writeln!(out, "{}}}", indent);
187            }
188            if !finally_stmts.is_empty() {
189                let _ = writeln!(out, "{}finally", indent);
190                let _ = writeln!(out, "{}{{", indent);
191                emit_stmts(finally_stmts, &inner, out);
192                let _ = writeln!(out, "{}}}", indent);
193            }
194        }
195        CSharpStmt::Using {
196            resource,
197            var_name,
198            body,
199        } => {
200            if body.is_empty() {
201                if let Some(name) = var_name {
202                    let _ = writeln!(out, "{}using var {} = {};", indent, name, resource);
203                } else {
204                    let _ = writeln!(out, "{}using ({});", indent, resource);
205                }
206            } else {
207                let _ = writeln!(out, "{}using ({})", indent, resource);
208                let _ = writeln!(out, "{}{{", indent);
209                emit_stmts(body, &inner, out);
210                let _ = writeln!(out, "{}}}", indent);
211            }
212        }
213        CSharpStmt::Lock { obj, body } => {
214            let _ = writeln!(out, "{}lock ({})", indent, obj);
215            let _ = writeln!(out, "{}{{", indent);
216            emit_stmts(body, &inner, out);
217            let _ = writeln!(out, "{}}}", indent);
218        }
219    }
220}
221/// Render a statement as a short inline string (for `for` loop init).
222pub(super) fn stmt_to_inline_str(stmt: &CSharpStmt) -> std::string::String {
223    match stmt {
224        CSharpStmt::LocalVar {
225            name,
226            ty,
227            init,
228            is_const,
229        } => {
230            let kw = if *is_const { "const" } else { "var" };
231            if let (Some(t), Some(v)) = (ty, init) {
232                format!("{} {} {} = {}", kw, t, name, v)
233            } else if let (None, Some(v)) = (ty, init) {
234                format!("{} {} = {}", kw, name, v)
235            } else {
236                format!("{} {}", kw, name)
237            }
238        }
239        CSharpStmt::Assign { target, value } => format!("{} = {}", target, value),
240        _ => std::string::String::new(),
241    }
242}
243/// All C# reserved keywords (sorted for binary search).
244pub const CSHARP_KEYWORDS: &[&str] = &[
245    "abstract",
246    "add",
247    "alias",
248    "as",
249    "ascending",
250    "async",
251    "await",
252    "base",
253    "bool",
254    "break",
255    "by",
256    "byte",
257    "case",
258    "catch",
259    "char",
260    "checked",
261    "class",
262    "const",
263    "continue",
264    "decimal",
265    "default",
266    "delegate",
267    "descending",
268    "do",
269    "double",
270    "dynamic",
271    "else",
272    "enum",
273    "equals",
274    "event",
275    "explicit",
276    "extern",
277    "false",
278    "finally",
279    "fixed",
280    "float",
281    "for",
282    "foreach",
283    "from",
284    "get",
285    "global",
286    "goto",
287    "group",
288    "if",
289    "implicit",
290    "in",
291    "init",
292    "int",
293    "interface",
294    "internal",
295    "into",
296    "is",
297    "join",
298    "let",
299    "lock",
300    "long",
301    "managed",
302    "nameof",
303    "namespace",
304    "new",
305    "notnull",
306    "null",
307    "object",
308    "on",
309    "operator",
310    "orderby",
311    "out",
312    "override",
313    "params",
314    "partial",
315    "private",
316    "protected",
317    "public",
318    "readonly",
319    "record",
320    "ref",
321    "remove",
322    "required",
323    "return",
324    "sbyte",
325    "sealed",
326    "select",
327    "set",
328    "short",
329    "sizeof",
330    "stackalloc",
331    "static",
332    "string",
333    "struct",
334    "switch",
335    "this",
336    "throw",
337    "true",
338    "try",
339    "typeof",
340    "uint",
341    "ulong",
342    "unchecked",
343    "unmanaged",
344    "unsafe",
345    "ushort",
346    "using",
347    "value",
348    "var",
349    "virtual",
350    "void",
351    "volatile",
352    "when",
353    "where",
354    "while",
355    "with",
356    "yield",
357];
358/// Check if a string is a C# keyword.
359pub fn is_csharp_keyword(s: &str) -> bool {
360    CSHARP_KEYWORDS.contains(&s)
361}
362/// Minimal C# runtime helper class emitted at the end of every generated file.
363pub const CSHARP_RUNTIME: &str = r#"
364/// <summary>OxiLean C# Runtime helpers.</summary>
365internal static class OxiLeanRt
366{
367    /// <summary>Called when pattern matching reaches an unreachable branch.</summary>
368    public static T Unreachable<T>() =>
369        throw new InvalidOperationException("OxiLean: unreachable code reached");
370
371    /// <summary>Natural number addition (long arithmetic).</summary>
372    public static long NatAdd(long a, long b) => a + b;
373
374    /// <summary>Natural number subtraction (saturating at 0).</summary>
375    public static long NatSub(long a, long b) => Math.Max(0L, a - b);
376
377    /// <summary>Natural number multiplication.</summary>
378    public static long NatMul(long a, long b) => a * b;
379
380    /// <summary>Natural number division (truncating, 0 if divisor is 0).</summary>
381    public static long NatDiv(long a, long b) => b == 0L ? 0L : a / b;
382
383    /// <summary>Natural number modulo.</summary>
384    public static long NatMod(long a, long b) => b == 0L ? a : a % b;
385
386    /// <summary>Boolean to nat (decidable equality).</summary>
387    public static long Decide(bool b) => b ? 1L : 0L;
388
389    /// <summary>String representation of a natural number.</summary>
390    public static string NatToString(long n) => n.ToString();
391
392    /// <summary>String append.</summary>
393    public static string StrAppend(string a, string b) => a + b;
394
395    /// <summary>List.cons — prepend element to list.</summary>
396    public static List<A> Cons<A>(A head, List<A> tail)
397    {
398        var result = new List<A> { head };
399        result.AddRange(tail);
400        return result;
401    }
402
403    /// <summary>List.nil — empty list.</summary>
404    public static List<A> Nil<A>() => new List<A>();
405
406    /// <summary>Option.some.</summary>
407    public static A? Some<A>(A value) where A : class => value;
408
409    /// <summary>Option.none.</summary>
410    public static A? None<A>() where A : class => null;
411}
412"#;
413#[cfg(test)]
414mod tests {
415    use super::*;
416    #[test]
417    pub(super) fn test_type_primitives() {
418        assert_eq!(CSharpType::Int.to_string(), "int");
419        assert_eq!(CSharpType::Long.to_string(), "long");
420        assert_eq!(CSharpType::Double.to_string(), "double");
421        assert_eq!(CSharpType::Float.to_string(), "float");
422        assert_eq!(CSharpType::Bool.to_string(), "bool");
423        assert_eq!(CSharpType::String.to_string(), "string");
424        assert_eq!(CSharpType::Void.to_string(), "void");
425        assert_eq!(CSharpType::Object.to_string(), "object");
426    }
427    #[test]
428    pub(super) fn test_type_list() {
429        let ty = CSharpType::List(Box::new(CSharpType::Int));
430        assert_eq!(ty.to_string(), "List<int>");
431    }
432    #[test]
433    pub(super) fn test_type_dict() {
434        let ty = CSharpType::Dict(Box::new(CSharpType::String), Box::new(CSharpType::Int));
435        assert_eq!(ty.to_string(), "Dictionary<string, int>");
436    }
437    #[test]
438    pub(super) fn test_type_tuple() {
439        let ty = CSharpType::Tuple(vec![CSharpType::Int, CSharpType::String]);
440        assert_eq!(ty.to_string(), "(int, string)");
441    }
442    #[test]
443    pub(super) fn test_type_nullable() {
444        let ty = CSharpType::Nullable(Box::new(CSharpType::String));
445        assert_eq!(ty.to_string(), "string?");
446    }
447    #[test]
448    pub(super) fn test_type_task_void() {
449        let ty = CSharpType::Task(Box::new(CSharpType::Void));
450        assert_eq!(ty.to_string(), "Task");
451    }
452    #[test]
453    pub(super) fn test_type_task_int() {
454        let ty = CSharpType::Task(Box::new(CSharpType::Int));
455        assert_eq!(ty.to_string(), "Task<int>");
456    }
457    #[test]
458    pub(super) fn test_type_custom() {
459        let ty = CSharpType::Custom("MyClass".to_string());
460        assert_eq!(ty.to_string(), "MyClass");
461    }
462    #[test]
463    pub(super) fn test_type_ienumerable() {
464        let ty = CSharpType::IEnumerable(Box::new(CSharpType::Long));
465        assert_eq!(ty.to_string(), "IEnumerable<long>");
466    }
467    #[test]
468    pub(super) fn test_type_func() {
469        let ty = CSharpType::Func(
470            vec![CSharpType::Int, CSharpType::Int],
471            Box::new(CSharpType::Bool),
472        );
473        assert_eq!(ty.to_string(), "Func<int, int, bool>");
474    }
475    #[test]
476    pub(super) fn test_type_action_empty() {
477        let ty = CSharpType::Action(vec![]);
478        assert_eq!(ty.to_string(), "Action");
479    }
480    #[test]
481    pub(super) fn test_lit_int() {
482        assert_eq!(CSharpLit::Int(42).to_string(), "42");
483        assert_eq!(CSharpLit::Int(-7).to_string(), "-7");
484    }
485    #[test]
486    pub(super) fn test_lit_long() {
487        assert_eq!(CSharpLit::Long(100).to_string(), "100L");
488    }
489    #[test]
490    pub(super) fn test_lit_bool() {
491        assert_eq!(CSharpLit::Bool(true).to_string(), "true");
492        assert_eq!(CSharpLit::Bool(false).to_string(), "false");
493    }
494    #[test]
495    pub(super) fn test_lit_null() {
496        assert_eq!(CSharpLit::Null.to_string(), "null");
497    }
498    #[test]
499    pub(super) fn test_lit_str_basic() {
500        assert_eq!(CSharpLit::Str("hello".to_string()).to_string(), "\"hello\"");
501    }
502    #[test]
503    pub(super) fn test_lit_str_escapes() {
504        let s = CSharpLit::Str("hi\n\"world\"\\".to_string());
505        let result = s.to_string();
506        assert!(result.contains("\\n"));
507        assert!(result.contains("\\\""));
508        assert!(result.contains("\\\\"));
509    }
510    #[test]
511    pub(super) fn test_lit_double() {
512        assert_eq!(CSharpLit::Double(3.14).to_string(), "3.14");
513        assert_eq!(CSharpLit::Double(2.0).to_string(), "2.0");
514    }
515    #[test]
516    pub(super) fn test_lit_float() {
517        assert_eq!(CSharpLit::Float(1.0).to_string(), "1.0f");
518    }
519    #[test]
520    pub(super) fn test_expr_var() {
521        let e = CSharpExpr::Var("myVar".to_string());
522        assert_eq!(e.to_string(), "myVar");
523    }
524    #[test]
525    pub(super) fn test_expr_binop() {
526        let e = CSharpExpr::BinOp {
527            op: "+".to_string(),
528            lhs: Box::new(CSharpExpr::Lit(CSharpLit::Int(1))),
529            rhs: Box::new(CSharpExpr::Lit(CSharpLit::Int(2))),
530        };
531        assert_eq!(e.to_string(), "(1 + 2)");
532    }
533    #[test]
534    pub(super) fn test_expr_call() {
535        let e = CSharpExpr::Call {
536            callee: Box::new(CSharpExpr::Var("Foo".to_string())),
537            args: vec![
538                CSharpExpr::Lit(CSharpLit::Int(1)),
539                CSharpExpr::Lit(CSharpLit::Int(2)),
540            ],
541        };
542        assert_eq!(e.to_string(), "Foo(1, 2)");
543    }
544    #[test]
545    pub(super) fn test_expr_method_call_linq() {
546        let e = CSharpExpr::MethodCall {
547            receiver: Box::new(CSharpExpr::Var("list".to_string())),
548            method: "Where".to_string(),
549            type_args: vec![],
550            args: vec![CSharpExpr::Lambda {
551                params: vec![("x".to_string(), None)],
552                body: Box::new(CSharpExpr::BinOp {
553                    op: ">".to_string(),
554                    lhs: Box::new(CSharpExpr::Var("x".to_string())),
555                    rhs: Box::new(CSharpExpr::Lit(CSharpLit::Int(0))),
556                }),
557            }],
558        };
559        assert!(e.to_string().contains("list.Where("));
560        assert!(e.to_string().contains("x => (x > 0)"));
561    }
562    #[test]
563    pub(super) fn test_expr_new() {
564        let e = CSharpExpr::New {
565            ty: CSharpType::Custom("MyClass".to_string()),
566            args: vec![CSharpExpr::Lit(CSharpLit::Int(42))],
567        };
568        assert_eq!(e.to_string(), "new MyClass(42)");
569    }
570    #[test]
571    pub(super) fn test_expr_lambda_single_param() {
572        let e = CSharpExpr::Lambda {
573            params: vec![("x".to_string(), None)],
574            body: Box::new(CSharpExpr::BinOp {
575                op: "*".to_string(),
576                lhs: Box::new(CSharpExpr::Var("x".to_string())),
577                rhs: Box::new(CSharpExpr::Lit(CSharpLit::Int(2))),
578            }),
579        };
580        assert_eq!(e.to_string(), "x => (x * 2)");
581    }
582    #[test]
583    pub(super) fn test_expr_lambda_multi_param() {
584        let e = CSharpExpr::Lambda {
585            params: vec![
586                ("x".to_string(), Some(CSharpType::Int)),
587                ("y".to_string(), Some(CSharpType::Int)),
588            ],
589            body: Box::new(CSharpExpr::BinOp {
590                op: "+".to_string(),
591                lhs: Box::new(CSharpExpr::Var("x".to_string())),
592                rhs: Box::new(CSharpExpr::Var("y".to_string())),
593            }),
594        };
595        assert!(e.to_string().contains("(int x, int y)"));
596        assert!(e.to_string().contains("=> (x + y)"));
597    }
598    #[test]
599    pub(super) fn test_expr_ternary() {
600        let e = CSharpExpr::Ternary {
601            cond: Box::new(CSharpExpr::Lit(CSharpLit::Bool(true))),
602            then_expr: Box::new(CSharpExpr::Lit(CSharpLit::Int(1))),
603            else_expr: Box::new(CSharpExpr::Lit(CSharpLit::Int(0))),
604        };
605        assert_eq!(e.to_string(), "(true ? 1 : 0)");
606    }
607    #[test]
608    pub(super) fn test_expr_await() {
609        let e = CSharpExpr::Await(Box::new(CSharpExpr::Call {
610            callee: Box::new(CSharpExpr::Var("FetchAsync".to_string())),
611            args: vec![],
612        }));
613        assert_eq!(e.to_string(), "await FetchAsync()");
614    }
615    #[test]
616    pub(super) fn test_expr_is_pattern() {
617        let e = CSharpExpr::Is {
618            expr: Box::new(CSharpExpr::Var("obj".to_string())),
619            pattern: "string s".to_string(),
620        };
621        assert_eq!(e.to_string(), "(obj is string s)");
622    }
623    #[test]
624    pub(super) fn test_expr_as_cast() {
625        let e = CSharpExpr::As {
626            expr: Box::new(CSharpExpr::Var("obj".to_string())),
627            ty: CSharpType::Custom("MyClass".to_string()),
628        };
629        assert_eq!(e.to_string(), "(obj as MyClass)");
630    }
631    #[test]
632    pub(super) fn test_expr_switch_expression() {
633        let e = CSharpExpr::SwitchExpr {
634            scrutinee: Box::new(CSharpExpr::Var("x".to_string())),
635            arms: vec![
636                CSharpSwitchArm {
637                    pattern: "1".to_string(),
638                    guard: None,
639                    body: CSharpExpr::Lit(CSharpLit::Str("one".to_string())),
640                },
641                CSharpSwitchArm {
642                    pattern: "_".to_string(),
643                    guard: None,
644                    body: CSharpExpr::Lit(CSharpLit::Str("other".to_string())),
645                },
646            ],
647        };
648        let out = e.to_string();
649        assert!(out.contains("x switch"));
650        assert!(out.contains("1 =>"));
651        assert!(out.contains("_ =>"));
652    }
653    #[test]
654    pub(super) fn test_expr_nameof_typeof() {
655        let e1 = CSharpExpr::NameOf("myProp".to_string());
656        let e2 = CSharpExpr::TypeOf(CSharpType::Custom("MyClass".to_string()));
657        assert_eq!(e1.to_string(), "nameof(myProp)");
658        assert_eq!(e2.to_string(), "typeof(MyClass)");
659    }
660    #[test]
661    pub(super) fn test_expr_collection() {
662        let e = CSharpExpr::CollectionExpr(vec![
663            CSharpExpr::Lit(CSharpLit::Int(1)),
664            CSharpExpr::Lit(CSharpLit::Int(2)),
665            CSharpExpr::Lit(CSharpLit::Int(3)),
666        ]);
667        assert_eq!(e.to_string(), "[1, 2, 3]");
668    }
669    #[test]
670    pub(super) fn test_expr_default() {
671        let e1 = CSharpExpr::Default(None);
672        let e2 = CSharpExpr::Default(Some(CSharpType::Int));
673        assert_eq!(e1.to_string(), "default");
674        assert_eq!(e2.to_string(), "default(int)");
675    }
676    #[test]
677    pub(super) fn test_record_simple() {
678        let mut r = CSharpRecord::new("Point");
679        r.fields.push(("X".to_string(), CSharpType::Int));
680        r.fields.push(("Y".to_string(), CSharpType::Int));
681        let out = r.emit("");
682        assert!(
683            out.contains("public record Point(int X, int Y)"),
684            "got: {}",
685            out
686        );
687    }
688    #[test]
689    pub(super) fn test_record_sealed() {
690        let mut r = CSharpRecord::new("Token");
691        r.is_sealed = true;
692        r.fields.push(("Value".to_string(), CSharpType::String));
693        let out = r.emit("");
694        assert!(
695            out.contains("public sealed record Token(string Value)"),
696            "got: {}",
697            out
698        );
699    }
700    #[test]
701    pub(super) fn test_record_readonly_struct() {
702        let mut r = CSharpRecord::new("Vec2");
703        r.is_readonly = true;
704        r.fields.push(("X".to_string(), CSharpType::Double));
705        r.fields.push(("Y".to_string(), CSharpType::Double));
706        let out = r.emit("");
707        assert!(
708            out.contains("record struct Vec2(double X, double Y)"),
709            "got: {}",
710            out
711        );
712    }
713    #[test]
714    pub(super) fn test_record_with_methods() {
715        let mut r = CSharpRecord::new("Person");
716        r.fields.push(("Name".to_string(), CSharpType::String));
717        let mut m = CSharpMethod::new("Greet", CSharpType::String);
718        m.expr_body = Some(CSharpExpr::Interpolated(vec![
719            CSharpInterpolationPart::Text("Hello, ".to_string()),
720            CSharpInterpolationPart::Expr(CSharpExpr::Var("Name".to_string())),
721        ]));
722        r.methods.push(m);
723        let out = r.emit("");
724        assert!(
725            out.contains("public record Person(string Name)"),
726            "got: {}",
727            out
728        );
729        assert!(out.contains("Greet"), "got: {}", out);
730    }
731    #[test]
732    pub(super) fn test_interface_basic() {
733        let mut iface = CSharpInterface::new("IFoo");
734        let mut m = CSharpMethod::new("Bar", CSharpType::Int);
735        m.is_abstract = true;
736        iface.methods.push(m);
737        let out = iface.emit("");
738        assert!(out.contains("public interface IFoo"), "got: {}", out);
739        assert!(out.contains("public int Bar()"), "got: {}", out);
740    }
741    #[test]
742    pub(super) fn test_interface_with_type_params() {
743        let mut iface = CSharpInterface::new("IRepository");
744        iface.type_params.push("T".to_string());
745        let out = iface.emit("");
746        assert!(
747            out.contains("public interface IRepository<T>"),
748            "got: {}",
749            out
750        );
751    }
752    #[test]
753    pub(super) fn test_class_basic() {
754        let cls = CSharpClass::new("Foo");
755        let out = cls.emit("");
756        assert!(out.contains("public class Foo"), "got: {}", out);
757        assert!(out.contains("{"));
758        assert!(out.contains("}"));
759    }
760    #[test]
761    pub(super) fn test_class_abstract() {
762        let mut cls = CSharpClass::new("Base");
763        cls.is_abstract = true;
764        let out = cls.emit("");
765        assert!(out.contains("public abstract class Base"), "got: {}", out);
766    }
767    #[test]
768    pub(super) fn test_class_sealed() {
769        let mut cls = CSharpClass::new("Final");
770        cls.is_sealed = true;
771        let out = cls.emit("");
772        assert!(out.contains("public sealed class Final"), "got: {}", out);
773    }
774    #[test]
775    pub(super) fn test_class_with_base_and_interfaces() {
776        let mut cls = CSharpClass::new("Dog");
777        cls.base_class = Some("Animal".to_string());
778        cls.interfaces.push("IComparable".to_string());
779        let out = cls.emit("");
780        assert!(
781            out.contains("class Dog : Animal, IComparable"),
782            "got: {}",
783            out
784        );
785    }
786    #[test]
787    pub(super) fn test_class_with_method() {
788        let mut cls = CSharpClass::new("Calculator");
789        let mut m = CSharpMethod::new("Add", CSharpType::Int);
790        m.params.push(("a".to_string(), CSharpType::Int));
791        m.params.push(("b".to_string(), CSharpType::Int));
792        m.body.push(CSharpStmt::Return(Some(CSharpExpr::BinOp {
793            op: "+".to_string(),
794            lhs: Box::new(CSharpExpr::Var("a".to_string())),
795            rhs: Box::new(CSharpExpr::Var("b".to_string())),
796        })));
797        cls.methods.push(m);
798        let out = cls.emit("");
799        assert!(out.contains("public int Add(int a, int b)"), "got: {}", out);
800        assert!(out.contains("return (a + b)"), "got: {}", out);
801    }
802    #[test]
803    pub(super) fn test_class_async_method() {
804        let mut cls = CSharpClass::new("Fetcher");
805        let mut m = CSharpMethod::new("FetchAsync", CSharpType::Task(Box::new(CSharpType::String)));
806        m.is_async = true;
807        m.body
808            .push(CSharpStmt::Return(Some(CSharpExpr::Await(Box::new(
809                CSharpExpr::Call {
810                    callee: Box::new(CSharpExpr::Var("httpClient.GetStringAsync".to_string())),
811                    args: vec![CSharpExpr::Lit(CSharpLit::Str(
812                        "https://example.com".to_string(),
813                    ))],
814                },
815            )))));
816        cls.methods.push(m);
817        let out = cls.emit("");
818        assert!(
819            out.contains("public async Task<string> FetchAsync()"),
820            "got: {}",
821            out
822        );
823        assert!(out.contains("await"), "got: {}", out);
824    }
825    #[test]
826    pub(super) fn test_module_namespace() {
827        let m = CSharpModule::new("OxiLean.Generated");
828        let out = m.emit();
829        assert!(out.contains("namespace OxiLean.Generated;"), "got: {}", out);
830    }
831    #[test]
832    pub(super) fn test_module_nullable_enable() {
833        let m = CSharpModule::new("Test");
834        let out = m.emit();
835        assert!(out.contains("#nullable enable"), "got: {}", out);
836    }
837    #[test]
838    pub(super) fn test_module_using_dedup() {
839        let mut m = CSharpModule::new("Test");
840        m.add_using("System");
841        m.add_using("System");
842        m.add_using("System.Linq");
843        let out = m.emit();
844        assert_eq!(out.matches("using System;").count(), 1, "got: {}", out);
845        assert!(out.contains("using System.Linq;"), "got: {}", out);
846    }
847    #[test]
848    pub(super) fn test_module_contains_runtime() {
849        let m = CSharpModule::new("Test");
850        let out = m.emit();
851        assert!(out.contains("OxiLeanRt"), "got: {}", out);
852        assert!(out.contains("NatAdd"), "got: {}", out);
853        assert!(out.contains("NatSub"), "got: {}", out);
854        assert!(out.contains("Cons"), "got: {}", out);
855    }
856    #[test]
857    pub(super) fn test_mangle_name_keywords() {
858        for kw in &["int", "class", "namespace", "return", "void", "var"] {
859            let result = CSharpBackend::mangle_name(kw);
860            assert!(
861                result.starts_with("ox_"),
862                "keyword '{}' should be prefixed, got '{}'",
863                kw,
864                result
865            );
866        }
867    }
868    #[test]
869    pub(super) fn test_mangle_name_digit_prefix() {
870        assert_eq!(CSharpBackend::mangle_name("0abc"), "ox_0abc");
871    }
872    #[test]
873    pub(super) fn test_mangle_name_empty() {
874        assert_eq!(CSharpBackend::mangle_name(""), "ox_empty");
875    }
876    #[test]
877    pub(super) fn test_mangle_name_special_chars() {
878        assert_eq!(CSharpBackend::mangle_name("foo-bar"), "foo_bar");
879        assert_eq!(CSharpBackend::mangle_name("a.b.c"), "a_b_c");
880    }
881    #[test]
882    pub(super) fn test_mangle_name_valid() {
883        assert_eq!(CSharpBackend::mangle_name("myFunc"), "myFunc");
884        assert_eq!(CSharpBackend::mangle_name("_private"), "_private");
885    }
886    #[test]
887    pub(super) fn test_fresh_var() {
888        let mut backend = CSharpBackend::new();
889        assert_eq!(backend.fresh_var(), "_cs0");
890        assert_eq!(backend.fresh_var(), "_cs1");
891        assert_eq!(backend.fresh_var(), "_cs2");
892    }
893    #[test]
894    pub(super) fn test_compile_decl_simple() {
895        let decl = LcnfFunDecl {
896            name: "myFn".to_string(),
897            original_name: None,
898            params: vec![LcnfParam {
899                id: LcnfVarId(0),
900                name: "x".to_string(),
901                ty: LcnfType::Nat,
902                erased: false,
903                borrowed: false,
904            }],
905            body: LcnfExpr::Return(LcnfArg::Var(LcnfVarId(0))),
906            ret_type: LcnfType::Nat,
907            is_recursive: false,
908            is_lifted: false,
909            inline_cost: 0,
910        };
911        let backend = CSharpBackend::new();
912        let method = backend.compile_decl(&decl);
913        assert_eq!(method.name, "myFn");
914        assert_eq!(method.params.len(), 1);
915        let out = method.emit("");
916        assert!(
917            out.contains("public static long myFn(long _x0)"),
918            "got: {}",
919            out
920        );
921        assert!(out.contains("return _x0"), "got: {}", out);
922    }
923    #[test]
924    pub(super) fn test_compile_decl_string_return() {
925        let decl = LcnfFunDecl {
926            name: "greeting".to_string(),
927            original_name: None,
928            params: vec![],
929            body: LcnfExpr::Return(LcnfArg::Lit(LcnfLit::Str("hello".to_string()))),
930            ret_type: LcnfType::LcnfString,
931            is_recursive: false,
932            is_lifted: false,
933            inline_cost: 0,
934        };
935        let backend = CSharpBackend::new();
936        let method = backend.compile_decl(&decl);
937        let out = method.emit("");
938        assert!(
939            out.contains("public static string greeting()"),
940            "got: {}",
941            out
942        );
943        assert!(out.contains("return \"hello\""), "got: {}", out);
944    }
945    #[test]
946    pub(super) fn test_emit_module_empty() {
947        let backend = CSharpBackend::new();
948        let module = backend.emit_module("OxiLean.Test", &[]);
949        let out = module.emit();
950        assert!(
951            out.contains("OxiLean-generated C# module: OxiLean.Test"),
952            "got: {}",
953            out
954        );
955        assert!(out.contains("namespace OxiLean.Test;"), "got: {}", out);
956        assert!(out.contains("using System;"), "got: {}", out);
957    }
958    #[test]
959    pub(super) fn test_backend_default() {
960        let b = CSharpBackend::default();
961        assert!(b.emit_public);
962        assert!(b.emit_comments);
963    }
964    #[test]
965    pub(super) fn test_runtime_nat_ops() {
966        assert!(CSHARP_RUNTIME.contains("NatAdd"));
967        assert!(CSHARP_RUNTIME.contains("NatSub"));
968        assert!(CSHARP_RUNTIME.contains("NatMul"));
969        assert!(CSHARP_RUNTIME.contains("NatDiv"));
970        assert!(CSHARP_RUNTIME.contains("NatMod"));
971    }
972    #[test]
973    pub(super) fn test_runtime_list_ops() {
974        assert!(CSHARP_RUNTIME.contains("Cons"));
975        assert!(CSHARP_RUNTIME.contains("Nil"));
976    }
977    #[test]
978    pub(super) fn test_enum_basic() {
979        let mut e = CSharpEnum::new("Color");
980        e.variants.push(("Red".to_string(), None));
981        e.variants.push(("Green".to_string(), Some(10)));
982        e.variants.push(("Blue".to_string(), None));
983        let out = e.emit("");
984        assert!(out.contains("public enum Color"), "got: {}", out);
985        assert!(out.contains("Red,"), "got: {}", out);
986        assert!(out.contains("Green = 10,"), "got: {}", out);
987        assert!(out.contains("Blue,"), "got: {}", out);
988    }
989    #[test]
990    pub(super) fn test_enum_with_underlying_type() {
991        let mut e = CSharpEnum::new("Flags");
992        e.underlying_type = Some(CSharpType::Int);
993        e.variants.push(("None".to_string(), Some(0)));
994        e.variants.push(("Read".to_string(), Some(1)));
995        e.variants.push(("Write".to_string(), Some(2)));
996        let out = e.emit("");
997        assert!(out.contains("public enum Flags : int"), "got: {}", out);
998    }
999    #[test]
1000    pub(super) fn test_property_auto_readwrite() {
1001        let p = CSharpProperty::new_auto("Name", CSharpType::String);
1002        let out = p.emit("    ");
1003        assert!(
1004            out.contains("public string Name { get; set; }"),
1005            "got: {}",
1006            out
1007        );
1008    }
1009    #[test]
1010    pub(super) fn test_property_expr_body() {
1011        let mut p = CSharpProperty::new_auto("Count", CSharpType::Int);
1012        p.has_setter = false;
1013        p.expr_body = Some(CSharpExpr::Lit(CSharpLit::Int(42)));
1014        let out = p.emit("    ");
1015        assert!(out.contains("public int Count => 42"), "got: {}", out);
1016    }
1017    #[test]
1018    pub(super) fn test_property_init_only() {
1019        let mut p = CSharpProperty::new_auto("Id", CSharpType::Long);
1020        p.is_init_only = true;
1021        let out = p.emit("    ");
1022        assert!(out.contains("{ get; init; }"), "got: {}", out);
1023    }
1024    #[test]
1025    pub(super) fn test_is_keyword_true() {
1026        assert!(is_csharp_keyword("int"));
1027        assert!(is_csharp_keyword("class"));
1028        assert!(is_csharp_keyword("namespace"));
1029        assert!(is_csharp_keyword("async"));
1030        assert!(is_csharp_keyword("await"));
1031        assert!(is_csharp_keyword("record"));
1032        assert!(is_csharp_keyword("var"));
1033        assert!(is_csharp_keyword("yield"));
1034    }
1035    #[test]
1036    pub(super) fn test_is_keyword_false() {
1037        assert!(!is_csharp_keyword("myFunc"));
1038        assert!(!is_csharp_keyword("oxilean"));
1039        assert!(!is_csharp_keyword("Foo"));
1040    }
1041}