Skip to main content

oxilean_codegen/fsharp_backend/
functions.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use super::types::{
6    ActivePatternKind, ComputationExprKind, FSharpActivePattern, FSharpAttribute, FSharpBackend,
7    FSharpExpr, FSharpFunction, FSharpFunctionBuilder, FSharpInterface, FSharpMeasure,
8    FSharpModule, FSharpModuleBuilder, FSharpModuleMetrics, FSharpMutualGroup,
9    FSharpNumericHelpers, FSharpPattern, FSharpRecord, FSharpSnippets, FSharpStdLib, FSharpType,
10    FSharpTypeAlias, FSharpUnion, FSharpUnionCase, FSharpUnionCaseNamed,
11};
12
13/// Wrap a type in parentheses if it contains arrows (for clarity in nested types).
14pub(super) fn paren_type(ty: &FSharpType) -> String {
15    match ty {
16        FSharpType::Fun(_, _) | FSharpType::Tuple(_) => format!("({})", ty),
17        _ => format!("{}", ty),
18    }
19}
20/// Wrap a function type in parens on the LHS of `->`.
21pub(super) fn paren_fun_type(ty: &FSharpType) -> String {
22    match ty {
23        FSharpType::Fun(_, _) => format!("({})", ty),
24        _ => format!("{}", ty),
25    }
26}
27#[cfg(test)]
28mod tests {
29    use super::*;
30    pub(super) fn b() -> FSharpBackend {
31        FSharpBackend::new()
32    }
33    #[test]
34    pub(super) fn test_type_int() {
35        assert_eq!(b().emit_type(&FSharpType::Int), "int");
36    }
37    #[test]
38    pub(super) fn test_type_fun() {
39        let ty = FSharpType::Fun(Box::new(FSharpType::Int), Box::new(FSharpType::Bool));
40        assert_eq!(b().emit_type(&ty), "int -> bool");
41    }
42    #[test]
43    pub(super) fn test_type_list() {
44        let ty = FSharpType::List(Box::new(FSharpType::FsString));
45        assert_eq!(b().emit_type(&ty), "string list");
46    }
47    #[test]
48    pub(super) fn test_type_option() {
49        let ty = FSharpType::Option(Box::new(FSharpType::Int));
50        assert_eq!(b().emit_type(&ty), "int option");
51    }
52    #[test]
53    pub(super) fn test_type_tuple() {
54        let ty = FSharpType::Tuple(vec![FSharpType::Int, FSharpType::Bool]);
55        assert_eq!(b().emit_type(&ty), "int * bool");
56    }
57    #[test]
58    pub(super) fn test_type_generic() {
59        let ty = FSharpType::Generic(
60            "Map".to_string(),
61            vec![FSharpType::FsString, FSharpType::Int],
62        );
63        assert_eq!(b().emit_type(&ty), "Map<string, int>");
64    }
65    #[test]
66    pub(super) fn test_expr_lit() {
67        assert_eq!(b().emit_expr(&FSharpExpr::Lit("42".to_string()), 0), "42");
68    }
69    #[test]
70    pub(super) fn test_expr_var() {
71        assert_eq!(b().emit_expr(&FSharpExpr::Var("x".to_string()), 0), "x");
72    }
73    #[test]
74    pub(super) fn test_expr_lambda() {
75        let e = FSharpExpr::Lambda("x".to_string(), Box::new(FSharpExpr::Var("x".to_string())));
76        assert_eq!(b().emit_expr(&e, 0), "fun x -> x");
77    }
78    #[test]
79    pub(super) fn test_expr_binop() {
80        let e = FSharpExpr::BinOp(
81            "+".to_string(),
82            Box::new(FSharpExpr::Lit("1".to_string())),
83            Box::new(FSharpExpr::Lit("2".to_string())),
84        );
85        assert_eq!(b().emit_expr(&e, 0), "1 + 2");
86    }
87    #[test]
88    pub(super) fn test_expr_list() {
89        let e = FSharpExpr::FsList(vec![
90            FSharpExpr::Lit("1".to_string()),
91            FSharpExpr::Lit("2".to_string()),
92        ]);
93        assert_eq!(b().emit_expr(&e, 0), "[1; 2]");
94    }
95    #[test]
96    pub(super) fn test_expr_empty_list() {
97        assert_eq!(b().emit_expr(&FSharpExpr::FsList(vec![]), 0), "[]");
98    }
99    #[test]
100    pub(super) fn test_expr_array() {
101        let e = FSharpExpr::FsArray(vec![FSharpExpr::Lit("3".to_string())]);
102        assert_eq!(b().emit_expr(&e, 0), "[|3|]");
103    }
104    #[test]
105    pub(super) fn test_expr_tuple() {
106        let e = FSharpExpr::Tuple(vec![
107            FSharpExpr::Lit("1".to_string()),
108            FSharpExpr::Lit("2".to_string()),
109        ]);
110        assert_eq!(b().emit_expr(&e, 0), "(1, 2)");
111    }
112    #[test]
113    pub(super) fn test_expr_record() {
114        let e = FSharpExpr::Record(vec![
115            ("x".to_string(), FSharpExpr::Lit("0".to_string())),
116            ("y".to_string(), FSharpExpr::Lit("1".to_string())),
117        ]);
118        let s = b().emit_expr(&e, 0);
119        assert!(s.contains("x = 0"), "got: {}", s);
120        assert!(s.contains("y = 1"), "got: {}", s);
121    }
122    #[test]
123    pub(super) fn test_expr_pipe() {
124        let e = FSharpExpr::Pipe(
125            Box::new(FSharpExpr::Var("xs".to_string())),
126            Box::new(FSharpExpr::Var("List.sort".to_string())),
127        );
128        let s = b().emit_expr(&e, 0);
129        assert!(s.contains("|>"), "pipe missing: {}", s);
130        assert!(s.contains("xs"), "lhs missing: {}", s);
131    }
132    #[test]
133    pub(super) fn test_expr_match() {
134        let e = FSharpExpr::Match(
135            Box::new(FSharpExpr::Var("x".to_string())),
136            vec![
137                (
138                    FSharpPattern::Lit("0".to_string()),
139                    FSharpExpr::Lit("\"zero\"".to_string()),
140                ),
141                (
142                    FSharpPattern::Wildcard,
143                    FSharpExpr::Lit("\"nonzero\"".to_string()),
144                ),
145            ],
146        );
147        let s = b().emit_expr(&e, 0);
148        assert!(s.contains("match x with"), "got: {}", s);
149        assert!(s.contains("| 0 -> "), "got: {}", s);
150        assert!(s.contains("| _ -> "), "got: {}", s);
151    }
152    #[test]
153    pub(super) fn test_expr_if() {
154        let e = FSharpExpr::If(
155            Box::new(FSharpExpr::Lit("true".to_string())),
156            Box::new(FSharpExpr::Lit("1".to_string())),
157            Box::new(FSharpExpr::Lit("0".to_string())),
158        );
159        let s = b().emit_expr(&e, 0);
160        assert!(s.contains("if true then"), "got: {}", s);
161        assert!(s.contains("else"), "got: {}", s);
162    }
163    #[test]
164    pub(super) fn test_pattern_ctor() {
165        let p = FSharpPattern::Ctor(
166            "Some".to_string(),
167            vec![FSharpPattern::Var("v".to_string())],
168        );
169        assert_eq!(b().emit_pattern(&p), "Some(v)");
170    }
171    #[test]
172    pub(super) fn test_pattern_cons() {
173        let p = FSharpPattern::Cons(
174            Box::new(FSharpPattern::Var("h".to_string())),
175            Box::new(FSharpPattern::Var("t".to_string())),
176        );
177        assert_eq!(b().emit_pattern(&p), "h :: t");
178    }
179    #[test]
180    pub(super) fn test_emit_record() {
181        let rec = FSharpRecord {
182            name: "Point".to_string(),
183            type_params: vec![],
184            fields: vec![
185                ("x".to_string(), FSharpType::Float),
186                ("y".to_string(), FSharpType::Float),
187            ],
188            doc: None,
189        };
190        let s = b().emit_record(&rec);
191        assert!(s.contains("type Point ="), "got: {}", s);
192        assert!(s.contains("x: float"), "got: {}", s);
193        assert!(s.contains("y: float"), "got: {}", s);
194    }
195    #[test]
196    pub(super) fn test_emit_union() {
197        let union = FSharpUnion {
198            name: "Shape".to_string(),
199            type_params: vec![],
200            cases: vec![
201                FSharpUnionCase {
202                    name: "Circle".to_string(),
203                    fields: vec![FSharpType::Float],
204                },
205                FSharpUnionCase {
206                    name: "Rect".to_string(),
207                    fields: vec![FSharpType::Float, FSharpType::Float],
208                },
209                FSharpUnionCase {
210                    name: "Point".to_string(),
211                    fields: vec![],
212                },
213            ],
214            doc: None,
215        };
216        let s = b().emit_union(&union);
217        assert!(s.contains("type Shape ="), "got: {}", s);
218        assert!(s.contains("| Circle of float"), "got: {}", s);
219        assert!(s.contains("| Rect of float * float"), "got: {}", s);
220        assert!(s.contains("| Point"), "got: {}", s);
221    }
222    #[test]
223    pub(super) fn test_emit_function() {
224        let func = FSharpFunction {
225            name: "add".to_string(),
226            is_recursive: false,
227            is_inline: false,
228            type_params: vec![],
229            params: vec![
230                ("a".to_string(), Some(FSharpType::Int)),
231                ("b".to_string(), Some(FSharpType::Int)),
232            ],
233            return_type: Some(FSharpType::Int),
234            body: FSharpExpr::BinOp(
235                "+".to_string(),
236                Box::new(FSharpExpr::Var("a".to_string())),
237                Box::new(FSharpExpr::Var("b".to_string())),
238            ),
239            doc: None,
240        };
241        let s = b().emit_function(&func);
242        assert!(s.contains("let add"), "got: {}", s);
243        assert!(s.contains("(a: int)"), "got: {}", s);
244        assert!(s.contains(": int"), "got: {}", s);
245        assert!(s.contains("a + b"), "got: {}", s);
246    }
247    #[test]
248    pub(super) fn test_emit_module() {
249        let module = FSharpModule {
250            name: "OxiLean.Math".to_string(),
251            auto_open: false,
252            records: vec![],
253            unions: vec![],
254            functions: vec![FSharpFunction {
255                name: "square".to_string(),
256                is_recursive: false,
257                is_inline: false,
258                type_params: vec![],
259                params: vec![("x".to_string(), Some(FSharpType::Int))],
260                return_type: Some(FSharpType::Int),
261                body: FSharpExpr::BinOp(
262                    "*".to_string(),
263                    Box::new(FSharpExpr::Var("x".to_string())),
264                    Box::new(FSharpExpr::Var("x".to_string())),
265                ),
266                doc: None,
267            }],
268            opens: vec!["System".to_string()],
269        };
270        let s = b().emit_module(&module);
271        assert!(s.contains("module OxiLean.Math"), "got: {}", s);
272        assert!(s.contains("open System"), "got: {}", s);
273        assert!(s.contains("let square"), "got: {}", s);
274        assert!(s.contains("x * x"), "got: {}", s);
275    }
276    #[test]
277    pub(super) fn test_recursive_function() {
278        let func = FSharpFunction {
279            name: "factorial".to_string(),
280            is_recursive: true,
281            is_inline: false,
282            type_params: vec![],
283            params: vec![("n".to_string(), Some(FSharpType::Int))],
284            return_type: Some(FSharpType::Int),
285            body: FSharpExpr::Match(
286                Box::new(FSharpExpr::Var("n".to_string())),
287                vec![
288                    (
289                        FSharpPattern::Lit("0".to_string()),
290                        FSharpExpr::Lit("1".to_string()),
291                    ),
292                    (
293                        FSharpPattern::Var("k".to_string()),
294                        FSharpExpr::BinOp(
295                            "*".to_string(),
296                            Box::new(FSharpExpr::Var("k".to_string())),
297                            Box::new(FSharpExpr::App(
298                                Box::new(FSharpExpr::Var("factorial".to_string())),
299                                Box::new(FSharpExpr::BinOp(
300                                    "-".to_string(),
301                                    Box::new(FSharpExpr::Var("k".to_string())),
302                                    Box::new(FSharpExpr::Lit("1".to_string())),
303                                )),
304                            )),
305                        ),
306                    ),
307                ],
308            ),
309            doc: None,
310        };
311        let s = b().emit_function(&func);
312        assert!(s.contains("let rec factorial"), "got: {}", s);
313        assert!(s.contains("match n with"), "got: {}", s);
314    }
315    #[test]
316    pub(super) fn test_expr_ann() {
317        let e = FSharpExpr::Ann(Box::new(FSharpExpr::Lit("42".to_string())), FSharpType::Int);
318        assert_eq!(b().emit_expr(&e, 0), "(42 : int)");
319    }
320    #[test]
321    pub(super) fn test_type_var() {
322        assert_eq!(b().emit_type(&FSharpType::TypeVar("a".to_string())), "'a");
323    }
324}
325/// Build a computation expression: `async { body }`.
326#[allow(dead_code)]
327pub fn computation_expr(kind: ComputationExprKind, stmts: Vec<FSharpExpr>) -> FSharpExpr {
328    let _kind_str = kind.to_string();
329    FSharpExpr::Raw(format!(
330        "{} {{\n{}\n}}",
331        _kind_str,
332        stmts
333            .iter()
334            .map(|s| format!("    {}", {
335                let b = FSharpBackend::new();
336                b.emit_expr(s, 1)
337            }))
338            .collect::<Vec<_>>()
339            .join("\n")
340    ))
341}
342/// Build a float with a unit of measure annotation: `42.0<kg>`.
343#[allow(dead_code)]
344pub fn float_with_measure(value: f64, measure: &str) -> FSharpExpr {
345    FSharpExpr::Lit(format!("{}<{}>", value, measure))
346}
347/// Build a simple variable reference.
348#[allow(dead_code)]
349pub fn fvar(name: impl Into<String>) -> FSharpExpr {
350    FSharpExpr::Var(name.into())
351}
352/// Build a literal expression.
353#[allow(dead_code)]
354pub fn flit(s: impl Into<String>) -> FSharpExpr {
355    FSharpExpr::Lit(s.into())
356}
357/// Build a function application: `f x`.
358#[allow(dead_code)]
359pub fn fapp(f: FSharpExpr, x: FSharpExpr) -> FSharpExpr {
360    FSharpExpr::App(Box::new(f), Box::new(x))
361}
362/// Build a multi-argument application: `f x1 x2 ...`.
363#[allow(dead_code)]
364pub fn fapp_multi(f: FSharpExpr, args: Vec<FSharpExpr>) -> FSharpExpr {
365    args.into_iter().fold(f, fapp)
366}
367/// Build a lambda expression.
368#[allow(dead_code)]
369pub fn flam(param: impl Into<String>, body: FSharpExpr) -> FSharpExpr {
370    FSharpExpr::Lambda(param.into(), Box::new(body))
371}
372/// Build a multi-parameter lambda.
373#[allow(dead_code)]
374pub fn flam_multi(params: Vec<impl Into<String>>, body: FSharpExpr) -> FSharpExpr {
375    FSharpExpr::MultiLambda(
376        params.into_iter().map(|p| p.into()).collect(),
377        Box::new(body),
378    )
379}
380/// Build a `let` binding.
381#[allow(dead_code)]
382pub fn flet(name: impl Into<String>, val: FSharpExpr, body: FSharpExpr) -> FSharpExpr {
383    FSharpExpr::Let(name.into(), Box::new(val), Box::new(body))
384}
385/// Build a `let rec` binding.
386#[allow(dead_code)]
387pub fn fletrec(name: impl Into<String>, val: FSharpExpr, body: FSharpExpr) -> FSharpExpr {
388    FSharpExpr::LetRec(name.into(), Box::new(val), Box::new(body))
389}
390/// Build an `if-then-else` expression.
391#[allow(dead_code)]
392pub fn fif(cond: FSharpExpr, then_e: FSharpExpr, else_e: FSharpExpr) -> FSharpExpr {
393    FSharpExpr::If(Box::new(cond), Box::new(then_e), Box::new(else_e))
394}
395/// Build a binary operation.
396#[allow(dead_code)]
397pub fn fbinop(op: impl Into<String>, lhs: FSharpExpr, rhs: FSharpExpr) -> FSharpExpr {
398    FSharpExpr::BinOp(op.into(), Box::new(lhs), Box::new(rhs))
399}
400/// Build a unary operation.
401#[allow(dead_code)]
402pub fn funary(op: impl Into<String>, operand: FSharpExpr) -> FSharpExpr {
403    FSharpExpr::UnaryOp(op.into(), Box::new(operand))
404}
405/// Build a pipe expression: `lhs |> rhs`.
406#[allow(dead_code)]
407pub fn fpipe(lhs: FSharpExpr, rhs: FSharpExpr) -> FSharpExpr {
408    FSharpExpr::Pipe(Box::new(lhs), Box::new(rhs))
409}
410/// Build a pipe chain: `e |> f1 |> f2 |> ...`.
411#[allow(dead_code)]
412pub fn fpipe_chain(init: FSharpExpr, funcs: Vec<FSharpExpr>) -> FSharpExpr {
413    funcs.into_iter().fold(init, fpipe)
414}
415/// Build a constructor application.
416#[allow(dead_code)]
417pub fn fctor(name: impl Into<String>, args: Vec<FSharpExpr>) -> FSharpExpr {
418    FSharpExpr::Ctor(name.into(), args)
419}
420/// Build a `Some x` expression.
421#[allow(dead_code)]
422pub fn fsome(x: FSharpExpr) -> FSharpExpr {
423    fctor("Some", vec![x])
424}
425/// Build a `None` expression.
426#[allow(dead_code)]
427pub fn fnone() -> FSharpExpr {
428    fvar("None")
429}
430/// Build an `Ok x` expression.
431#[allow(dead_code)]
432pub fn fok(x: FSharpExpr) -> FSharpExpr {
433    fctor("Ok", vec![x])
434}
435/// Build an `Error e` expression.
436#[allow(dead_code)]
437pub fn ferror(e: FSharpExpr) -> FSharpExpr {
438    fctor("Error", vec![e])
439}
440/// Build a sequence operator: `e1; e2`.
441#[allow(dead_code)]
442pub fn fseq(e1: FSharpExpr, e2: FSharpExpr) -> FSharpExpr {
443    FSharpExpr::Seq(Box::new(e1), Box::new(e2))
444}
445/// Build a field access: `expr.Field`.
446#[allow(dead_code)]
447pub fn ffield(base: FSharpExpr, field: impl Into<String>) -> FSharpExpr {
448    FSharpExpr::FieldAccess(Box::new(base), field.into())
449}
450/// Build a type-annotated expression: `(expr : ty)`.
451#[allow(dead_code)]
452pub fn fann(expr: FSharpExpr, ty: FSharpType) -> FSharpExpr {
453    FSharpExpr::Ann(Box::new(expr), ty)
454}
455/// Build an integer literal.
456#[allow(dead_code)]
457pub fn fint(n: i64) -> FSharpExpr {
458    FSharpExpr::Lit(n.to_string())
459}
460/// Build a float literal.
461#[allow(dead_code)]
462pub fn ffloat(x: f64) -> FSharpExpr {
463    FSharpExpr::Lit(format!("{}", x))
464}
465/// Build a boolean literal.
466#[allow(dead_code)]
467pub fn fbool(b: bool) -> FSharpExpr {
468    FSharpExpr::Lit(if b { "true" } else { "false" }.to_string())
469}
470/// Build a string literal (quoted).
471#[allow(dead_code)]
472pub fn fstring(s: impl Into<String>) -> FSharpExpr {
473    FSharpExpr::Lit(format!("\"{}\"", s.into()))
474}
475/// Build the unit value `()`.
476#[allow(dead_code)]
477pub fn funit() -> FSharpExpr {
478    FSharpExpr::Lit("()".to_string())
479}
480/// Build a wildcard pattern `_`.
481#[allow(dead_code)]
482pub fn pwild() -> FSharpPattern {
483    FSharpPattern::Wildcard
484}
485/// Build a variable pattern.
486#[allow(dead_code)]
487pub fn pvar(name: impl Into<String>) -> FSharpPattern {
488    FSharpPattern::Var(name.into())
489}
490/// Build a literal pattern.
491#[allow(dead_code)]
492pub fn plit(s: impl Into<String>) -> FSharpPattern {
493    FSharpPattern::Lit(s.into())
494}
495/// Build a `Some v` pattern.
496#[allow(dead_code)]
497pub fn psome(inner: FSharpPattern) -> FSharpPattern {
498    FSharpPattern::Ctor("Some".to_string(), vec![inner])
499}
500/// Build a `None` pattern.
501#[allow(dead_code)]
502pub fn pnone() -> FSharpPattern {
503    FSharpPattern::Ctor("None".to_string(), vec![])
504}
505/// Build a `Ok v` pattern.
506#[allow(dead_code)]
507pub fn pok(inner: FSharpPattern) -> FSharpPattern {
508    FSharpPattern::Ctor("Ok".to_string(), vec![inner])
509}
510/// Build an `Error e` pattern.
511#[allow(dead_code)]
512pub fn perror(inner: FSharpPattern) -> FSharpPattern {
513    FSharpPattern::Ctor("Error".to_string(), vec![inner])
514}
515/// Build a tuple pattern `(a, b)`.
516#[allow(dead_code)]
517pub fn ptuple(pats: Vec<FSharpPattern>) -> FSharpPattern {
518    FSharpPattern::Tuple(pats)
519}
520/// Build a cons pattern `h :: t`.
521#[allow(dead_code)]
522pub fn pcons(head: FSharpPattern, tail: FSharpPattern) -> FSharpPattern {
523    FSharpPattern::Cons(Box::new(head), Box::new(tail))
524}
525/// Build an `as` pattern `p as name`.
526#[allow(dead_code)]
527pub fn pas(pat: FSharpPattern, name: impl Into<String>) -> FSharpPattern {
528    FSharpPattern::As(Box::new(pat), name.into())
529}
530/// Build a guarded pattern `p when expr`.
531#[allow(dead_code)]
532pub fn pwhen(pat: FSharpPattern, guard: FSharpExpr) -> FSharpPattern {
533    FSharpPattern::When(Box::new(pat), Box::new(guard))
534}
535/// Build `int list`.
536#[allow(dead_code)]
537pub fn fs_int_list() -> FSharpType {
538    FSharpType::List(Box::new(FSharpType::Int))
539}
540/// Build `string list`.
541#[allow(dead_code)]
542pub fn fs_string_list() -> FSharpType {
543    FSharpType::List(Box::new(FSharpType::FsString))
544}
545/// Build `int option`.
546#[allow(dead_code)]
547pub fn fs_int_option() -> FSharpType {
548    FSharpType::Option(Box::new(FSharpType::Int))
549}
550/// Build `T array`.
551#[allow(dead_code)]
552pub fn fs_array(ty: FSharpType) -> FSharpType {
553    FSharpType::Array(Box::new(ty))
554}
555/// Build a generic `Map<K, V>` type.
556#[allow(dead_code)]
557pub fn fs_map(key: FSharpType, value: FSharpType) -> FSharpType {
558    FSharpType::Generic("Map".to_string(), vec![key, value])
559}
560/// Build a generic `Set<T>` type.
561#[allow(dead_code)]
562pub fn fs_set(elem: FSharpType) -> FSharpType {
563    FSharpType::Generic("Set".to_string(), vec![elem])
564}
565/// Build `Result<T, E>`.
566#[allow(dead_code)]
567pub fn fs_result(ok: FSharpType, err: FSharpType) -> FSharpType {
568    FSharpType::Result(Box::new(ok), Box::new(err))
569}
570/// Build `T -> U`.
571#[allow(dead_code)]
572pub fn fs_fun(from: FSharpType, to: FSharpType) -> FSharpType {
573    FSharpType::Fun(Box::new(from), Box::new(to))
574}
575/// Build `T1 * T2 * T3` tuple type.
576#[allow(dead_code)]
577pub fn fs_tuple(tys: Vec<FSharpType>) -> FSharpType {
578    FSharpType::Tuple(tys)
579}
580/// Collect simple metrics from an `FSharpModule`.
581#[allow(dead_code)]
582pub fn collect_module_metrics(module: &FSharpModule) -> FSharpModuleMetrics {
583    let mut metrics = FSharpModuleMetrics::default();
584    metrics.type_count = module.records.len() + module.unions.len();
585    metrics.function_count = module.functions.len();
586    for f in &module.functions {
587        if f.is_recursive {
588            metrics.recursive_count += 1;
589        }
590        if f.is_inline {
591            metrics.inline_count += 1;
592        }
593    }
594    metrics.estimated_lines = 5
595        + module.opens.len()
596        + module.records.len() * 4
597        + module.unions.len() * 5
598        + module.functions.len() * 6;
599    metrics
600}
601/// Build an `async { return! x }` expression.
602#[allow(dead_code)]
603pub fn async_return(x: FSharpExpr) -> FSharpExpr {
604    FSharpExpr::Raw(format!(
605        "async {{ return! {} }}",
606        FSharpBackend::new().emit_expr(&x, 0)
607    ))
608}
609/// Build an `async { let! v = comp in body }` expression.
610#[allow(dead_code)]
611pub fn async_let_bang(name: impl Into<String>, comp: FSharpExpr, body: FSharpExpr) -> FSharpExpr {
612    let b = FSharpBackend::new();
613    FSharpExpr::Raw(format!(
614        "async {{\n    let! {} = {}\n    return! {}\n}}",
615        name.into(),
616        b.emit_expr(&comp, 1),
617        b.emit_expr(&body, 1)
618    ))
619}
620/// Build an `Async.map f computation` expression.
621#[allow(dead_code)]
622pub fn async_map(f: FSharpExpr, comp: FSharpExpr) -> FSharpExpr {
623    fapp_multi(fvar("Async.map"), vec![f, comp])
624}
625/// Build an `Async.bind f computation` expression.
626#[allow(dead_code)]
627pub fn async_bind(f: FSharpExpr, comp: FSharpExpr) -> FSharpExpr {
628    fapp_multi(fvar("Async.bind"), vec![f, comp])
629}
630#[cfg(test)]
631mod extended_fsharp_tests {
632    use super::*;
633    #[test]
634    pub(super) fn test_attribute_simple() {
635        let a = FSharpAttribute::simple("Serializable");
636        assert_eq!(a.emit(), "[<Serializable>]");
637    }
638    #[test]
639    pub(super) fn test_attribute_with_args() {
640        let a = FSharpAttribute::with_args("DllImport", vec!["\"mylib.dll\"".to_string()]);
641        assert!(a.emit().contains("DllImport"));
642        assert!(a.emit().contains("mylib.dll"));
643    }
644    #[test]
645    pub(super) fn test_type_alias_emit() {
646        let alias = FSharpTypeAlias {
647            name: "IntList".to_string(),
648            type_params: vec![],
649            aliased_type: FSharpType::List(Box::new(FSharpType::Int)),
650            doc: None,
651        };
652        let s = alias.emit();
653        assert!(s.contains("type IntList = "));
654        assert!(s.contains("int list"));
655    }
656    #[test]
657    pub(super) fn test_type_alias_generic() {
658        let alias = FSharpTypeAlias {
659            name: "Pair".to_string(),
660            type_params: vec!["'a".to_string(), "'b".to_string()],
661            aliased_type: FSharpType::Tuple(vec![
662                FSharpType::TypeVar("a".to_string()),
663                FSharpType::TypeVar("b".to_string()),
664            ]),
665            doc: None,
666        };
667        let s = alias.emit();
668        assert!(s.contains("type Pair<"));
669        assert!(s.contains("'a * 'b"));
670    }
671    #[test]
672    pub(super) fn test_fvar_flit() {
673        let b = FSharpBackend::new();
674        assert_eq!(b.emit_expr(&fvar("x"), 0), "x");
675        assert_eq!(b.emit_expr(&flit("42"), 0), "42");
676    }
677    #[test]
678    pub(super) fn test_fapp_multi() {
679        let b = FSharpBackend::new();
680        let e = fapp_multi(fvar("f"), vec![fvar("x"), fvar("y")]);
681        let s = b.emit_expr(&e, 0);
682        assert!(s.contains("f"));
683        assert!(s.contains("x"));
684        assert!(s.contains("y"));
685    }
686    #[test]
687    pub(super) fn test_fpipe_chain() {
688        let b = FSharpBackend::new();
689        let e = fpipe_chain(fvar("xs"), vec![fvar("List.sort"), fvar("List.rev")]);
690        let s = b.emit_expr(&e, 0);
691        assert!(s.contains("|>"));
692        assert!(s.contains("xs"));
693    }
694    #[test]
695    pub(super) fn test_fsome_fnone() {
696        let b = FSharpBackend::new();
697        let s = b.emit_expr(&fsome(fvar("x")), 0);
698        assert!(s.contains("Some"));
699        let n = b.emit_expr(&fnone(), 0);
700        assert_eq!(n, "None");
701    }
702    #[test]
703    pub(super) fn test_fok_ferror() {
704        let b = FSharpBackend::new();
705        let ok = b.emit_expr(&fok(fvar("v")), 0);
706        assert!(ok.contains("Ok"));
707        let err = b.emit_expr(&ferror(fvar("e")), 0);
708        assert!(err.contains("Error"));
709    }
710    #[test]
711    pub(super) fn test_fint_ffloat() {
712        let b = FSharpBackend::new();
713        assert_eq!(b.emit_expr(&fint(42), 0), "42");
714        assert!(b.emit_expr(&ffloat(3.14), 0).contains("3.14"));
715    }
716    #[test]
717    pub(super) fn test_fbool_fstring_funit() {
718        let b = FSharpBackend::new();
719        assert_eq!(b.emit_expr(&fbool(true), 0), "true");
720        assert_eq!(b.emit_expr(&fbool(false), 0), "false");
721        assert!(b.emit_expr(&fstring("hello"), 0).contains("hello"));
722        assert_eq!(b.emit_expr(&funit(), 0), "()");
723    }
724    #[test]
725    pub(super) fn test_pattern_psome_pnone() {
726        let b = FSharpBackend::new();
727        let p = psome(pvar("v"));
728        assert!(b.emit_pattern(&p).contains("Some"));
729        let n = pnone();
730        assert!(b.emit_pattern(&n).contains("None"));
731    }
732    #[test]
733    pub(super) fn test_pattern_pok_perror() {
734        let b = FSharpBackend::new();
735        let p = pok(pvar("x"));
736        assert!(b.emit_pattern(&p).contains("Ok"));
737        let e = perror(pvar("err"));
738        assert!(b.emit_pattern(&e).contains("Error"));
739    }
740    #[test]
741    pub(super) fn test_pattern_ptuple() {
742        let b = FSharpBackend::new();
743        let p = ptuple(vec![pvar("a"), pvar("b")]);
744        let s = b.emit_pattern(&p);
745        assert!(s.contains("a"));
746        assert!(s.contains("b"));
747        assert!(s.contains("("));
748    }
749    #[test]
750    pub(super) fn test_pattern_pcons() {
751        let b = FSharpBackend::new();
752        let p = pcons(pvar("h"), pvar("t"));
753        let s = b.emit_pattern(&p);
754        assert!(s.contains("h :: t"));
755    }
756    #[test]
757    pub(super) fn test_fs_type_constructors() {
758        assert!(format!("{}", fs_int_list()).contains("int list"));
759        assert!(format!("{}", fs_string_list()).contains("string list"));
760        assert!(format!("{}", fs_int_option()).contains("int option"));
761        assert!(format!("{}", fs_array(FSharpType::Float)).contains("array"));
762        assert!(format!("{}", fs_map(FSharpType::FsString, FSharpType::Int)).contains("Map"));
763        assert!(format!("{}", fs_set(FSharpType::Int)).contains("Set"));
764        assert!(format!("{}", fs_result(FSharpType::Int, FSharpType::FsString)).contains("Result"));
765    }
766    #[test]
767    pub(super) fn test_fs_fun_type() {
768        let ty = fs_fun(FSharpType::Int, FSharpType::Bool);
769        assert!(format!("{}", ty).contains("->"));
770    }
771    #[test]
772    pub(super) fn test_fs_tuple_type() {
773        let ty = fs_tuple(vec![FSharpType::Int, FSharpType::Float, FSharpType::Bool]);
774        let s = format!("{}", ty);
775        assert!(s.contains("int"));
776        assert!(s.contains("float"));
777        assert!(s.contains("bool"));
778        assert!(s.contains("*"));
779    }
780    #[test]
781    pub(super) fn test_snippets() {
782        assert!(FSharpSnippets::option_map().contains("Some"));
783        assert!(FSharpSnippets::option_bind().contains("None"));
784        assert!(FSharpSnippets::result_map().contains("Ok"));
785        assert!(FSharpSnippets::list_fold().contains("foldLeft"));
786        assert!(FSharpSnippets::memoize().contains("Dictionary"));
787        assert!(FSharpSnippets::fix_point().contains("fix"));
788    }
789    #[test]
790    pub(super) fn test_numeric_helpers() {
791        let b = FSharpBackend::new();
792        let clamp = FSharpNumericHelpers::clamp();
793        let s = b.emit_function(&clamp);
794        assert!(s.contains("clamp"));
795        assert!(s.contains("inline"));
796        let sq = FSharpNumericHelpers::square();
797        let s2 = b.emit_function(&sq);
798        assert!(s2.contains("square"));
799        let pow = FSharpNumericHelpers::pow_int();
800        let s3 = b.emit_function(&pow);
801        assert!(s3.contains("powInt"));
802        assert!(s3.contains("rec"));
803        let gcd = FSharpNumericHelpers::gcd();
804        let s4 = b.emit_function(&gcd);
805        assert!(s4.contains("gcd"));
806    }
807    #[test]
808    pub(super) fn test_module_builder() {
809        let s = FSharpModuleBuilder::new("MyLib")
810            .open("System")
811            .function(FSharpNumericHelpers::square())
812            .emit();
813        assert!(s.contains("module MyLib"));
814        assert!(s.contains("open System"));
815        assert!(s.contains("square"));
816    }
817    #[test]
818    pub(super) fn test_function_builder() {
819        let f = FSharpFunctionBuilder::new("double")
820            .param("x", Some(FSharpType::Int))
821            .returns(FSharpType::Int)
822            .body(fbinop("*", flit("2"), fvar("x")))
823            .doc("Double an integer.")
824            .build();
825        let b = FSharpBackend::new();
826        let s = b.emit_function(&f);
827        assert!(s.contains("double"));
828        assert!(s.contains("2 * x"));
829    }
830    #[test]
831    pub(super) fn test_collect_module_metrics() {
832        let module = FSharpModule {
833            name: "Test".to_string(),
834            auto_open: false,
835            records: vec![FSharpRecord {
836                name: "Point".to_string(),
837                type_params: vec![],
838                fields: vec![("x".to_string(), FSharpType::Float)],
839                doc: None,
840            }],
841            unions: vec![],
842            functions: vec![FSharpNumericHelpers::square(), FSharpNumericHelpers::gcd()],
843            opens: vec!["System".to_string()],
844        };
845        let m = collect_module_metrics(&module);
846        assert_eq!(m.type_count, 1);
847        assert_eq!(m.function_count, 2);
848        assert_eq!(m.recursive_count, 1);
849        assert_eq!(m.inline_count, 1);
850        assert!(m.estimated_lines > 0);
851    }
852    #[test]
853    pub(super) fn test_stdlib_id() {
854        let b = FSharpBackend::new();
855        let f = FSharpStdLib::id_function();
856        let s = b.emit_function(&f);
857        assert!(s.contains("id"));
858    }
859    #[test]
860    pub(super) fn test_stdlib_flip() {
861        let b = FSharpBackend::new();
862        let f = FSharpStdLib::flip_function();
863        let s = b.emit_function(&f);
864        assert!(s.contains("flip"));
865    }
866    #[test]
867    pub(super) fn test_stdlib_const() {
868        let b = FSharpBackend::new();
869        let f = FSharpStdLib::const_function();
870        let s = b.emit_function(&f);
871        assert!(s.contains("konst"));
872    }
873    #[test]
874    pub(super) fn test_stdlib_foldl() {
875        let b = FSharpBackend::new();
876        let f = FSharpStdLib::foldl_function();
877        let s = b.emit_function(&f);
878        assert!(s.contains("foldl"));
879        assert!(s.contains("rec"));
880    }
881    #[test]
882    pub(super) fn test_stdlib_filter() {
883        let b = FSharpBackend::new();
884        let f = FSharpStdLib::filter_function();
885        let s = b.emit_function(&f);
886        assert!(s.contains("filterList"));
887    }
888    #[test]
889    pub(super) fn test_stdlib_zip_with() {
890        let b = FSharpBackend::new();
891        let f = FSharpStdLib::zip_with_function();
892        let s = b.emit_function(&f);
893        assert!(s.contains("zipWith"));
894    }
895    #[test]
896    pub(super) fn test_union_case_named() {
897        let c = FSharpUnionCaseNamed {
898            name: "Circle".to_string(),
899            named_fields: vec![("radius".to_string(), FSharpType::Float)],
900        };
901        let s = c.emit();
902        assert!(s.contains("Circle"));
903        assert!(s.contains("radius: float"));
904    }
905    #[test]
906    pub(super) fn test_measure_decl() {
907        let m = FSharpMeasure {
908            name: "kg".to_string(),
909            abbrev: None,
910        };
911        let s = m.emit();
912        assert!(s.contains("[<Measure>]"));
913        assert!(s.contains("type kg"));
914    }
915    #[test]
916    pub(super) fn test_float_with_measure() {
917        let b = FSharpBackend::new();
918        let e = float_with_measure(42.0, "kg");
919        let s = b.emit_expr(&e, 0);
920        assert!(s.contains("kg"));
921    }
922    #[test]
923    pub(super) fn test_computation_expr() {
924        let e = computation_expr(ComputationExprKind::Async, vec![fvar("doSomething")]);
925        let b = FSharpBackend::new();
926        let s = b.emit_expr(&e, 0);
927        assert!(s.contains("async"));
928        assert!(s.contains("doSomething"));
929    }
930    #[test]
931    pub(super) fn test_mutual_group_emit() {
932        let f1 = FSharpFunctionBuilder::new("isEven")
933            .recursive()
934            .param("n", Some(FSharpType::Int))
935            .returns(FSharpType::Bool)
936            .body(fif(
937                fbinop("=", fvar("n"), fint(0)),
938                fbool(true),
939                fapp(fvar("isOdd"), fbinop("-", fvar("n"), fint(1))),
940            ))
941            .build();
942        let f2 = FSharpFunctionBuilder::new("isOdd")
943            .recursive()
944            .param("n", Some(FSharpType::Int))
945            .returns(FSharpType::Bool)
946            .body(fif(
947                fbinop("=", fvar("n"), fint(0)),
948                fbool(false),
949                fapp(fvar("isEven"), fbinop("-", fvar("n"), fint(1))),
950            ))
951            .build();
952        let group = FSharpMutualGroup {
953            functions: vec![f1, f2],
954        };
955        let b = FSharpBackend::new();
956        let s = group.emit(&b);
957        assert!(s.contains("isEven"));
958        assert!(s.contains("isOdd"));
959    }
960    #[test]
961    pub(super) fn test_interface_emit() {
962        let iface = FSharpInterface {
963            name: "IComparable".to_string(),
964            type_params: vec!["'T".to_string()],
965            methods: vec![(
966                "CompareTo".to_string(),
967                vec![("other".to_string(), FSharpType::TypeVar("T".to_string()))],
968                FSharpType::Int,
969            )],
970            properties: vec![],
971            doc: None,
972        };
973        let s = iface.emit();
974        assert!(s.contains("IComparable"));
975        assert!(s.contains("abstract member CompareTo"));
976    }
977    #[test]
978    pub(super) fn test_active_pattern_total() {
979        let ap = FSharpActivePattern {
980            kind: ActivePatternKind::Total(vec!["Even".to_string(), "Odd".to_string()]),
981            params: vec!["n".to_string()],
982            body: fif(
983                fbinop("=", fbinop("%", fvar("n"), fint(2)), fint(0)),
984                fvar("Even"),
985                fvar("Odd"),
986            ),
987        };
988        let b = FSharpBackend::new();
989        let s = ap.emit(&b);
990        assert!(s.contains("Even|Odd"));
991        assert!(s.contains("let"));
992    }
993    #[test]
994    pub(super) fn test_active_pattern_partial() {
995        let ap = FSharpActivePattern {
996            kind: ActivePatternKind::Partial("IsPositive".to_string()),
997            params: vec!["x".to_string()],
998            body: fif(fbinop(">", fvar("x"), fint(0)), fsome(fvar("x")), fnone()),
999        };
1000        let b = FSharpBackend::new();
1001        let s = ap.emit(&b);
1002        assert!(s.contains("IsPositive"));
1003        assert!(s.contains("|_|"));
1004    }
1005    #[test]
1006    pub(super) fn test_async_return() {
1007        let b = FSharpBackend::new();
1008        let e = async_return(fvar("result"));
1009        let s = b.emit_expr(&e, 0);
1010        assert!(s.contains("async"));
1011        assert!(s.contains("result"));
1012    }
1013    #[test]
1014    pub(super) fn test_async_map_bind() {
1015        let b = FSharpBackend::new();
1016        let e = async_map(fvar("f"), fvar("comp"));
1017        let s = b.emit_expr(&e, 0);
1018        assert!(s.contains("Async.map"));
1019        let e2 = async_bind(fvar("f"), fvar("comp"));
1020        let s2 = b.emit_expr(&e2, 0);
1021        assert!(s2.contains("Async.bind"));
1022    }
1023    #[test]
1024    pub(super) fn test_auto_open_module() {
1025        let s = FSharpModuleBuilder::new("Operators").auto_open().emit();
1026        assert!(s.contains("[<AutoOpen>]"));
1027        assert!(s.contains("module Operators"));
1028    }
1029}