Skip to main content

oxilean_codegen/dart_backend/
types.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use crate::lcnf::*;
6use std::collections::{HashMap, HashSet};
7
8use super::functions::DART_KEYWORDS;
9use super::functions::*;
10
11/// Dart expression AST.
12#[derive(Debug, Clone, PartialEq)]
13pub enum DartExpr {
14    /// Literal value
15    Lit(DartLit),
16    /// Variable reference
17    Var(String),
18    /// Property access: `expr.field`
19    Field(Box<DartExpr>, String),
20    /// Method call: `expr.method(args)`
21    MethodCall(Box<DartExpr>, String, Vec<DartExpr>),
22    /// Static / free function call: `name(args)`
23    Call(Box<DartExpr>, Vec<DartExpr>),
24    /// Constructor call: `ClassName(args)` or `ClassName.named(args)`
25    New(String, Option<String>, Vec<DartExpr>),
26    /// List literal: `[e1, e2, ...]`
27    ListLit(Vec<DartExpr>),
28    /// Map literal: `{k1: v1, k2: v2, ...}`
29    MapLit(Vec<(DartExpr, DartExpr)>),
30    /// Set literal: `{e1, e2, ...}`
31    SetLit(Vec<DartExpr>),
32    /// Anonymous function: `(params) { body }` or `(params) => expr`
33    Lambda(Vec<(DartType, String)>, Box<DartExpr>),
34    /// Arrow function: `(params) => expr`
35    Arrow(Vec<(DartType, String)>, Box<DartExpr>),
36    /// Binary operation: `left op right`
37    BinOp(Box<DartExpr>, String, Box<DartExpr>),
38    /// Unary operation: `op expr`
39    UnaryOp(String, Box<DartExpr>),
40    /// Conditional / ternary: `cond ? then : else`
41    Ternary(Box<DartExpr>, Box<DartExpr>, Box<DartExpr>),
42    /// Null-aware access: `expr?.field`
43    NullAware(Box<DartExpr>, String),
44    /// Null coalescing: `expr ?? fallback`
45    NullCoalesce(Box<DartExpr>, Box<DartExpr>),
46    /// Cascade: `expr..method(args)`
47    Cascade(Box<DartExpr>, String, Vec<DartExpr>),
48    /// `await expr`
49    Await(Box<DartExpr>),
50    /// Type cast: `expr as Type`
51    As(Box<DartExpr>, DartType),
52    /// Type check: `expr is Type`
53    Is(Box<DartExpr>, DartType),
54    /// `throw expr`
55    Throw(Box<DartExpr>),
56    /// Spread in list/map: `...expr`
57    Spread(Box<DartExpr>),
58    /// Index access: `expr[index]`
59    Index(Box<DartExpr>, Box<DartExpr>),
60    /// Raw Dart snippet (for runtime helpers)
61    Raw(String),
62}
63/// A Dart `enum` declaration (enhanced enums with members).
64#[allow(dead_code)]
65#[derive(Debug, Clone)]
66pub struct DartEnum {
67    pub name: String,
68    pub variants: Vec<DartEnumVariant>,
69    pub implements: Vec<DartType>,
70    pub doc: Option<String>,
71}
72#[allow(dead_code)]
73impl DartEnum {
74    pub fn new(name: impl Into<String>) -> Self {
75        DartEnum {
76            name: name.into(),
77            variants: vec![],
78            implements: vec![],
79            doc: None,
80        }
81    }
82    pub fn add_variant(&mut self, v: DartEnumVariant) {
83        self.variants.push(v);
84    }
85    pub fn emit(&self) -> String {
86        let mut out = String::new();
87        if let Some(doc) = &self.doc {
88            out.push_str(&format!("/// {}\n", doc));
89        }
90        let impl_part = if self.implements.is_empty() {
91            String::new()
92        } else {
93            let impls: Vec<String> = self.implements.iter().map(|t| t.to_string()).collect();
94            format!(" implements {}", impls.join(", "))
95        };
96        out.push_str(&format!("enum {}{} {{\n", self.name, impl_part));
97        for (i, variant) in self.variants.iter().enumerate() {
98            let comma = if i + 1 < self.variants.len() {
99                ","
100            } else {
101                ";"
102            };
103            if let Some(doc) = &variant.doc {
104                out.push_str(&format!("  /// {}\n", doc));
105            }
106            out.push_str(&format!("  {}{}\n", variant.name, comma));
107        }
108        out.push_str("}\n");
109        out
110    }
111}
112/// Dart literal values.
113#[derive(Debug, Clone, PartialEq)]
114pub enum DartLit {
115    /// Integer literal: `0`, `42`
116    Int(i64),
117    /// Double literal: `3.14`
118    Double(f64),
119    /// Boolean literal: `true` or `false`
120    Bool(bool),
121    /// String literal: `'hello'`
122    Str(String),
123    /// `null`
124    Null,
125}
126/// A Dart import directive.
127#[derive(Debug, Clone)]
128pub struct DartImport {
129    pub uri: String,
130    pub as_prefix: Option<String>,
131    pub show: Vec<String>,
132    pub hide: Vec<String>,
133    pub is_deferred: bool,
134}
135impl DartImport {
136    pub fn simple(uri: impl Into<String>) -> Self {
137        DartImport {
138            uri: uri.into(),
139            as_prefix: None,
140            show: Vec::new(),
141            hide: Vec::new(),
142            is_deferred: false,
143        }
144    }
145    pub fn with_prefix(uri: impl Into<String>, prefix: impl Into<String>) -> Self {
146        DartImport {
147            uri: uri.into(),
148            as_prefix: Some(prefix.into()),
149            show: Vec::new(),
150            hide: Vec::new(),
151            is_deferred: false,
152        }
153    }
154    /// Emit the import statement as Dart source.
155    pub fn emit(&self) -> String {
156        let mut out = format!("import '{}'", self.uri);
157        if self.is_deferred {
158            out.push_str(" deferred");
159        }
160        if let Some(prefix) = &self.as_prefix {
161            out.push_str(&format!(" as {}", prefix));
162        }
163        if !self.show.is_empty() {
164            out.push_str(&format!(" show {}", self.show.join(", ")));
165        }
166        if !self.hide.is_empty() {
167            out.push_str(&format!(" hide {}", self.hide.join(", ")));
168        }
169        out.push_str(";\n");
170        out
171    }
172}
173/// A Dart class declaration.
174#[derive(Debug, Clone)]
175pub struct DartClass {
176    pub name: String,
177    pub type_params: Vec<String>,
178    pub extends: Option<String>,
179    pub implements: Vec<String>,
180    pub mixins: Vec<String>,
181    pub fields: Vec<DartField>,
182    pub constructors: Vec<DartFunction>,
183    pub methods: Vec<DartFunction>,
184    pub is_abstract: bool,
185    pub doc: Option<String>,
186}
187impl DartClass {
188    pub fn new(name: impl Into<String>) -> Self {
189        DartClass {
190            name: name.into(),
191            type_params: Vec::new(),
192            extends: None,
193            implements: Vec::new(),
194            mixins: Vec::new(),
195            fields: Vec::new(),
196            constructors: Vec::new(),
197            methods: Vec::new(),
198            is_abstract: false,
199            doc: None,
200        }
201    }
202}
203/// A Dart extension method block.
204#[allow(dead_code)]
205#[derive(Debug, Clone)]
206pub struct DartExtension {
207    pub name: Option<String>,
208    pub on_type: DartType,
209    pub methods: Vec<DartFunction>,
210    pub getters: Vec<DartFunction>,
211}
212#[allow(dead_code)]
213impl DartExtension {
214    pub fn new(on_type: DartType) -> Self {
215        DartExtension {
216            name: None,
217            on_type,
218            methods: vec![],
219            getters: vec![],
220        }
221    }
222    pub fn named(mut self, name: impl Into<String>) -> Self {
223        self.name = Some(name.into());
224        self
225    }
226    pub fn add_method(&mut self, m: DartFunction) {
227        self.methods.push(m);
228    }
229    pub fn add_getter(&mut self, g: DartFunction) {
230        self.getters.push(g);
231    }
232    pub fn emit(&self, backend: &DartBackend, indent_level: usize) -> String {
233        let name_part = self
234            .name
235            .as_deref()
236            .map(|n| format!(" {}", n))
237            .unwrap_or_default();
238        let pad = "    ".repeat(indent_level);
239        let mut out = format!("{}extension{} on {} {{\n", pad, name_part, self.on_type);
240        for g in &self.getters {
241            out.push_str(&backend.emit_getter(g, indent_level + 1));
242        }
243        for m in &self.methods {
244            out.push_str(&backend.emit_function(m, indent_level + 1));
245        }
246        out.push_str(&format!("{}}}\n", pad));
247        out
248    }
249}
250/// A single enum variant with optional associated values.
251#[allow(dead_code)]
252#[derive(Debug, Clone)]
253pub struct DartEnumVariant {
254    pub name: String,
255    pub values: Vec<DartExpr>,
256    pub doc: Option<String>,
257}
258#[allow(dead_code)]
259impl DartEnumVariant {
260    pub fn simple(name: impl Into<String>) -> Self {
261        DartEnumVariant {
262            name: name.into(),
263            values: vec![],
264            doc: None,
265        }
266    }
267}
268/// A Dart `mixin` declaration.
269#[allow(dead_code)]
270#[derive(Debug, Clone)]
271pub struct DartMixin {
272    pub name: String,
273    pub on_types: Vec<DartType>,
274    pub fields: Vec<DartField>,
275    pub methods: Vec<DartFunction>,
276    pub doc: Option<String>,
277}
278#[allow(dead_code)]
279impl DartMixin {
280    pub fn new(name: impl Into<String>) -> Self {
281        DartMixin {
282            name: name.into(),
283            on_types: vec![],
284            fields: vec![],
285            methods: vec![],
286            doc: None,
287        }
288    }
289    pub fn with_on(mut self, ty: DartType) -> Self {
290        self.on_types.push(ty);
291        self
292    }
293    pub fn with_doc(mut self, doc: impl Into<String>) -> Self {
294        self.doc = Some(doc.into());
295        self
296    }
297    pub fn emit(&self, backend: &DartBackend, indent_level: usize) -> String {
298        let pad = "    ".repeat(indent_level);
299        let mut out = String::new();
300        if let Some(doc) = &self.doc {
301            out.push_str(&format!("{}/// {}\n", pad, doc));
302        }
303        let on_part = if self.on_types.is_empty() {
304            String::new()
305        } else {
306            let types: Vec<String> = self.on_types.iter().map(|t| t.to_string()).collect();
307            format!(" on {}", types.join(", "))
308        };
309        out.push_str(&format!("{}mixin {}{} {{\n", pad, self.name, on_part));
310        for field in &self.fields {
311            out.push_str(&format!(
312                "{}{}\n",
313                "    ".repeat(indent_level + 1),
314                emit_dart_field(field)
315            ));
316        }
317        for method in &self.methods {
318            out.push_str(&backend.emit_function(method, indent_level + 1));
319        }
320        out.push_str(&format!("{}}}\n", pad));
321        out
322    }
323}
324/// A Dart file/module (compilation unit).
325#[derive(Debug, Clone)]
326pub struct DartModule {
327    pub imports: Vec<DartImport>,
328    pub exports: Vec<String>,
329    pub classes: Vec<DartClass>,
330    pub functions: Vec<DartFunction>,
331    pub globals: Vec<(DartType, String, DartExpr)>,
332    pub part_of: Option<String>,
333}
334impl DartModule {
335    pub fn new() -> Self {
336        DartModule {
337            imports: Vec::new(),
338            exports: Vec::new(),
339            classes: Vec::new(),
340            functions: Vec::new(),
341            globals: Vec::new(),
342            part_of: None,
343        }
344    }
345}
346/// A Dart top-level or class function/method.
347#[derive(Debug, Clone)]
348pub struct DartFunction {
349    pub name: String,
350    pub return_type: DartType,
351    pub params: Vec<DartParam>,
352    pub body: Vec<DartStmt>,
353    pub is_async: bool,
354    pub is_static: bool,
355    pub is_abstract: bool,
356    pub type_params: Vec<String>,
357    pub doc: Option<String>,
358}
359impl DartFunction {
360    pub fn new(name: impl Into<String>, return_type: DartType) -> Self {
361        DartFunction {
362            name: name.into(),
363            return_type,
364            params: Vec::new(),
365            body: Vec::new(),
366            is_async: false,
367            is_static: false,
368            is_abstract: false,
369            type_params: Vec::new(),
370            doc: None,
371        }
372    }
373}
374/// A Dart annotation (metadata).
375#[allow(dead_code)]
376#[derive(Debug, Clone)]
377pub enum DartAnnotation {
378    /// `@override`
379    Override,
380    /// `@deprecated`
381    Deprecated,
382    /// `@visibleForTesting`
383    VisibleForTesting,
384    /// `@immutable`
385    Immutable,
386    /// `@sealed`
387    Sealed,
388    /// Custom annotation with optional arguments.
389    Custom(String, Vec<String>),
390}
391/// A parameter in a Dart function/method.
392#[derive(Debug, Clone, PartialEq)]
393pub struct DartParam {
394    pub ty: DartType,
395    pub name: String,
396    pub is_named: bool,
397    pub is_required: bool,
398    pub default_value: Option<DartExpr>,
399}
400impl DartParam {
401    pub fn positional(ty: DartType, name: impl Into<String>) -> Self {
402        DartParam {
403            ty,
404            name: name.into(),
405            is_named: false,
406            is_required: true,
407            default_value: None,
408        }
409    }
410    pub fn named_required(ty: DartType, name: impl Into<String>) -> Self {
411        DartParam {
412            ty,
413            name: name.into(),
414            is_named: true,
415            is_required: true,
416            default_value: None,
417        }
418    }
419    pub fn named_optional(ty: DartType, name: impl Into<String>, default: DartExpr) -> Self {
420        DartParam {
421            ty,
422            name: name.into(),
423            is_named: true,
424            is_required: false,
425            default_value: Some(default),
426        }
427    }
428}
429/// Dart code generation backend.
430pub struct DartBackend {
431    /// Counter for generating unique variable names.
432    pub(super) var_counter: u64,
433    /// Dart keywords that must be escaped.
434    pub(super) keywords: HashSet<&'static str>,
435    /// Map from mangled name → original name.
436    pub(super) name_cache: HashMap<String, String>,
437    /// Indentation step (spaces).
438    pub(super) indent_width: usize,
439}
440impl DartBackend {
441    pub fn new() -> Self {
442        let mut keywords = HashSet::new();
443        for kw in DART_KEYWORDS {
444            keywords.insert(*kw);
445        }
446        DartBackend {
447            var_counter: 0,
448            keywords,
449            name_cache: HashMap::new(),
450            indent_width: 2,
451        }
452    }
453    /// Generate a fresh local variable name.
454    pub fn fresh_var(&mut self) -> String {
455        let id = self.var_counter;
456        self.var_counter += 1;
457        format!("_v{}", id)
458    }
459    /// Mangle an OxiLean identifier to a valid Dart identifier.
460    pub fn mangle_name(&mut self, name: &str) -> String {
461        if let Some(cached) = self.name_cache.get(name) {
462            return cached.clone();
463        }
464        let mangled = mangle_dart_ident(name, &self.keywords);
465        self.name_cache.insert(name.to_string(), mangled.clone());
466        mangled
467    }
468    /// Emit a complete Dart module (file) as a String.
469    pub fn emit_module(&mut self, module: &DartModule) -> String {
470        let mut out = String::new();
471        if let Some(part) = &module.part_of {
472            out.push_str(&format!("part of '{}';\n\n", part));
473        }
474        for import in &module.imports {
475            out.push_str(&self.emit_import(import));
476        }
477        if !module.imports.is_empty() {
478            out.push('\n');
479        }
480        for exp in &module.exports {
481            out.push_str(&format!("export '{}';\n", exp));
482        }
483        if !module.exports.is_empty() {
484            out.push('\n');
485        }
486        for (ty, name, init) in &module.globals {
487            out.push_str(&format!("final {} {} = {};\n", ty, name, init));
488        }
489        if !module.globals.is_empty() {
490            out.push('\n');
491        }
492        for func in &module.functions {
493            out.push_str(&self.emit_function(func, 0));
494            out.push('\n');
495        }
496        for class in &module.classes {
497            out.push_str(&self.emit_class(class, 0));
498            out.push('\n');
499        }
500        out
501    }
502    /// Emit a `DartImport`.
503    pub fn emit_import(&self, import: &DartImport) -> String {
504        let mut line = format!("import '{}'", import.uri);
505        if import.is_deferred {
506            line.push_str(" deferred");
507        }
508        if let Some(prefix) = &import.as_prefix {
509            line.push_str(&format!(" as {}", prefix));
510        }
511        if !import.show.is_empty() {
512            line.push_str(&format!(" show {}", import.show.join(", ")));
513        }
514        if !import.hide.is_empty() {
515            line.push_str(&format!(" hide {}", import.hide.join(", ")));
516        }
517        line.push_str(";\n");
518        line
519    }
520    /// Emit a Dart class declaration.
521    pub fn emit_class(&self, class: &DartClass, depth: usize) -> String {
522        let indent = self.indent(depth);
523        let mut out = String::new();
524        if let Some(doc) = &class.doc {
525            for line in doc.lines() {
526                out.push_str(&format!("{}/// {}\n", indent, line));
527            }
528        }
529        if class.is_abstract {
530            out.push_str(&format!("{}abstract ", indent));
531        } else {
532            out.push_str(&indent);
533        }
534        out.push_str("class ");
535        out.push_str(&class.name);
536        if !class.type_params.is_empty() {
537            out.push('<');
538            out.push_str(&class.type_params.join(", "));
539            out.push('>');
540        }
541        if let Some(ext) = &class.extends {
542            out.push_str(&format!(" extends {}", ext));
543        }
544        if !class.mixins.is_empty() {
545            out.push_str(&format!(" with {}", class.mixins.join(", ")));
546        }
547        if !class.implements.is_empty() {
548            out.push_str(&format!(" implements {}", class.implements.join(", ")));
549        }
550        out.push_str(" {\n");
551        let inner = self.indent(depth + 1);
552        for field in &class.fields {
553            if let Some(doc) = &field.doc {
554                out.push_str(&format!("{}/// {}\n", inner, doc));
555            }
556            let mut modifiers = String::new();
557            if field.is_static {
558                modifiers.push_str("static ");
559            }
560            if field.is_final {
561                modifiers.push_str("final ");
562            }
563            if field.is_late {
564                modifiers.push_str("late ");
565            }
566            if let Some(init) = &field.default_value {
567                out.push_str(&format!(
568                    "{}{}{} {} = {};\n",
569                    inner, modifiers, field.ty, field.name, init
570                ));
571            } else {
572                out.push_str(&format!(
573                    "{}{}{} {};\n",
574                    inner, modifiers, field.ty, field.name
575                ));
576            }
577        }
578        if !class.fields.is_empty() && (!class.constructors.is_empty() || !class.methods.is_empty())
579        {
580            out.push('\n');
581        }
582        for ctor in &class.constructors {
583            out.push_str(&self.emit_constructor(ctor, &class.name, depth + 1));
584            out.push('\n');
585        }
586        for method in &class.methods {
587            out.push_str(&self.emit_function(method, depth + 1));
588            out.push('\n');
589        }
590        out.push_str(&format!("{}}}\n", indent));
591        out
592    }
593    /// Emit a Dart constructor (uses class name, not return type).
594    pub fn emit_constructor(&self, ctor: &DartFunction, class_name: &str, depth: usize) -> String {
595        let indent = self.indent(depth);
596        let mut out = String::new();
597        if let Some(doc) = &ctor.doc {
598            out.push_str(&format!("{}/// {}\n", indent, doc));
599        }
600        if ctor.is_static {
601            out.push_str(&format!("{}static ", indent));
602        } else {
603            out.push_str(&indent);
604        }
605        let ctor_name = if ctor.name.is_empty() {
606            class_name.to_string()
607        } else {
608            format!("{}.{}", class_name, ctor.name)
609        };
610        out.push_str(&ctor_name);
611        out.push('(');
612        out.push_str(&self.emit_params(&ctor.params));
613        out.push(')');
614        if ctor.is_abstract {
615            out.push_str(";\n");
616        } else {
617            out.push_str(" {\n");
618            for stmt in &ctor.body {
619                out.push_str(&self.emit_stmt(stmt, depth + 1));
620            }
621            out.push_str(&format!("{}}}\n", indent));
622        }
623        out
624    }
625    /// Emit a Dart function or method.
626    pub fn emit_function(&self, func: &DartFunction, depth: usize) -> String {
627        let indent = self.indent(depth);
628        let mut out = String::new();
629        if let Some(doc) = &func.doc {
630            for line in doc.lines() {
631                out.push_str(&format!("{}/// {}\n", indent, line));
632            }
633        }
634        if func.is_static {
635            out.push_str(&format!("{}static ", indent));
636        } else {
637            out.push_str(&indent);
638        }
639        let async_suffix = if func.is_async { " async" } else { "" };
640        let ret_ty = if func.is_async {
641            match &func.return_type {
642                DartType::DtFuture(_) => format!("{}", func.return_type),
643                other => format!("Future<{}>", other),
644            }
645        } else {
646            format!("{}", func.return_type)
647        };
648        let type_params_str = if func.type_params.is_empty() {
649            String::new()
650        } else {
651            format!("<{}>", func.type_params.join(", "))
652        };
653        out.push_str(&format!(
654            "{} {}{}({}){}",
655            ret_ty,
656            func.name,
657            type_params_str,
658            self.emit_params(&func.params),
659            async_suffix,
660        ));
661        if func.is_abstract {
662            out.push_str(";\n");
663        } else {
664            out.push_str(" {\n");
665            for stmt in &func.body {
666                out.push_str(&self.emit_stmt(stmt, depth + 1));
667            }
668            out.push_str(&format!("{}}}\n", indent));
669        }
670        out
671    }
672    /// Emit a Dart getter (a function with `get` keyword, no params).
673    pub fn emit_getter(&self, func: &DartFunction, depth: usize) -> String {
674        let indent = self.indent(depth);
675        let mut out = String::new();
676        if let Some(doc) = &func.doc {
677            for line in doc.lines() {
678                out.push_str(&format!("{}/// {}\n", indent, line));
679            }
680        }
681        if func.is_static {
682            out.push_str(&format!("{}static ", indent));
683        } else {
684            out.push_str(&indent);
685        }
686        out.push_str(&format!("{} get {}", func.return_type, func.name));
687        if func.is_abstract {
688            out.push_str(";\n");
689        } else {
690            out.push_str(" {\n");
691            for stmt in &func.body {
692                out.push_str(&self.emit_stmt(stmt, depth + 1));
693            }
694            out.push_str(&format!("{}}}\n", indent));
695        }
696        out
697    }
698    /// Emit a parameter list.
699    pub fn emit_params(&self, params: &[DartParam]) -> String {
700        let positional: Vec<&DartParam> = params.iter().filter(|p| !p.is_named).collect();
701        let named: Vec<&DartParam> = params.iter().filter(|p| p.is_named).collect();
702        let mut parts: Vec<String> = positional
703            .iter()
704            .map(|p| format!("{} {}", p.ty, p.name))
705            .collect();
706        if !named.is_empty() {
707            let named_parts: Vec<String> = named
708                .iter()
709                .map(|p| {
710                    let req = if p.is_required { "required " } else { "" };
711                    if let Some(def) = &p.default_value {
712                        format!("{}{} {} = {}", req, p.ty, p.name, def)
713                    } else {
714                        format!("{}{} {}", req, p.ty, p.name)
715                    }
716                })
717                .collect();
718            parts.push(format!("{{{}}}", named_parts.join(", ")));
719        }
720        parts.join(", ")
721    }
722    /// Emit a Dart statement with proper indentation.
723    pub fn emit_stmt(&self, stmt: &DartStmt, depth: usize) -> String {
724        let indent = self.indent(depth);
725        match stmt {
726            DartStmt::VarDecl(ty, name, init) => {
727                format!("{}{} {} = {};\n", indent, ty, name, init)
728            }
729            DartStmt::VarInferred(name, init) => {
730                format!("{}var {} = {};\n", indent, name, init)
731            }
732            DartStmt::FinalDecl(ty, name, init) => {
733                format!("{}final {} {} = {};\n", indent, ty, name, init)
734            }
735            DartStmt::ConstDecl(ty, name, init) => {
736                format!("{}const {} {} = {};\n", indent, ty, name, init)
737            }
738            DartStmt::Assign(name, val) => format!("{}{} = {};\n", indent, name, val),
739            DartStmt::FieldAssign(obj, field, val) => {
740                format!("{}{}.{} = {};\n", indent, obj, field, val)
741            }
742            DartStmt::IndexAssign(obj, idx, val) => {
743                format!("{}{}[{}] = {};\n", indent, obj, idx, val)
744            }
745            DartStmt::Return(None) => format!("{}return;\n", indent),
746            DartStmt::Return(Some(expr)) => format!("{}return {};\n", indent, expr),
747            DartStmt::Expr(expr) => format!("{}{};\n", indent, expr),
748            DartStmt::If(cond, then, else_) => {
749                let mut out = format!("{}if ({}) {{\n", indent, cond);
750                for s in then {
751                    out.push_str(&self.emit_stmt(s, depth + 1));
752                }
753                if !else_.is_empty() {
754                    out.push_str(&format!("{}}} else {{\n", indent));
755                    for s in else_ {
756                        out.push_str(&self.emit_stmt(s, depth + 1));
757                    }
758                }
759                out.push_str(&format!("{}}}\n", indent));
760                out
761            }
762            DartStmt::While(cond, body) => {
763                let mut out = format!("{}while ({}) {{\n", indent, cond);
764                for s in body {
765                    out.push_str(&self.emit_stmt(s, depth + 1));
766                }
767                out.push_str(&format!("{}}}\n", indent));
768                out
769            }
770            DartStmt::DoWhile(body, cond) => {
771                let mut out = format!("{}do {{\n", indent);
772                for s in body {
773                    out.push_str(&self.emit_stmt(s, depth + 1));
774                }
775                out.push_str(&format!("{}}} while ({});\n", indent, cond));
776                out
777            }
778            DartStmt::For(init, cond, update, body) => {
779                let init_str = self.emit_stmt(init, 0).trim_end_matches('\n').to_string();
780                let update_str = self
781                    .emit_stmt(update, 0)
782                    .trim_end_matches(";\n")
783                    .trim()
784                    .to_string();
785                let mut out = format!(
786                    "{}for ({}; {}; {}) {{\n",
787                    indent,
788                    init_str.trim_start(),
789                    cond,
790                    update_str
791                );
792                for s in body {
793                    out.push_str(&self.emit_stmt(s, depth + 1));
794                }
795                out.push_str(&format!("{}}}\n", indent));
796                out
797            }
798            DartStmt::ForIn(var, iter, body) => {
799                let mut out = format!("{}for (final {} in {}) {{\n", indent, var, iter);
800                for s in body {
801                    out.push_str(&self.emit_stmt(s, depth + 1));
802                }
803                out.push_str(&format!("{}}}\n", indent));
804                out
805            }
806            DartStmt::Break => format!("{}break;\n", indent),
807            DartStmt::Continue => format!("{}continue;\n", indent),
808            DartStmt::Throw(expr) => format!("{}throw {};\n", indent, expr),
809            DartStmt::TryCatch(body, catch_var, handler, fin) => {
810                let mut out = format!("{}try {{\n", indent);
811                for s in body {
812                    out.push_str(&self.emit_stmt(s, depth + 1));
813                }
814                out.push_str(&format!("{}}} catch ({}) {{\n", indent, catch_var));
815                for s in handler {
816                    out.push_str(&self.emit_stmt(s, depth + 1));
817                }
818                if !fin.is_empty() {
819                    out.push_str(&format!("{}}} finally {{\n", indent));
820                    for s in fin {
821                        out.push_str(&self.emit_stmt(s, depth + 1));
822                    }
823                }
824                out.push_str(&format!("{}}}\n", indent));
825                out
826            }
827            DartStmt::Switch(expr, cases, default) => {
828                let mut out = format!("{}switch ({}) {{\n", indent, expr);
829                for (val, stmts) in cases {
830                    out.push_str(&format!("{}  case {}:\n", indent, val));
831                    for s in stmts {
832                        out.push_str(&self.emit_stmt(s, depth + 2));
833                    }
834                    out.push_str(&format!("{}    break;\n", indent));
835                }
836                if !default.is_empty() {
837                    out.push_str(&format!("{}  default:\n", indent));
838                    for s in default {
839                        out.push_str(&self.emit_stmt(s, depth + 2));
840                    }
841                }
842                out.push_str(&format!("{}}}\n", indent));
843                out
844            }
845            DartStmt::Assert(expr) => format!("{}assert({});\n", indent, expr),
846            DartStmt::Block(stmts) => {
847                let mut out = format!("{}{{\n", indent);
848                for s in stmts {
849                    out.push_str(&self.emit_stmt(s, depth + 1));
850                }
851                out.push_str(&format!("{}}}\n", indent));
852                out
853            }
854            DartStmt::Raw(code) => format!("{}{}\n", indent, code),
855        }
856    }
857    pub(super) fn indent(&self, depth: usize) -> String {
858        " ".repeat(depth * self.indent_width)
859    }
860    /// Compile an LCNF function to a `DartFunction`.
861    pub fn compile_lcnf_function(&mut self, func: &LcnfFunDecl) -> Result<DartFunction, String> {
862        let name = self.mangle_name(&func.name.to_string());
863        let ret_ty = lcnf_type_to_dart(&func.ret_type);
864        let mut params = Vec::new();
865        for param in &func.params {
866            let pname = format!("_x{}", param.id.0);
867            let pty = lcnf_type_to_dart(&param.ty);
868            params.push(DartParam::positional(pty, pname));
869        }
870        let mut body_stmts = Vec::new();
871        let result_expr = self.compile_expr(&func.body, &mut body_stmts)?;
872        body_stmts.push(DartStmt::Return(Some(result_expr)));
873        let mut dart_fn = DartFunction::new(name, ret_ty);
874        dart_fn.params = params;
875        dart_fn.body = body_stmts;
876        Ok(dart_fn)
877    }
878    /// Compile an LCNF module to a `DartModule`.
879    pub fn compile_lcnf_module(&mut self, module: &LcnfModule) -> Result<DartModule, String> {
880        let mut dart_module = DartModule::new();
881        dart_module.imports.push(DartImport::simple("dart:core"));
882        dart_module
883            .imports
884            .push(DartImport::simple("dart:collection"));
885        let ctor_names = collect_ctor_names(module);
886        for ctor_name in &ctor_names {
887            dart_module.classes.push(make_ctor_class(ctor_name));
888        }
889        dart_module
890            .functions
891            .push(DartFunction::new("_unreachable", DartType::DtDynamic));
892        for func in &module.fun_decls {
893            let dart_fn = self.compile_lcnf_function(func)?;
894            dart_module.functions.push(dart_fn);
895        }
896        Ok(dart_module)
897    }
898    pub(super) fn compile_expr(
899        &mut self,
900        expr: &LcnfExpr,
901        stmts: &mut Vec<DartStmt>,
902    ) -> Result<DartExpr, String> {
903        match expr {
904            LcnfExpr::Return(arg) => Ok(self.compile_arg(arg)),
905            LcnfExpr::Unreachable => {
906                stmts.push(DartStmt::Throw(DartExpr::New(
907                    "StateError".to_string(),
908                    None,
909                    vec![DartExpr::Lit(DartLit::Str(
910                        "OxiLean: unreachable".to_string(),
911                    ))],
912                )));
913                Ok(DartExpr::Lit(DartLit::Null))
914            }
915            LcnfExpr::TailCall(func, args) => {
916                let callee = self.compile_arg(func);
917                let dart_args: Vec<DartExpr> = args.iter().map(|a| self.compile_arg(a)).collect();
918                Ok(DartExpr::Call(Box::new(callee), dart_args))
919            }
920            LcnfExpr::Let {
921                id,
922                name: _,
923                ty,
924                value,
925                body,
926            } => {
927                let var_name = format!("_x{}", id.0);
928                let dart_ty = lcnf_type_to_dart(ty);
929                let val_expr = self.compile_let_value(value)?;
930                stmts.push(DartStmt::VarDecl(dart_ty, var_name, val_expr));
931                self.compile_expr(body, stmts)
932            }
933            LcnfExpr::Case {
934                scrutinee,
935                scrutinee_ty: _,
936                alts,
937                default,
938            } => {
939                let scrutinee_expr = DartExpr::Var(format!("_x{}", scrutinee.0));
940                let result_var = self.fresh_var();
941                let mut cases: Vec<(DartExpr, Vec<DartStmt>)> = Vec::new();
942                for alt in alts {
943                    let mut branch_stmts: Vec<DartStmt> = Vec::new();
944                    for (idx, param) in alt.params.iter().enumerate() {
945                        let field_access = DartExpr::Index(
946                            Box::new(DartExpr::Field(
947                                Box::new(scrutinee_expr.clone()),
948                                "fields".to_string(),
949                            )),
950                            Box::new(DartExpr::Lit(DartLit::Int(idx as i64))),
951                        );
952                        let pname = format!("_x{}", param.id.0);
953                        let pty = lcnf_type_to_dart(&param.ty);
954                        branch_stmts.push(DartStmt::FinalDecl(pty, pname, field_access));
955                    }
956                    let branch_result = self.compile_expr(&alt.body, &mut branch_stmts)?;
957                    branch_stmts.push(DartStmt::Assign(result_var.clone(), branch_result));
958                    let tag_val = DartExpr::Lit(DartLit::Int(alt.ctor_tag as i64));
959                    cases.push((tag_val, branch_stmts));
960                }
961                let mut default_stmts: Vec<DartStmt> = Vec::new();
962                if let Some(def) = default {
963                    let def_result = self.compile_expr(def, &mut default_stmts)?;
964                    default_stmts.push(DartStmt::Assign(result_var.clone(), def_result));
965                } else {
966                    default_stmts.push(DartStmt::Throw(DartExpr::New(
967                        "StateError".to_string(),
968                        None,
969                        vec![DartExpr::Lit(DartLit::Str(
970                            "OxiLean: unreachable branch".to_string(),
971                        ))],
972                    )));
973                }
974                let discriminant = DartExpr::Field(Box::new(scrutinee_expr), "tag".to_string());
975                stmts.push(DartStmt::VarDecl(
976                    DartType::DtDynamic,
977                    result_var.clone(),
978                    DartExpr::Lit(DartLit::Null),
979                ));
980                stmts.push(DartStmt::Switch(discriminant, cases, default_stmts));
981                Ok(DartExpr::Var(result_var))
982            }
983        }
984    }
985    pub(super) fn compile_let_value(&mut self, value: &LcnfLetValue) -> Result<DartExpr, String> {
986        match value {
987            LcnfLetValue::Lit(lit) => Ok(self.compile_lit(lit)),
988            LcnfLetValue::Erased => Ok(DartExpr::Lit(DartLit::Null)),
989            LcnfLetValue::FVar(id) => Ok(DartExpr::Var(format!("_x{}", id.0))),
990            LcnfLetValue::App(func, args) => {
991                let callee = self.compile_arg(func);
992                let dart_args: Vec<DartExpr> = args.iter().map(|a| self.compile_arg(a)).collect();
993                Ok(DartExpr::Call(Box::new(callee), dart_args))
994            }
995            LcnfLetValue::Proj(_name, idx, var) => {
996                let base = DartExpr::Var(format!("_x{}", var.0));
997                Ok(DartExpr::Index(
998                    Box::new(DartExpr::Field(Box::new(base), "fields".to_string())),
999                    Box::new(DartExpr::Lit(DartLit::Int(*idx as i64))),
1000                ))
1001            }
1002            LcnfLetValue::Ctor(name, _tag, args) => {
1003                let ctor_name = self.mangle_name(name);
1004                let dart_args: Vec<DartExpr> = args.iter().map(|a| self.compile_arg(a)).collect();
1005                Ok(DartExpr::New(ctor_name, None, dart_args))
1006            }
1007            LcnfLetValue::Reset(_var) => Ok(DartExpr::Lit(DartLit::Null)),
1008            LcnfLetValue::Reuse(_slot, name, _tag, args) => {
1009                let ctor_name = self.mangle_name(name);
1010                let dart_args: Vec<DartExpr> = args.iter().map(|a| self.compile_arg(a)).collect();
1011                Ok(DartExpr::New(ctor_name, None, dart_args))
1012            }
1013        }
1014    }
1015    pub(super) fn compile_arg(&self, arg: &LcnfArg) -> DartExpr {
1016        match arg {
1017            LcnfArg::Var(id) => DartExpr::Var(format!("_x{}", id.0)),
1018            LcnfArg::Lit(lit) => self.compile_lit(lit),
1019            LcnfArg::Erased => DartExpr::Lit(DartLit::Null),
1020            LcnfArg::Type(_) => DartExpr::Lit(DartLit::Null),
1021        }
1022    }
1023    pub(super) fn compile_lit(&self, lit: &LcnfLit) -> DartExpr {
1024        match lit {
1025            LcnfLit::Nat(n) => DartExpr::Lit(DartLit::Int(*n as i64)),
1026            LcnfLit::Str(s) => DartExpr::Lit(DartLit::Str(s.clone())),
1027        }
1028    }
1029}
1030/// A complete Dart source file.
1031#[allow(dead_code)]
1032#[derive(Debug, Clone)]
1033pub struct DartFile {
1034    pub imports: Vec<DartImport>,
1035    pub type_aliases: Vec<DartTypeAlias>,
1036    pub enums: Vec<DartEnum>,
1037    pub classes: Vec<DartClass>,
1038    pub mixins: Vec<DartMixin>,
1039    pub top_level_functions: Vec<DartFunction>,
1040    pub top_level_vars: Vec<(DartType, String, Option<DartExpr>)>,
1041    pub library_name: Option<String>,
1042}
1043#[allow(dead_code)]
1044impl DartFile {
1045    pub fn new() -> Self {
1046        DartFile {
1047            imports: vec![],
1048            type_aliases: vec![],
1049            enums: vec![],
1050            classes: vec![],
1051            mixins: vec![],
1052            top_level_functions: vec![],
1053            top_level_vars: vec![],
1054            library_name: None,
1055        }
1056    }
1057    pub fn with_library(mut self, name: impl Into<String>) -> Self {
1058        self.library_name = Some(name.into());
1059        self
1060    }
1061    pub fn add_import(&mut self, imp: DartImport) {
1062        self.imports.push(imp);
1063    }
1064    pub fn add_enum(&mut self, e: DartEnum) {
1065        self.enums.push(e);
1066    }
1067    pub fn add_class(&mut self, c: DartClass) {
1068        self.classes.push(c);
1069    }
1070    pub fn add_function(&mut self, f: DartFunction) {
1071        self.top_level_functions.push(f);
1072    }
1073    pub fn add_type_alias(&mut self, ta: DartTypeAlias) {
1074        self.type_aliases.push(ta);
1075    }
1076    pub fn add_mixin(&mut self, m: DartMixin) {
1077        self.mixins.push(m);
1078    }
1079    /// Emit the full Dart source file.
1080    pub fn emit(&self, backend: &DartBackend) -> String {
1081        let mut out = String::new();
1082        if let Some(lib) = &self.library_name {
1083            out.push_str(&format!("library {};\n\n", lib));
1084        }
1085        for imp in &self.imports {
1086            out.push_str(&imp.emit());
1087        }
1088        if !self.imports.is_empty() {
1089            out.push('\n');
1090        }
1091        for ta in &self.type_aliases {
1092            out.push_str(&ta.emit());
1093        }
1094        if !self.type_aliases.is_empty() {
1095            out.push('\n');
1096        }
1097        for e in &self.enums {
1098            out.push_str(&e.emit());
1099            out.push('\n');
1100        }
1101        for m in &self.mixins {
1102            out.push_str(&m.emit(backend, 0));
1103            out.push('\n');
1104        }
1105        for c in &self.classes {
1106            out.push_str(&backend.emit_class(c, 0));
1107            out.push('\n');
1108        }
1109        for (ty, name, init) in &self.top_level_vars {
1110            if let Some(val) = init {
1111                out.push_str(&format!("{} {} = {};\n", ty, name, val));
1112            } else {
1113                out.push_str(&format!("late {} {};\n", ty, name));
1114            }
1115        }
1116        if !self.top_level_vars.is_empty() {
1117            out.push('\n');
1118        }
1119        for func in &self.top_level_functions {
1120            out.push_str(&backend.emit_function(func, 0));
1121        }
1122        out
1123    }
1124}
1125/// Helper for building common Dart stream patterns.
1126#[allow(dead_code)]
1127#[derive(Debug, Clone)]
1128pub struct DartStreamBuilder {
1129    pub item_type: DartType,
1130}
1131#[allow(dead_code)]
1132impl DartStreamBuilder {
1133    pub fn new(item_type: DartType) -> Self {
1134        DartStreamBuilder { item_type }
1135    }
1136    /// Generate a `Stream.fromIterable([...])` expression.
1137    pub fn from_iterable(items: Vec<DartExpr>) -> DartExpr {
1138        DartExpr::MethodCall(
1139            Box::new(DartExpr::Var("Stream".to_string())),
1140            "fromIterable".to_string(),
1141            vec![DartExpr::ListLit(items)],
1142        )
1143    }
1144    /// Generate a `stream.listen((item) { ... })` statement.
1145    pub fn listen(stream: DartExpr, param: &str, body: Vec<DartStmt>) -> DartStmt {
1146        let backend = DartBackend::new();
1147        let body_str: String = body.iter().map(|s| backend.emit_stmt(s, 1)).collect();
1148        let closure = DartExpr::Raw(format!("({}) {{\n{}}}", param, body_str));
1149        DartStmt::Expr(DartExpr::MethodCall(
1150            Box::new(stream),
1151            "listen".to_string(),
1152            vec![closure],
1153        ))
1154    }
1155    /// Generate a `StreamController<T>` declaration.
1156    pub fn controller_decl(&self, name: &str) -> DartStmt {
1157        let ty = DartType::DtGeneric("StreamController".to_string(), vec![self.item_type.clone()]);
1158        DartStmt::VarDecl(
1159            ty.clone(),
1160            name.to_string(),
1161            DartExpr::New("StreamController".to_string(), None, vec![]),
1162        )
1163    }
1164}
1165/// Describes a sealed class hierarchy for ADT-style sum types.
1166#[allow(dead_code)]
1167#[derive(Debug, Clone)]
1168pub struct DartSealedHierarchy {
1169    pub base_name: String,
1170    pub variants: Vec<DartClass>,
1171}
1172#[allow(dead_code)]
1173impl DartSealedHierarchy {
1174    pub fn new(base_name: impl Into<String>) -> Self {
1175        DartSealedHierarchy {
1176            base_name: base_name.into(),
1177            variants: vec![],
1178        }
1179    }
1180    pub fn add_variant(&mut self, variant: DartClass) {
1181        self.variants.push(variant);
1182    }
1183    /// Emit the sealed base class and all variant subclasses.
1184    pub fn emit(&self, backend: &DartBackend) -> String {
1185        let mut out = String::new();
1186        out.push_str(&format!(
1187            "sealed class {} {{\n  const {}();\n}}\n\n",
1188            self.base_name, self.base_name
1189        ));
1190        for variant in &self.variants {
1191            out.push_str(&backend.emit_class(variant, 0));
1192            out.push('\n');
1193        }
1194        out
1195    }
1196}
1197/// A Dart `typedef` (type alias) declaration.
1198#[allow(dead_code)]
1199#[derive(Debug, Clone)]
1200pub struct DartTypeAlias {
1201    pub name: String,
1202    pub ty: DartType,
1203    pub doc: Option<String>,
1204}
1205#[allow(dead_code)]
1206impl DartTypeAlias {
1207    pub fn new(name: impl Into<String>, ty: DartType) -> Self {
1208        DartTypeAlias {
1209            name: name.into(),
1210            ty,
1211            doc: None,
1212        }
1213    }
1214    pub fn with_doc(mut self, doc: impl Into<String>) -> Self {
1215        self.doc = Some(doc.into());
1216        self
1217    }
1218    pub fn emit(&self) -> String {
1219        let mut out = String::new();
1220        if let Some(doc) = &self.doc {
1221            out.push_str(&format!("/// {}\n", doc));
1222        }
1223        out.push_str(&format!("typedef {} = {};\n", self.name, self.ty));
1224        out
1225    }
1226}
1227/// Dart statement AST.
1228#[derive(Debug, Clone, PartialEq)]
1229pub enum DartStmt {
1230    /// `Type name = expr;` or `var name = expr;`
1231    VarDecl(DartType, String, DartExpr),
1232    /// `var name = expr;` (inferred type)
1233    VarInferred(String, DartExpr),
1234    /// `final Type name = expr;`
1235    FinalDecl(DartType, String, DartExpr),
1236    /// `const Type name = expr;`
1237    ConstDecl(DartType, String, DartExpr),
1238    /// `name = expr;`
1239    Assign(String, DartExpr),
1240    /// `expr.field = value;`
1241    FieldAssign(DartExpr, String, DartExpr),
1242    /// `expr[idx] = value;`
1243    IndexAssign(DartExpr, DartExpr, DartExpr),
1244    /// `return expr;`
1245    Return(Option<DartExpr>),
1246    /// Expression statement: `expr;`
1247    Expr(DartExpr),
1248    /// `if (cond) { then } else { else_ }`
1249    If(DartExpr, Vec<DartStmt>, Vec<DartStmt>),
1250    /// `while (cond) { body }`
1251    While(DartExpr, Vec<DartStmt>),
1252    /// `do { body } while (cond);`
1253    DoWhile(Vec<DartStmt>, DartExpr),
1254    /// `for (init; cond; update) { body }`
1255    For(Box<DartStmt>, DartExpr, Box<DartStmt>, Vec<DartStmt>),
1256    /// `for (final elem in iterable) { body }`
1257    ForIn(String, DartExpr, Vec<DartStmt>),
1258    /// `break;`
1259    Break,
1260    /// `continue;`
1261    Continue,
1262    /// `throw expr;`
1263    Throw(DartExpr),
1264    /// `try { body } catch (e) { handler } finally { fin }`
1265    TryCatch(Vec<DartStmt>, String, Vec<DartStmt>, Vec<DartStmt>),
1266    /// `switch (expr) { case v: stmts ... default: stmts }`
1267    Switch(DartExpr, Vec<(DartExpr, Vec<DartStmt>)>, Vec<DartStmt>),
1268    /// `assert(expr);`
1269    Assert(DartExpr),
1270    /// Block `{ stmts }`
1271    Block(Vec<DartStmt>),
1272    /// Raw Dart code
1273    Raw(String),
1274}
1275/// A Dart import directive.
1276#[allow(dead_code)]
1277#[derive(Debug, Clone)]
1278pub struct DartImportExt {
1279    pub uri: String,
1280    pub prefix: Option<String>,
1281    pub show: Vec<String>,
1282    pub hide: Vec<String>,
1283    pub is_deferred: bool,
1284}
1285#[allow(dead_code)]
1286impl DartImportExt {
1287    pub fn simple(uri: impl Into<String>) -> Self {
1288        DartImportExt {
1289            uri: uri.into(),
1290            prefix: None,
1291            show: vec![],
1292            hide: vec![],
1293            is_deferred: false,
1294        }
1295    }
1296    pub fn with_prefix(mut self, prefix: impl Into<String>) -> Self {
1297        self.prefix = Some(prefix.into());
1298        self
1299    }
1300    pub fn show_identifiers(mut self, ids: Vec<String>) -> Self {
1301        self.show = ids;
1302        self
1303    }
1304    pub fn hide_identifiers(mut self, ids: Vec<String>) -> Self {
1305        self.hide = ids;
1306        self
1307    }
1308    pub fn deferred(mut self) -> Self {
1309        self.is_deferred = true;
1310        self
1311    }
1312    pub fn emit(&self) -> String {
1313        let mut out = format!("import '{}'", self.uri);
1314        if self.is_deferred {
1315            out.push_str(" deferred");
1316        }
1317        if let Some(prefix) = &self.prefix {
1318            out.push_str(&format!(" as {}", prefix));
1319        }
1320        if !self.show.is_empty() {
1321            out.push_str(&format!(" show {}", self.show.join(", ")));
1322        }
1323        if !self.hide.is_empty() {
1324            out.push_str(&format!(" hide {}", self.hide.join(", ")));
1325        }
1326        out.push_str(";\n");
1327        out
1328    }
1329}
1330/// Metrics collected over a Dart file's generated output.
1331#[allow(dead_code)]
1332#[derive(Debug, Clone, Default)]
1333pub struct DartCodeMetrics {
1334    pub total_lines: usize,
1335    pub class_count: usize,
1336    pub function_count: usize,
1337    pub import_count: usize,
1338}
1339#[allow(dead_code)]
1340impl DartCodeMetrics {
1341    pub fn collect(file: &DartFile) -> Self {
1342        DartCodeMetrics {
1343            total_lines: 0,
1344            class_count: file.classes.len(),
1345            function_count: file.top_level_functions.len(),
1346            import_count: file.imports.len(),
1347        }
1348    }
1349    pub fn update_lines(&mut self, source: &str) {
1350        self.total_lines = source.lines().count();
1351    }
1352}
1353/// A field in a Dart class.
1354#[derive(Debug, Clone)]
1355pub struct DartField {
1356    pub ty: DartType,
1357    pub name: String,
1358    pub is_final: bool,
1359    pub is_static: bool,
1360    pub is_late: bool,
1361    pub default_value: Option<DartExpr>,
1362    pub doc: Option<String>,
1363}
1364impl DartField {
1365    pub fn new(ty: DartType, name: impl Into<String>) -> Self {
1366        DartField {
1367            ty,
1368            name: name.into(),
1369            is_final: false,
1370            is_static: false,
1371            is_late: false,
1372            default_value: None,
1373            doc: None,
1374        }
1375    }
1376    pub fn final_field(ty: DartType, name: impl Into<String>) -> Self {
1377        DartField {
1378            ty,
1379            name: name.into(),
1380            is_final: true,
1381            is_static: false,
1382            is_late: false,
1383            default_value: None,
1384            doc: None,
1385        }
1386    }
1387}
1388/// Dart type representation (null-safe Dart ≥ 2.12).
1389#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1390pub enum DartType {
1391    /// `int`
1392    DtInt,
1393    /// `double`
1394    DtDouble,
1395    /// `bool`
1396    DtBool,
1397    /// `String`
1398    DtString,
1399    /// `void`
1400    DtVoid,
1401    /// `dynamic`
1402    DtDynamic,
1403    /// `Object`
1404    DtObject,
1405    /// `Null`
1406    DtNull,
1407    /// `T?` — nullable wrapper
1408    DtNullable(Box<DartType>),
1409    /// `List<T>`
1410    DtList(Box<DartType>),
1411    /// `Map<K, V>`
1412    DtMap(Box<DartType>, Box<DartType>),
1413    /// `Set<T>`
1414    DtSet(Box<DartType>),
1415    /// `Future<T>`
1416    DtFuture(Box<DartType>),
1417    /// `Stream<T>`
1418    DtStream(Box<DartType>),
1419    /// `T Function(P0, P1, ...)` — function type
1420    DtFunction(Vec<DartType>, Box<DartType>),
1421    /// Named class, e.g. `MyClass`
1422    DtNamed(String),
1423    /// Generic instantiation, e.g. `MyClass<int, String>`
1424    DtGeneric(String, Vec<DartType>),
1425}
1426/// Null safety helper expressions.
1427#[allow(dead_code)]
1428pub struct DartNullSafety;
1429#[allow(dead_code)]
1430impl DartNullSafety {
1431    /// `expr!` — null assertion operator.
1432    pub fn assert_non_null(expr: DartExpr) -> DartExpr {
1433        DartExpr::Raw(format!("{}!", expr))
1434    }
1435    /// `expr ?? fallback` — null coalescing.
1436    pub fn coalesce(expr: DartExpr, fallback: DartExpr) -> DartExpr {
1437        DartExpr::NullCoalesce(Box::new(expr), Box::new(fallback))
1438    }
1439    /// `expr?.field` — null-safe field access.
1440    pub fn safe_field(expr: DartExpr, field: impl Into<String>) -> DartExpr {
1441        DartExpr::NullAware(Box::new(expr), field.into())
1442    }
1443    /// Emit a null check guard statement.
1444    pub fn guard_not_null(var: &str, _ty: DartType) -> DartStmt {
1445        DartStmt::If(
1446            DartExpr::BinOp(
1447                Box::new(DartExpr::Var(var.to_string())),
1448                "==".to_string(),
1449                Box::new(DartExpr::Lit(DartLit::Null)),
1450            ),
1451            vec![DartStmt::Throw(DartExpr::New(
1452                "ArgumentError".to_string(),
1453                None,
1454                vec![DartExpr::Lit(DartLit::Str(format!(
1455                    "{} must not be null",
1456                    var
1457                )))],
1458            ))],
1459            vec![],
1460        )
1461    }
1462    /// `T? x = null;` — nullable variable declaration.
1463    pub fn nullable_decl(ty: DartType, name: &str) -> DartStmt {
1464        DartStmt::VarDecl(
1465            DartType::DtNullable(Box::new(ty)),
1466            name.to_string(),
1467            DartExpr::Lit(DartLit::Null),
1468        )
1469    }
1470}