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