Skip to main content

oxilean_codegen/typescript_backend/
types.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use std::collections::{HashMap, HashSet, VecDeque};
6
7/// A segment of a template literal.
8#[derive(Debug, Clone, PartialEq)]
9pub enum TsTemplatePart {
10    /// A raw string segment.
11    Text(std::string::String),
12    /// An interpolated expression: `${expr}`.
13    Expr(TsExpr),
14}
15#[allow(dead_code)]
16pub struct TSPassRegistry {
17    pub(super) configs: Vec<TSPassConfig>,
18    pub(super) stats: std::collections::HashMap<String, TSPassStats>,
19}
20impl TSPassRegistry {
21    #[allow(dead_code)]
22    pub fn new() -> Self {
23        TSPassRegistry {
24            configs: Vec::new(),
25            stats: std::collections::HashMap::new(),
26        }
27    }
28    #[allow(dead_code)]
29    pub fn register(&mut self, config: TSPassConfig) {
30        self.stats
31            .insert(config.pass_name.clone(), TSPassStats::new());
32        self.configs.push(config);
33    }
34    #[allow(dead_code)]
35    pub fn enabled_passes(&self) -> Vec<&TSPassConfig> {
36        self.configs.iter().filter(|c| c.enabled).collect()
37    }
38    #[allow(dead_code)]
39    pub fn get_stats(&self, name: &str) -> Option<&TSPassStats> {
40        self.stats.get(name)
41    }
42    #[allow(dead_code)]
43    pub fn total_passes(&self) -> usize {
44        self.configs.len()
45    }
46    #[allow(dead_code)]
47    pub fn enabled_count(&self) -> usize {
48        self.enabled_passes().len()
49    }
50    #[allow(dead_code)]
51    pub fn update_stats(&mut self, name: &str, changes: u64, time_ms: u64, iter: u32) {
52        if let Some(stats) = self.stats.get_mut(name) {
53            stats.record_run(changes, time_ms, iter);
54        }
55    }
56}
57/// A TypeScript class declaration.
58#[derive(Debug, Clone)]
59pub struct TsClass {
60    pub name: std::string::String,
61    pub extends: Option<std::string::String>,
62    pub implements: Vec<std::string::String>,
63    pub fields: Vec<TsClassField>,
64    pub methods: Vec<TsClassMethod>,
65    pub type_params: Vec<std::string::String>,
66    pub is_exported: bool,
67}
68/// A diagnostic message from a TsExt pass.
69#[derive(Debug, Clone)]
70pub struct TsExtDiagMsg {
71    pub severity: TsExtDiagSeverity,
72    pub pass: String,
73    pub message: String,
74}
75impl TsExtDiagMsg {
76    pub fn error(pass: impl Into<String>, msg: impl Into<String>) -> Self {
77        TsExtDiagMsg {
78            severity: TsExtDiagSeverity::Error,
79            pass: pass.into(),
80            message: msg.into(),
81        }
82    }
83    pub fn warning(pass: impl Into<String>, msg: impl Into<String>) -> Self {
84        TsExtDiagMsg {
85            severity: TsExtDiagSeverity::Warning,
86            pass: pass.into(),
87            message: msg.into(),
88        }
89    }
90    pub fn note(pass: impl Into<String>, msg: impl Into<String>) -> Self {
91        TsExtDiagMsg {
92            severity: TsExtDiagSeverity::Note,
93            pass: pass.into(),
94            message: msg.into(),
95        }
96    }
97}
98#[allow(dead_code)]
99#[derive(Debug, Clone)]
100pub struct TSCacheEntry {
101    pub key: String,
102    pub data: Vec<u8>,
103    pub timestamp: u64,
104    pub valid: bool,
105}
106/// A feature flag set for TsExt capabilities.
107#[derive(Debug, Clone, Default)]
108pub struct TsExtFeatures {
109    pub(super) flags: std::collections::HashSet<String>,
110}
111impl TsExtFeatures {
112    pub fn new() -> Self {
113        TsExtFeatures::default()
114    }
115    pub fn enable(&mut self, flag: impl Into<String>) {
116        self.flags.insert(flag.into());
117    }
118    pub fn disable(&mut self, flag: &str) {
119        self.flags.remove(flag);
120    }
121    pub fn is_enabled(&self, flag: &str) -> bool {
122        self.flags.contains(flag)
123    }
124    pub fn len(&self) -> usize {
125        self.flags.len()
126    }
127    pub fn is_empty(&self) -> bool {
128        self.flags.is_empty()
129    }
130    pub fn union(&self, other: &TsExtFeatures) -> TsExtFeatures {
131        TsExtFeatures {
132            flags: self.flags.union(&other.flags).cloned().collect(),
133        }
134    }
135    pub fn intersection(&self, other: &TsExtFeatures) -> TsExtFeatures {
136        TsExtFeatures {
137            flags: self.flags.intersection(&other.flags).cloned().collect(),
138        }
139    }
140}
141/// A TypeScript function parameter.
142#[derive(Debug, Clone)]
143pub struct TsParam {
144    pub name: std::string::String,
145    pub ty: TsType,
146    pub optional: bool,
147    pub rest: bool,
148}
149/// A field in a TypeScript class.
150#[derive(Debug, Clone)]
151pub struct TsClassField {
152    pub name: std::string::String,
153    pub ty: TsType,
154    pub readonly: bool,
155    pub optional: bool,
156    pub is_private: bool,
157    pub is_static: bool,
158}
159/// A TypeScript source module that can emit `.ts` or `.d.ts` source.
160#[derive(Debug, Clone)]
161pub struct TsModule {
162    pub imports: Vec<TsImport>,
163    pub type_imports: Vec<TsImport>,
164    pub declarations: Vec<TsDeclaration>,
165}
166impl TsModule {
167    /// Create an empty module.
168    pub fn new() -> Self {
169        TsModule {
170            imports: Vec::new(),
171            type_imports: Vec::new(),
172            declarations: Vec::new(),
173        }
174    }
175    /// Emit valid `.ts` source code.
176    pub fn emit(&self) -> std::string::String {
177        let mut out = std::string::String::new();
178        for imp in &self.type_imports {
179            out.push_str(&format!("{}\n", imp));
180        }
181        for imp in &self.imports {
182            out.push_str(&format!("{}\n", imp));
183        }
184        if !self.imports.is_empty() || !self.type_imports.is_empty() {
185            out.push('\n');
186        }
187        for decl in &self.declarations {
188            out.push_str(&format!("{}\n\n", decl));
189        }
190        out
191    }
192    /// Emit a `.d.ts` declaration file (ambient declarations only).
193    pub fn emit_d_ts(&self) -> std::string::String {
194        let mut out = std::string::String::new();
195        for imp in &self.type_imports {
196            out.push_str(&format!("{}\n", imp));
197        }
198        for decl in &self.declarations {
199            let dts = match decl {
200                TsDeclaration::Interface(i) => format!("{}\n\n", i),
201                TsDeclaration::TypeAlias(t) => format!("{}\n\n", t),
202                TsDeclaration::Enum(e) => format!("{}\n\n", e),
203                TsDeclaration::Function(f) => {
204                    let mut sig = std::string::String::new();
205                    if f.is_exported {
206                        sig.push_str("export ");
207                    }
208                    sig.push_str(&format!("declare function {}", f.name));
209                    if !f.type_params.is_empty() {
210                        sig.push_str(&format!("<{}>", f.type_params.join(", ")));
211                    }
212                    sig.push('(');
213                    for (i, p) in f.params.iter().enumerate() {
214                        if i > 0 {
215                            sig.push_str(", ");
216                        }
217                        sig.push_str(&format!("{}: {}", p.name, p.ty));
218                    }
219                    sig.push_str(&format!("): {};\n\n", f.return_type));
220                    sig
221                }
222                TsDeclaration::Class(c) => format!("{}\n\n", c),
223                TsDeclaration::Const(name, ty, _) => {
224                    if let Some(t) = ty {
225                        format!("export declare const {}: {};\n\n", name, t)
226                    } else {
227                        format!("export declare const {};\n\n", name)
228                    }
229                }
230                TsDeclaration::Let(name, ty, _) => {
231                    if let Some(t) = ty {
232                        format!("export declare let {}: {};\n\n", name, t)
233                    } else {
234                        format!("export declare let {};\n\n", name)
235                    }
236                }
237                TsDeclaration::ReExport(path) => {
238                    format!("export * from \"{}\";\n\n", path)
239                }
240            };
241            out.push_str(&dts);
242        }
243        out
244    }
245}
246/// A text buffer for building TsExt output source code.
247#[derive(Debug, Default)]
248pub struct TsExtSourceBuffer {
249    pub(super) buf: String,
250    pub(super) indent_level: usize,
251    pub(super) indent_str: String,
252}
253impl TsExtSourceBuffer {
254    pub fn new() -> Self {
255        TsExtSourceBuffer {
256            buf: String::new(),
257            indent_level: 0,
258            indent_str: "    ".to_string(),
259        }
260    }
261    pub fn with_indent(mut self, indent: impl Into<String>) -> Self {
262        self.indent_str = indent.into();
263        self
264    }
265    pub fn push_line(&mut self, line: &str) {
266        for _ in 0..self.indent_level {
267            self.buf.push_str(&self.indent_str);
268        }
269        self.buf.push_str(line);
270        self.buf.push('\n');
271    }
272    pub fn push_raw(&mut self, s: &str) {
273        self.buf.push_str(s);
274    }
275    pub fn indent(&mut self) {
276        self.indent_level += 1;
277    }
278    pub fn dedent(&mut self) {
279        self.indent_level = self.indent_level.saturating_sub(1);
280    }
281    pub fn as_str(&self) -> &str {
282        &self.buf
283    }
284    pub fn len(&self) -> usize {
285        self.buf.len()
286    }
287    pub fn is_empty(&self) -> bool {
288        self.buf.is_empty()
289    }
290    pub fn line_count(&self) -> usize {
291        self.buf.lines().count()
292    }
293    pub fn into_string(self) -> String {
294        self.buf
295    }
296    pub fn reset(&mut self) {
297        self.buf.clear();
298        self.indent_level = 0;
299    }
300}
301/// Severity of a TsExt diagnostic.
302#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
303pub enum TsExtDiagSeverity {
304    Note,
305    Warning,
306    Error,
307}
308/// Heuristic freshness key for TsExt incremental compilation.
309#[derive(Debug, Clone, PartialEq, Eq, Hash)]
310pub struct TsExtIncrKey {
311    pub content_hash: u64,
312    pub config_hash: u64,
313}
314impl TsExtIncrKey {
315    pub fn new(content: u64, config: u64) -> Self {
316        TsExtIncrKey {
317            content_hash: content,
318            config_hash: config,
319        }
320    }
321    pub fn combined_hash(&self) -> u64 {
322        self.content_hash.wrapping_mul(0x9e3779b97f4a7c15) ^ self.config_hash
323    }
324    pub fn matches(&self, other: &TsExtIncrKey) -> bool {
325        self.content_hash == other.content_hash && self.config_hash == other.config_hash
326    }
327}
328/// A version tag for TsExt output artifacts.
329#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
330pub struct TsExtVersion {
331    pub major: u32,
332    pub minor: u32,
333    pub patch: u32,
334    pub pre: Option<String>,
335}
336impl TsExtVersion {
337    pub fn new(major: u32, minor: u32, patch: u32) -> Self {
338        TsExtVersion {
339            major,
340            minor,
341            patch,
342            pre: None,
343        }
344    }
345    pub fn with_pre(mut self, pre: impl Into<String>) -> Self {
346        self.pre = Some(pre.into());
347        self
348    }
349    pub fn is_stable(&self) -> bool {
350        self.pre.is_none()
351    }
352    pub fn is_compatible_with(&self, other: &TsExtVersion) -> bool {
353        self.major == other.major && self.minor >= other.minor
354    }
355}
356/// TypeScript literal values.
357#[derive(Debug, Clone, PartialEq)]
358pub enum TsLit {
359    Num(f64),
360    Str(std::string::String),
361    Bool(bool),
362    Null,
363    Undefined,
364    BigInt(i64),
365}
366#[allow(dead_code)]
367#[derive(Debug, Clone, Default)]
368pub struct TSPassStats {
369    pub total_runs: u32,
370    pub successful_runs: u32,
371    pub total_changes: u64,
372    pub time_ms: u64,
373    pub iterations_used: u32,
374}
375impl TSPassStats {
376    #[allow(dead_code)]
377    pub fn new() -> Self {
378        Self::default()
379    }
380    #[allow(dead_code)]
381    pub fn record_run(&mut self, changes: u64, time_ms: u64, iterations: u32) {
382        self.total_runs += 1;
383        self.successful_runs += 1;
384        self.total_changes += changes;
385        self.time_ms += time_ms;
386        self.iterations_used = iterations;
387    }
388    #[allow(dead_code)]
389    pub fn average_changes_per_run(&self) -> f64 {
390        if self.total_runs == 0 {
391            return 0.0;
392        }
393        self.total_changes as f64 / self.total_runs as f64
394    }
395    #[allow(dead_code)]
396    pub fn success_rate(&self) -> f64 {
397        if self.total_runs == 0 {
398            return 0.0;
399        }
400        self.successful_runs as f64 / self.total_runs as f64
401    }
402    #[allow(dead_code)]
403    pub fn format_summary(&self) -> String {
404        format!(
405            "Runs: {}/{}, Changes: {}, Time: {}ms",
406            self.successful_runs, self.total_runs, self.total_changes, self.time_ms
407        )
408    }
409}
410/// A top-level TypeScript declaration.
411#[derive(Debug, Clone)]
412pub enum TsDeclaration {
413    Interface(TsInterface),
414    TypeAlias(TsTypeAlias),
415    Enum(TsEnum),
416    Function(TsFunction),
417    Class(TsClass),
418    Const(std::string::String, Option<TsType>, TsExpr),
419    Let(std::string::String, Option<TsType>, TsExpr),
420    /// Re-export from another module.
421    ReExport(std::string::String),
422}
423#[allow(dead_code)]
424#[derive(Debug, Clone)]
425pub struct TSDominatorTree {
426    pub idom: Vec<Option<u32>>,
427    pub dom_children: Vec<Vec<u32>>,
428    pub dom_depth: Vec<u32>,
429}
430impl TSDominatorTree {
431    #[allow(dead_code)]
432    pub fn new(size: usize) -> Self {
433        TSDominatorTree {
434            idom: vec![None; size],
435            dom_children: vec![Vec::new(); size],
436            dom_depth: vec![0; size],
437        }
438    }
439    #[allow(dead_code)]
440    pub fn set_idom(&mut self, node: usize, idom: u32) {
441        self.idom[node] = Some(idom);
442    }
443    #[allow(dead_code)]
444    pub fn dominates(&self, a: usize, b: usize) -> bool {
445        if a == b {
446            return true;
447        }
448        let mut cur = b;
449        loop {
450            match self.idom[cur] {
451                Some(parent) if parent as usize == a => return true,
452                Some(parent) if parent as usize == cur => return false,
453                Some(parent) => cur = parent as usize,
454                None => return false,
455            }
456        }
457    }
458    #[allow(dead_code)]
459    pub fn depth(&self, node: usize) -> u32 {
460        self.dom_depth.get(node).copied().unwrap_or(0)
461    }
462}
463#[allow(dead_code)]
464#[derive(Debug, Clone)]
465pub struct TSLivenessInfo {
466    pub live_in: Vec<std::collections::HashSet<u32>>,
467    pub live_out: Vec<std::collections::HashSet<u32>>,
468    pub defs: Vec<std::collections::HashSet<u32>>,
469    pub uses: Vec<std::collections::HashSet<u32>>,
470}
471impl TSLivenessInfo {
472    #[allow(dead_code)]
473    pub fn new(block_count: usize) -> Self {
474        TSLivenessInfo {
475            live_in: vec![std::collections::HashSet::new(); block_count],
476            live_out: vec![std::collections::HashSet::new(); block_count],
477            defs: vec![std::collections::HashSet::new(); block_count],
478            uses: vec![std::collections::HashSet::new(); block_count],
479        }
480    }
481    #[allow(dead_code)]
482    pub fn add_def(&mut self, block: usize, var: u32) {
483        if block < self.defs.len() {
484            self.defs[block].insert(var);
485        }
486    }
487    #[allow(dead_code)]
488    pub fn add_use(&mut self, block: usize, var: u32) {
489        if block < self.uses.len() {
490            self.uses[block].insert(var);
491        }
492    }
493    #[allow(dead_code)]
494    pub fn is_live_in(&self, block: usize, var: u32) -> bool {
495        self.live_in
496            .get(block)
497            .map(|s| s.contains(&var))
498            .unwrap_or(false)
499    }
500    #[allow(dead_code)]
501    pub fn is_live_out(&self, block: usize, var: u32) -> bool {
502        self.live_out
503            .get(block)
504            .map(|s| s.contains(&var))
505            .unwrap_or(false)
506    }
507}
508/// A single enum member (name + optional value).
509#[derive(Debug, Clone)]
510pub struct TsEnumMember {
511    pub name: std::string::String,
512    pub value: Option<TsLit>,
513}
514/// Emission statistics for TsExt.
515#[derive(Debug, Clone, Default)]
516pub struct TsExtEmitStats {
517    pub bytes_emitted: usize,
518    pub items_emitted: usize,
519    pub errors: usize,
520    pub warnings: usize,
521    pub elapsed_ms: u64,
522}
523impl TsExtEmitStats {
524    pub fn new() -> Self {
525        TsExtEmitStats::default()
526    }
527    pub fn throughput_bps(&self) -> f64 {
528        if self.elapsed_ms == 0 {
529            0.0
530        } else {
531            self.bytes_emitted as f64 / (self.elapsed_ms as f64 / 1000.0)
532        }
533    }
534    pub fn is_clean(&self) -> bool {
535        self.errors == 0
536    }
537}
538#[allow(dead_code)]
539pub struct TSConstantFoldingHelper;
540impl TSConstantFoldingHelper {
541    #[allow(dead_code)]
542    pub fn fold_add_i64(a: i64, b: i64) -> Option<i64> {
543        a.checked_add(b)
544    }
545    #[allow(dead_code)]
546    pub fn fold_sub_i64(a: i64, b: i64) -> Option<i64> {
547        a.checked_sub(b)
548    }
549    #[allow(dead_code)]
550    pub fn fold_mul_i64(a: i64, b: i64) -> Option<i64> {
551        a.checked_mul(b)
552    }
553    #[allow(dead_code)]
554    pub fn fold_div_i64(a: i64, b: i64) -> Option<i64> {
555        if b == 0 {
556            None
557        } else {
558            a.checked_div(b)
559        }
560    }
561    #[allow(dead_code)]
562    pub fn fold_add_f64(a: f64, b: f64) -> f64 {
563        a + b
564    }
565    #[allow(dead_code)]
566    pub fn fold_mul_f64(a: f64, b: f64) -> f64 {
567        a * b
568    }
569    #[allow(dead_code)]
570    pub fn fold_neg_i64(a: i64) -> Option<i64> {
571        a.checked_neg()
572    }
573    #[allow(dead_code)]
574    pub fn fold_not_bool(a: bool) -> bool {
575        !a
576    }
577    #[allow(dead_code)]
578    pub fn fold_and_bool(a: bool, b: bool) -> bool {
579        a && b
580    }
581    #[allow(dead_code)]
582    pub fn fold_or_bool(a: bool, b: bool) -> bool {
583        a || b
584    }
585    #[allow(dead_code)]
586    pub fn fold_shl_i64(a: i64, b: u32) -> Option<i64> {
587        a.checked_shl(b)
588    }
589    #[allow(dead_code)]
590    pub fn fold_shr_i64(a: i64, b: u32) -> Option<i64> {
591        a.checked_shr(b)
592    }
593    #[allow(dead_code)]
594    pub fn fold_rem_i64(a: i64, b: i64) -> Option<i64> {
595        if b == 0 {
596            None
597        } else {
598            Some(a % b)
599        }
600    }
601    #[allow(dead_code)]
602    pub fn fold_bitand_i64(a: i64, b: i64) -> i64 {
603        a & b
604    }
605    #[allow(dead_code)]
606    pub fn fold_bitor_i64(a: i64, b: i64) -> i64 {
607        a | b
608    }
609    #[allow(dead_code)]
610    pub fn fold_bitxor_i64(a: i64, b: i64) -> i64 {
611        a ^ b
612    }
613    #[allow(dead_code)]
614    pub fn fold_bitnot_i64(a: i64) -> i64 {
615        !a
616    }
617}
618/// A top-level TypeScript function declaration.
619#[derive(Debug, Clone)]
620pub struct TsFunction {
621    pub name: std::string::String,
622    pub params: Vec<TsParam>,
623    pub return_type: TsType,
624    pub body: Vec<TsStmt>,
625    pub is_async: bool,
626    pub type_params: Vec<std::string::String>,
627    pub is_exported: bool,
628}
629/// A fixed-capacity ring buffer of strings (for recent-event logging in TsExt).
630#[derive(Debug)]
631pub struct TsExtEventLog {
632    pub(super) entries: std::collections::VecDeque<String>,
633    pub(super) capacity: usize,
634}
635impl TsExtEventLog {
636    pub fn new(capacity: usize) -> Self {
637        TsExtEventLog {
638            entries: std::collections::VecDeque::with_capacity(capacity),
639            capacity,
640        }
641    }
642    pub fn push(&mut self, event: impl Into<String>) {
643        if self.entries.len() >= self.capacity {
644            self.entries.pop_front();
645        }
646        self.entries.push_back(event.into());
647    }
648    pub fn iter(&self) -> impl Iterator<Item = &String> {
649        self.entries.iter()
650    }
651    pub fn len(&self) -> usize {
652        self.entries.len()
653    }
654    pub fn is_empty(&self) -> bool {
655        self.entries.is_empty()
656    }
657    pub fn capacity(&self) -> usize {
658        self.capacity
659    }
660    pub fn clear(&mut self) {
661        self.entries.clear();
662    }
663}
664#[allow(dead_code)]
665#[derive(Debug, Clone)]
666pub struct TSAnalysisCache {
667    pub(super) entries: std::collections::HashMap<String, TSCacheEntry>,
668    pub(super) max_size: usize,
669    pub(super) hits: u64,
670    pub(super) misses: u64,
671}
672impl TSAnalysisCache {
673    #[allow(dead_code)]
674    pub fn new(max_size: usize) -> Self {
675        TSAnalysisCache {
676            entries: std::collections::HashMap::new(),
677            max_size,
678            hits: 0,
679            misses: 0,
680        }
681    }
682    #[allow(dead_code)]
683    pub fn get(&mut self, key: &str) -> Option<&TSCacheEntry> {
684        if self.entries.contains_key(key) {
685            self.hits += 1;
686            self.entries.get(key)
687        } else {
688            self.misses += 1;
689            None
690        }
691    }
692    #[allow(dead_code)]
693    pub fn insert(&mut self, key: String, data: Vec<u8>) {
694        if self.entries.len() >= self.max_size {
695            if let Some(oldest) = self.entries.keys().next().cloned() {
696                self.entries.remove(&oldest);
697            }
698        }
699        self.entries.insert(
700            key.clone(),
701            TSCacheEntry {
702                key,
703                data,
704                timestamp: 0,
705                valid: true,
706            },
707        );
708    }
709    #[allow(dead_code)]
710    pub fn invalidate(&mut self, key: &str) {
711        if let Some(entry) = self.entries.get_mut(key) {
712            entry.valid = false;
713        }
714    }
715    #[allow(dead_code)]
716    pub fn clear(&mut self) {
717        self.entries.clear();
718    }
719    #[allow(dead_code)]
720    pub fn hit_rate(&self) -> f64 {
721        let total = self.hits + self.misses;
722        if total == 0 {
723            return 0.0;
724        }
725        self.hits as f64 / total as f64
726    }
727    #[allow(dead_code)]
728    pub fn size(&self) -> usize {
729        self.entries.len()
730    }
731}
732#[allow(dead_code)]
733#[derive(Debug, Clone, PartialEq)]
734pub enum TSPassPhase {
735    Analysis,
736    Transformation,
737    Verification,
738    Cleanup,
739}
740impl TSPassPhase {
741    #[allow(dead_code)]
742    pub fn name(&self) -> &str {
743        match self {
744            TSPassPhase::Analysis => "analysis",
745            TSPassPhase::Transformation => "transformation",
746            TSPassPhase::Verification => "verification",
747            TSPassPhase::Cleanup => "cleanup",
748        }
749    }
750    #[allow(dead_code)]
751    pub fn is_modifying(&self) -> bool {
752        matches!(self, TSPassPhase::Transformation | TSPassPhase::Cleanup)
753    }
754}
755/// TypeScript expression AST node.
756#[derive(Debug, Clone, PartialEq)]
757pub enum TsExpr {
758    /// A literal value.
759    Lit(TsLit),
760    /// A variable reference: `x`, `myVar`
761    Var(std::string::String),
762    /// A binary operation: `lhs + rhs`
763    BinOp(std::string::String, Box<TsExpr>, Box<TsExpr>),
764    /// A unary operation: `!x`, `-n`
765    UnaryOp(std::string::String, Box<TsExpr>),
766    /// A function/method call: `f(a, b)`
767    Call(Box<TsExpr>, Vec<TsExpr>),
768    /// A method call: `obj.method(a, b)`
769    MethodCall(Box<TsExpr>, std::string::String, Vec<TsExpr>),
770    /// Constructor call: `new Foo(a, b)`
771    New(Box<TsExpr>, Vec<TsExpr>),
772    /// Arrow function: `(x: T) => expr`
773    Arrow(Vec<(std::string::String, Option<TsType>)>, Box<TsExpr>),
774    /// Ternary expression: `cond ? then : else`
775    Ternary(Box<TsExpr>, Box<TsExpr>, Box<TsExpr>),
776    /// Type assertion: `expr as T`
777    As(Box<TsExpr>, TsType),
778    /// Satisfies expression: `expr satisfies T`
779    Satisfies(Box<TsExpr>, TsType),
780    /// Non-null assertion: `expr!`
781    TypeAssert(Box<TsExpr>),
782    /// Object literal: `{ key: val }`
783    ObjectLit(Vec<(std::string::String, TsExpr)>),
784    /// Array literal: `[a, b, c]`
785    ArrayLit(Vec<TsExpr>),
786    /// Template literal: `` `Hello ${name}!` ``
787    Template(Vec<TsTemplatePart>),
788    /// Await expression: `await p`
789    Await(Box<TsExpr>),
790    /// Nullish coalescing: `a ?? b`
791    Nullish(Box<TsExpr>, Box<TsExpr>),
792    /// Optional chaining: `obj?.prop`
793    OptChain(Box<TsExpr>, std::string::String),
794}
795/// TypeScript statement AST node.
796#[derive(Debug, Clone, PartialEq)]
797pub enum TsStmt {
798    /// Expression statement: `expr;`
799    Expr(TsExpr),
800    /// `const x: T = expr;`
801    Const(std::string::String, Option<TsType>, TsExpr),
802    /// `let x: T = expr;`
803    Let(std::string::String, Option<TsType>, TsExpr),
804    /// `var x: T = expr;`
805    Var(std::string::String, Option<TsType>, TsExpr),
806    /// `if (cond) { then } else { else_ }`
807    If(TsExpr, Vec<TsStmt>, Vec<TsStmt>),
808    /// `switch (expr) { case x: ... default: ... }`
809    Switch(TsExpr, Vec<(TsExpr, Vec<TsStmt>)>, Vec<TsStmt>),
810    /// `for (let i = init; cond; step) { body }`
811    For(Box<TsStmt>, TsExpr, TsExpr, Vec<TsStmt>),
812    /// `for (const x of iter) { body }`
813    ForOf(std::string::String, TsExpr, Vec<TsStmt>),
814    /// `for (const k in obj) { body }`
815    ForIn(std::string::String, TsExpr, Vec<TsStmt>),
816    /// `while (cond) { body }`
817    While(TsExpr, Vec<TsStmt>),
818    /// `return expr;`
819    Return(TsExpr),
820    /// `throw expr;`
821    Throw(TsExpr),
822    /// `try { body } catch (e) { handler } finally { fin }`
823    TryCatch(Vec<TsStmt>, std::string::String, Vec<TsStmt>, Vec<TsStmt>),
824    /// `{ stmts }`
825    Block(Vec<TsStmt>),
826    /// `break;`
827    Break,
828    /// `continue;`
829    Continue,
830}
831/// A method in a TypeScript class.
832#[derive(Debug, Clone)]
833pub struct TsClassMethod {
834    pub name: std::string::String,
835    pub params: Vec<TsParam>,
836    pub return_type: TsType,
837    pub body: Vec<TsStmt>,
838    pub is_async: bool,
839    pub is_static: bool,
840    pub is_private: bool,
841    pub is_getter: bool,
842    pub is_setter: bool,
843}
844/// The TypeScript code generation backend.
845///
846/// Compiles OxiLean LCNF declarations into TypeScript source code.
847pub struct TypeScriptBackend {
848    pub(super) module: TsModule,
849}
850impl TypeScriptBackend {
851    /// Create a new TypeScript backend.
852    pub fn new() -> Self {
853        TypeScriptBackend {
854            module: TsModule::new(),
855        }
856    }
857    /// Add a declaration to the module.
858    pub fn add_declaration(&mut self, decl: TsDeclaration) {
859        self.module.declarations.push(decl);
860    }
861    /// Add an import to the module.
862    pub fn add_import(&mut self, imp: TsImport) {
863        if imp.is_type {
864            self.module.type_imports.push(imp);
865        } else {
866            self.module.imports.push(imp);
867        }
868    }
869    /// Build a discriminated union type from a list of variant names and their fields.
870    ///
871    /// Example:
872    /// ```text
873    /// type Shape =
874    ///   | { kind: 'circle'; radius: number }
875    ///   | { kind: 'rect'; w: number; h: number }
876    /// ```
877    pub fn make_discriminated_union(
878        &self,
879        type_name: &str,
880        variants: &[(&str, Vec<(&str, TsType)>)],
881    ) -> TsTypeAlias {
882        let union_types: Vec<TsType> = variants
883            .iter()
884            .map(|(variant_name, fields)| {
885                let mut members: Vec<(std::string::String, TsType)> = vec![(
886                    "kind".to_string(),
887                    TsType::Custom(format!("'{}'", variant_name)),
888                )];
889                for (field_name, field_ty) in fields {
890                    members.push((field_name.to_string(), field_ty.clone()));
891                }
892                TsType::Object(members)
893            })
894            .collect();
895        TsTypeAlias {
896            name: type_name.to_string(),
897            type_params: Vec::new(),
898            definition: TsType::Union(union_types),
899        }
900    }
901    /// Emit the full TypeScript module source.
902    pub fn emit_module(&self) -> std::string::String {
903        self.module.emit()
904    }
905    /// Emit a `.d.ts` declaration file.
906    pub fn emit_d_ts(&self) -> std::string::String {
907        self.module.emit_d_ts()
908    }
909}
910/// TypeScript type representation.
911#[derive(Debug, Clone, PartialEq, Eq, Hash)]
912pub enum TsType {
913    /// `number`
914    Number,
915    /// `string`
916    String,
917    /// `boolean`
918    Boolean,
919    /// `void`
920    Void,
921    /// `never`
922    Never,
923    /// `unknown`
924    Unknown,
925    /// `any`
926    Any,
927    /// `null`
928    Null,
929    /// `undefined`
930    Undefined,
931    /// `[T0, T1, ...]`
932    Tuple(Vec<TsType>),
933    /// `T[]`
934    Array(Box<TsType>),
935    /// `{ key: T; ... }`
936    Object(Vec<(std::string::String, TsType)>),
937    /// `T0 | T1 | ...`
938    Union(Vec<TsType>),
939    /// `T0 & T1 & ...`
940    Intersection(Vec<TsType>),
941    /// `(p0: T0, p1: T1, ...) => R`
942    Function {
943        params: Vec<TsType>,
944        ret: Box<TsType>,
945    },
946    /// A named type: `MyClass`, `SomeInterface`
947    Custom(std::string::String),
948    /// A generic type application: `Promise<T>`, `Map<K, V>`
949    Generic(std::string::String, Vec<TsType>),
950    /// `readonly T`
951    ReadOnly(Box<TsType>),
952    /// `Readonly<T>` utility type alias shorthand (no arg stored)
953    Readonly,
954}
955#[allow(dead_code)]
956#[derive(Debug, Clone)]
957pub struct TSPassConfig {
958    pub phase: TSPassPhase,
959    pub enabled: bool,
960    pub max_iterations: u32,
961    pub debug_output: bool,
962    pub pass_name: String,
963}
964impl TSPassConfig {
965    #[allow(dead_code)]
966    pub fn new(name: impl Into<String>, phase: TSPassPhase) -> Self {
967        TSPassConfig {
968            phase,
969            enabled: true,
970            max_iterations: 10,
971            debug_output: false,
972            pass_name: name.into(),
973        }
974    }
975    #[allow(dead_code)]
976    pub fn disabled(mut self) -> Self {
977        self.enabled = false;
978        self
979    }
980    #[allow(dead_code)]
981    pub fn with_debug(mut self) -> Self {
982        self.debug_output = true;
983        self
984    }
985    #[allow(dead_code)]
986    pub fn max_iter(mut self, n: u32) -> Self {
987        self.max_iterations = n;
988        self
989    }
990}
991/// Collects TsExt diagnostics.
992#[derive(Debug, Default)]
993pub struct TsExtDiagCollector {
994    pub(super) msgs: Vec<TsExtDiagMsg>,
995}
996impl TsExtDiagCollector {
997    pub fn new() -> Self {
998        TsExtDiagCollector::default()
999    }
1000    pub fn emit(&mut self, d: TsExtDiagMsg) {
1001        self.msgs.push(d);
1002    }
1003    pub fn has_errors(&self) -> bool {
1004        self.msgs
1005            .iter()
1006            .any(|d| d.severity == TsExtDiagSeverity::Error)
1007    }
1008    pub fn errors(&self) -> Vec<&TsExtDiagMsg> {
1009        self.msgs
1010            .iter()
1011            .filter(|d| d.severity == TsExtDiagSeverity::Error)
1012            .collect()
1013    }
1014    pub fn warnings(&self) -> Vec<&TsExtDiagMsg> {
1015        self.msgs
1016            .iter()
1017            .filter(|d| d.severity == TsExtDiagSeverity::Warning)
1018            .collect()
1019    }
1020    pub fn len(&self) -> usize {
1021        self.msgs.len()
1022    }
1023    pub fn is_empty(&self) -> bool {
1024        self.msgs.is_empty()
1025    }
1026    pub fn clear(&mut self) {
1027        self.msgs.clear();
1028    }
1029}
1030/// A TypeScript interface member.
1031#[derive(Debug, Clone, PartialEq)]
1032pub struct TsInterfaceMember {
1033    pub name: std::string::String,
1034    pub ty: TsType,
1035    pub optional: bool,
1036    pub readonly: bool,
1037}
1038/// A TypeScript `enum` or `const enum` declaration.
1039#[derive(Debug, Clone)]
1040pub struct TsEnum {
1041    pub name: std::string::String,
1042    pub is_const: bool,
1043    pub members: Vec<TsEnumMember>,
1044}
1045#[allow(dead_code)]
1046#[derive(Debug, Clone)]
1047pub struct TSDepGraph {
1048    pub(super) nodes: Vec<u32>,
1049    pub(super) edges: Vec<(u32, u32)>,
1050}
1051impl TSDepGraph {
1052    #[allow(dead_code)]
1053    pub fn new() -> Self {
1054        TSDepGraph {
1055            nodes: Vec::new(),
1056            edges: Vec::new(),
1057        }
1058    }
1059    #[allow(dead_code)]
1060    pub fn add_node(&mut self, id: u32) {
1061        if !self.nodes.contains(&id) {
1062            self.nodes.push(id);
1063        }
1064    }
1065    #[allow(dead_code)]
1066    pub fn add_dep(&mut self, dep: u32, dependent: u32) {
1067        self.add_node(dep);
1068        self.add_node(dependent);
1069        self.edges.push((dep, dependent));
1070    }
1071    #[allow(dead_code)]
1072    pub fn dependents_of(&self, node: u32) -> Vec<u32> {
1073        self.edges
1074            .iter()
1075            .filter(|(d, _)| *d == node)
1076            .map(|(_, dep)| *dep)
1077            .collect()
1078    }
1079    #[allow(dead_code)]
1080    pub fn dependencies_of(&self, node: u32) -> Vec<u32> {
1081        self.edges
1082            .iter()
1083            .filter(|(_, dep)| *dep == node)
1084            .map(|(d, _)| *d)
1085            .collect()
1086    }
1087    #[allow(dead_code)]
1088    pub fn topological_sort(&self) -> Vec<u32> {
1089        let mut in_degree: std::collections::HashMap<u32, u32> = std::collections::HashMap::new();
1090        for &n in &self.nodes {
1091            in_degree.insert(n, 0);
1092        }
1093        for (_, dep) in &self.edges {
1094            *in_degree.entry(*dep).or_insert(0) += 1;
1095        }
1096        let mut queue: std::collections::VecDeque<u32> = self
1097            .nodes
1098            .iter()
1099            .filter(|&&n| in_degree[&n] == 0)
1100            .copied()
1101            .collect();
1102        let mut result = Vec::new();
1103        while let Some(node) = queue.pop_front() {
1104            result.push(node);
1105            for dep in self.dependents_of(node) {
1106                let cnt = in_degree.entry(dep).or_insert(0);
1107                *cnt = cnt.saturating_sub(1);
1108                if *cnt == 0 {
1109                    queue.push_back(dep);
1110                }
1111            }
1112        }
1113        result
1114    }
1115    #[allow(dead_code)]
1116    pub fn has_cycle(&self) -> bool {
1117        self.topological_sort().len() < self.nodes.len()
1118    }
1119}
1120/// Tracks declared names for TsExt scope analysis.
1121#[derive(Debug, Default)]
1122pub struct TsExtNameScope {
1123    pub(super) declared: std::collections::HashSet<String>,
1124    pub(super) depth: usize,
1125    pub(super) parent: Option<Box<TsExtNameScope>>,
1126}
1127impl TsExtNameScope {
1128    pub fn new() -> Self {
1129        TsExtNameScope::default()
1130    }
1131    pub fn declare(&mut self, name: impl Into<String>) -> bool {
1132        self.declared.insert(name.into())
1133    }
1134    pub fn is_declared(&self, name: &str) -> bool {
1135        self.declared.contains(name)
1136    }
1137    pub fn push_scope(self) -> Self {
1138        TsExtNameScope {
1139            declared: std::collections::HashSet::new(),
1140            depth: self.depth + 1,
1141            parent: Some(Box::new(self)),
1142        }
1143    }
1144    pub fn pop_scope(self) -> Self {
1145        *self.parent.unwrap_or_default()
1146    }
1147    pub fn depth(&self) -> usize {
1148        self.depth
1149    }
1150    pub fn len(&self) -> usize {
1151        self.declared.len()
1152    }
1153}
1154/// A TypeScript `type` alias.
1155#[derive(Debug, Clone)]
1156pub struct TsTypeAlias {
1157    pub name: std::string::String,
1158    pub type_params: Vec<std::string::String>,
1159    pub definition: TsType,
1160}
1161#[allow(dead_code)]
1162#[derive(Debug, Clone)]
1163pub struct TSWorklist {
1164    pub(super) items: std::collections::VecDeque<u32>,
1165    pub(super) in_worklist: std::collections::HashSet<u32>,
1166}
1167impl TSWorklist {
1168    #[allow(dead_code)]
1169    pub fn new() -> Self {
1170        TSWorklist {
1171            items: std::collections::VecDeque::new(),
1172            in_worklist: std::collections::HashSet::new(),
1173        }
1174    }
1175    #[allow(dead_code)]
1176    pub fn push(&mut self, item: u32) -> bool {
1177        if self.in_worklist.insert(item) {
1178            self.items.push_back(item);
1179            true
1180        } else {
1181            false
1182        }
1183    }
1184    #[allow(dead_code)]
1185    pub fn pop(&mut self) -> Option<u32> {
1186        let item = self.items.pop_front()?;
1187        self.in_worklist.remove(&item);
1188        Some(item)
1189    }
1190    #[allow(dead_code)]
1191    pub fn is_empty(&self) -> bool {
1192        self.items.is_empty()
1193    }
1194    #[allow(dead_code)]
1195    pub fn len(&self) -> usize {
1196        self.items.len()
1197    }
1198    #[allow(dead_code)]
1199    pub fn contains(&self, item: u32) -> bool {
1200        self.in_worklist.contains(&item)
1201    }
1202}
1203/// Pipeline profiler for TsExt.
1204#[derive(Debug, Default)]
1205pub struct TsExtProfiler {
1206    pub(super) timings: Vec<TsExtPassTiming>,
1207}
1208impl TsExtProfiler {
1209    pub fn new() -> Self {
1210        TsExtProfiler::default()
1211    }
1212    pub fn record(&mut self, t: TsExtPassTiming) {
1213        self.timings.push(t);
1214    }
1215    pub fn total_elapsed_us(&self) -> u64 {
1216        self.timings.iter().map(|t| t.elapsed_us).sum()
1217    }
1218    pub fn slowest_pass(&self) -> Option<&TsExtPassTiming> {
1219        self.timings.iter().max_by_key(|t| t.elapsed_us)
1220    }
1221    pub fn num_passes(&self) -> usize {
1222        self.timings.len()
1223    }
1224    pub fn profitable_passes(&self) -> Vec<&TsExtPassTiming> {
1225        self.timings.iter().filter(|t| t.is_profitable()).collect()
1226    }
1227}
1228/// Pass-timing record for TsExt profiler.
1229#[derive(Debug, Clone)]
1230pub struct TsExtPassTiming {
1231    pub pass_name: String,
1232    pub elapsed_us: u64,
1233    pub items_processed: usize,
1234    pub bytes_before: usize,
1235    pub bytes_after: usize,
1236}
1237impl TsExtPassTiming {
1238    pub fn new(
1239        pass_name: impl Into<String>,
1240        elapsed_us: u64,
1241        items: usize,
1242        before: usize,
1243        after: usize,
1244    ) -> Self {
1245        TsExtPassTiming {
1246            pass_name: pass_name.into(),
1247            elapsed_us,
1248            items_processed: items,
1249            bytes_before: before,
1250            bytes_after: after,
1251        }
1252    }
1253    pub fn throughput_mps(&self) -> f64 {
1254        if self.elapsed_us == 0 {
1255            0.0
1256        } else {
1257            self.items_processed as f64 / (self.elapsed_us as f64 / 1_000_000.0)
1258        }
1259    }
1260    pub fn size_ratio(&self) -> f64 {
1261        if self.bytes_before == 0 {
1262            1.0
1263        } else {
1264            self.bytes_after as f64 / self.bytes_before as f64
1265        }
1266    }
1267    pub fn is_profitable(&self) -> bool {
1268        self.size_ratio() <= 1.05
1269    }
1270}
1271/// A generic key-value configuration store for TsExt.
1272#[derive(Debug, Clone, Default)]
1273pub struct TsExtConfig {
1274    pub(super) entries: std::collections::HashMap<String, String>,
1275}
1276impl TsExtConfig {
1277    pub fn new() -> Self {
1278        TsExtConfig::default()
1279    }
1280    pub fn set(&mut self, key: impl Into<String>, value: impl Into<String>) {
1281        self.entries.insert(key.into(), value.into());
1282    }
1283    pub fn get(&self, key: &str) -> Option<&str> {
1284        self.entries.get(key).map(|s| s.as_str())
1285    }
1286    pub fn get_bool(&self, key: &str) -> bool {
1287        matches!(self.get(key), Some("true") | Some("1") | Some("yes"))
1288    }
1289    pub fn get_int(&self, key: &str) -> Option<i64> {
1290        self.get(key)?.parse().ok()
1291    }
1292    pub fn len(&self) -> usize {
1293        self.entries.len()
1294    }
1295    pub fn is_empty(&self) -> bool {
1296        self.entries.is_empty()
1297    }
1298}
1299/// A TypeScript import statement.
1300#[derive(Debug, Clone)]
1301pub struct TsImport {
1302    pub names: Vec<std::string::String>,
1303    pub from: std::string::String,
1304    pub is_type: bool,
1305}
1306/// A monotonically increasing ID generator for TsExt.
1307#[derive(Debug, Default)]
1308pub struct TsExtIdGen {
1309    pub(super) next: u32,
1310}
1311impl TsExtIdGen {
1312    pub fn new() -> Self {
1313        TsExtIdGen::default()
1314    }
1315    pub fn next_id(&mut self) -> u32 {
1316        let id = self.next;
1317        self.next += 1;
1318        id
1319    }
1320    pub fn peek_next(&self) -> u32 {
1321        self.next
1322    }
1323    pub fn reset(&mut self) {
1324        self.next = 0;
1325    }
1326    pub fn skip(&mut self, n: u32) {
1327        self.next += n;
1328    }
1329}
1330/// A TypeScript `interface` declaration.
1331#[derive(Debug, Clone)]
1332pub struct TsInterface {
1333    pub name: std::string::String,
1334    /// Interfaces this interface extends.
1335    pub extends: Vec<std::string::String>,
1336    /// Members of the interface.
1337    pub members: Vec<TsInterfaceMember>,
1338    /// Generic type parameters: `<T, U extends string>`.
1339    pub type_params: Vec<std::string::String>,
1340}