Skip to main content

oxilean_codegen/nix_backend/
types.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use super::functions::*;
6use std::collections::{HashMap, HashSet, VecDeque};
7
8/// Structural validator for NixOS module expressions.
9#[allow(dead_code)]
10pub struct NixModuleValidator;
11impl NixModuleValidator {
12    /// Check that a NixOS module has the expected structure.
13    ///
14    /// A valid NixOS module is a lambda returning an attrset with
15    /// `options` and/or `config` keys.
16    pub fn validate(module: &NixModule) -> NixModuleValidationReport {
17        let mut report = NixModuleValidationReport::default();
18        match &module.top_level {
19            NixExpr::Lambda(func) => match &*func.body {
20                NixExpr::AttrSet(attrs) => {
21                    let has_options = attrs.iter().any(|a| a.name == "options");
22                    let has_config = attrs.iter().any(|a| a.name == "config");
23                    if !has_options && !has_config {
24                        report
25                            .warnings
26                            .push("Module body has neither 'options' nor 'config' keys".into());
27                    }
28                }
29                _ => {
30                    report
31                        .errors
32                        .push("NixOS module body should be an attribute set".into());
33                }
34            },
35            _ => {
36                if module.is_module_file {
37                    report
38                        .errors
39                        .push("NixOS module file should start with a lambda".into());
40                }
41            }
42        }
43        report
44    }
45}
46/// Helpers for emitting common `nixpkgs` patterns.
47#[allow(dead_code)]
48pub struct NixPkgsHelper;
49impl NixPkgsHelper {
50    /// Generate `pkgs.fetchurl { url = ...; sha256 = ...; }`.
51    pub fn fetch_url(url: &str, sha256: &str) -> NixExpr {
52        nix_apply(
53            nix_select(nix_var("pkgs"), "fetchurl"),
54            nix_set(vec![("url", nix_str(url)), ("sha256", nix_str(sha256))]),
55        )
56    }
57    /// Generate `pkgs.fetchFromGitHub { owner, repo, rev, sha256 }`.
58    pub fn fetch_from_github(owner: &str, repo: &str, rev: &str, sha256: &str) -> NixExpr {
59        nix_apply(
60            nix_select(nix_var("pkgs"), "fetchFromGitHub"),
61            nix_set(vec![
62                ("owner", nix_str(owner)),
63                ("repo", nix_str(repo)),
64                ("rev", nix_str(rev)),
65                ("sha256", nix_str(sha256)),
66            ]),
67        )
68    }
69    /// Generate `pkgs.fetchGit { url; rev; }`.
70    pub fn fetch_git(url: &str, rev: &str) -> NixExpr {
71        nix_apply(
72            nix_select(nix_var("pkgs"), "fetchGit"),
73            nix_set(vec![("url", nix_str(url)), ("rev", nix_str(rev))]),
74        )
75    }
76    /// Generate `pkgs.callPackage ./path.nix { }`.
77    pub fn call_package(path: &str) -> NixExpr {
78        nix_apply(
79            nix_apply(nix_select(nix_var("pkgs"), "callPackage"), nix_path(path)),
80            nix_set(vec![]),
81        )
82    }
83    /// Generate `pkgs.writeShellScriptBin name content`.
84    pub fn write_shell_script_bin(name: &str, content: &str) -> NixExpr {
85        nix_apply(
86            nix_apply(
87                nix_select(nix_var("pkgs"), "writeShellScriptBin"),
88                nix_str(name),
89            ),
90            NixExpr::Multiline(content.to_string()),
91        )
92    }
93    /// Generate `pkgs.writeText name content`.
94    pub fn write_text(name: &str, content: &str) -> NixExpr {
95        nix_apply(
96            nix_apply(nix_select(nix_var("pkgs"), "writeText"), nix_str(name)),
97            nix_str(content),
98        )
99    }
100    /// Generate a symlinkJoin package: `pkgs.symlinkJoin { name; paths = [...]; }`.
101    pub fn symlink_join(name: &str, paths: Vec<NixExpr>) -> NixExpr {
102        nix_apply(
103            nix_select(nix_var("pkgs"), "symlinkJoin"),
104            nix_set(vec![("name", nix_str(name)), ("paths", nix_list(paths))]),
105        )
106    }
107}
108/// Nix expression formatter with configurable options.
109#[allow(dead_code)]
110pub struct NixFormatter {
111    /// Indentation width (default 2).
112    pub indent: usize,
113    /// Maximum line length before wrapping (default 80).
114    pub max_line_len: usize,
115    /// Whether to sort attribute set keys alphabetically.
116    pub sort_attrs: bool,
117}
118impl NixFormatter {
119    /// Create a new formatter with default options.
120    pub fn new() -> Self {
121        NixFormatter {
122            indent: 2,
123            max_line_len: 80,
124            sort_attrs: true,
125        }
126    }
127    /// Format a NixExpr, optionally sorting attribute keys.
128    pub fn format(&self, expr: &NixExpr) -> String {
129        if self.sort_attrs {
130            let sorted = self.sort_attrset(expr);
131            sorted.emit(0)
132        } else {
133            expr.emit(0)
134        }
135    }
136    /// Recursively sort attribute set keys.
137    pub(super) fn sort_attrset(&self, expr: &NixExpr) -> NixExpr {
138        match expr {
139            NixExpr::AttrSet(attrs) => {
140                let mut sorted = attrs
141                    .iter()
142                    .map(|a| NixAttr {
143                        name: a.name.clone(),
144                        value: self.sort_attrset(&a.value),
145                    })
146                    .collect::<Vec<_>>();
147                sorted.sort_by(|a, b| a.name.cmp(&b.name));
148                NixExpr::AttrSet(sorted)
149            }
150            NixExpr::Rec(attrs) => {
151                let mut sorted = attrs
152                    .iter()
153                    .map(|a| NixAttr {
154                        name: a.name.clone(),
155                        value: self.sort_attrset(&a.value),
156                    })
157                    .collect::<Vec<_>>();
158                sorted.sort_by(|a, b| a.name.cmp(&b.name));
159                NixExpr::Rec(sorted)
160            }
161            other => other.clone(),
162        }
163    }
164    /// Estimate the rendered line length of an expression.
165    pub fn estimate_width(expr: &NixExpr) -> usize {
166        expr.emit(0).lines().map(|l| l.len()).max().unwrap_or(0)
167    }
168}
169#[allow(dead_code)]
170#[derive(Debug, Clone, PartialEq)]
171pub enum NixPassPhase {
172    Analysis,
173    Transformation,
174    Verification,
175    Cleanup,
176}
177impl NixPassPhase {
178    #[allow(dead_code)]
179    pub fn name(&self) -> &str {
180        match self {
181            NixPassPhase::Analysis => "analysis",
182            NixPassPhase::Transformation => "transformation",
183            NixPassPhase::Verification => "verification",
184            NixPassPhase::Cleanup => "cleanup",
185        }
186    }
187    #[allow(dead_code)]
188    pub fn is_modifying(&self) -> bool {
189        matches!(self, NixPassPhase::Transformation | NixPassPhase::Cleanup)
190    }
191}
192/// A simplified structural type checker for Nix expressions.
193///
194/// Nix is dynamically typed; this checker infers descriptive `NixType` tags.
195#[allow(dead_code)]
196pub struct NixTypeChecker;
197impl NixTypeChecker {
198    /// Infer the `NixType` of a NixExpr (best-effort, not full analysis).
199    pub fn infer(expr: &NixExpr) -> NixType {
200        match expr {
201            NixExpr::Int(_) => NixType::Int,
202            NixExpr::Float(_) => NixType::Float,
203            NixExpr::Bool(_) => NixType::Bool,
204            NixExpr::Str(_) | NixExpr::Antiquote(_, _, _) | NixExpr::Multiline(_) => {
205                NixType::String
206            }
207            NixExpr::Path(_) => NixType::Path,
208            NixExpr::Null => NixType::NullType,
209            NixExpr::List(items) => {
210                let elem_ty = items.first().map(Self::infer).unwrap_or(NixType::NullType);
211                NixType::List(Box::new(elem_ty))
212            }
213            NixExpr::AttrSet(attrs) | NixExpr::Rec(attrs) => NixType::AttrSet(
214                attrs
215                    .iter()
216                    .map(|a| (a.name.clone(), Self::infer(&a.value)))
217                    .collect(),
218            ),
219            NixExpr::Lambda(_) => {
220                NixType::Function(Box::new(NixType::NullType), Box::new(NixType::NullType))
221            }
222            NixExpr::If(_, t, f) => {
223                let ty_t = Self::infer(t);
224                let _ty_f = Self::infer(f);
225                ty_t
226            }
227            NixExpr::Let(_, body) => Self::infer(body),
228            NixExpr::With(_, body) => Self::infer(body),
229            NixExpr::Apply(_, _) => NixType::NullType,
230            NixExpr::Select(_, _, _) => NixType::NullType,
231            NixExpr::Import(_) => NixType::AttrSet(vec![]),
232            NixExpr::Var(_) => NixType::NullType,
233            NixExpr::UnOp(op, e) => {
234                if op == "!" {
235                    NixType::Bool
236                } else {
237                    Self::infer(e)
238                }
239            }
240            NixExpr::BinOp(op, lhs, _) => match op.as_str() {
241                "==" | "!=" | "<" | ">" | "<=" | ">=" | "&&" | "||" => NixType::Bool,
242                "++" => NixType::List(Box::new(NixType::NullType)),
243                "//" => NixType::AttrSet(vec![]),
244                _ => Self::infer(lhs),
245            },
246            _ => NixType::NullType,
247        }
248    }
249}
250#[allow(dead_code)]
251pub struct NixConstantFoldingHelper;
252impl NixConstantFoldingHelper {
253    #[allow(dead_code)]
254    pub fn fold_add_i64(a: i64, b: i64) -> Option<i64> {
255        a.checked_add(b)
256    }
257    #[allow(dead_code)]
258    pub fn fold_sub_i64(a: i64, b: i64) -> Option<i64> {
259        a.checked_sub(b)
260    }
261    #[allow(dead_code)]
262    pub fn fold_mul_i64(a: i64, b: i64) -> Option<i64> {
263        a.checked_mul(b)
264    }
265    #[allow(dead_code)]
266    pub fn fold_div_i64(a: i64, b: i64) -> Option<i64> {
267        if b == 0 {
268            None
269        } else {
270            a.checked_div(b)
271        }
272    }
273    #[allow(dead_code)]
274    pub fn fold_add_f64(a: f64, b: f64) -> f64 {
275        a + b
276    }
277    #[allow(dead_code)]
278    pub fn fold_mul_f64(a: f64, b: f64) -> f64 {
279        a * b
280    }
281    #[allow(dead_code)]
282    pub fn fold_neg_i64(a: i64) -> Option<i64> {
283        a.checked_neg()
284    }
285    #[allow(dead_code)]
286    pub fn fold_not_bool(a: bool) -> bool {
287        !a
288    }
289    #[allow(dead_code)]
290    pub fn fold_and_bool(a: bool, b: bool) -> bool {
291        a && b
292    }
293    #[allow(dead_code)]
294    pub fn fold_or_bool(a: bool, b: bool) -> bool {
295        a || b
296    }
297    #[allow(dead_code)]
298    pub fn fold_shl_i64(a: i64, b: u32) -> Option<i64> {
299        a.checked_shl(b)
300    }
301    #[allow(dead_code)]
302    pub fn fold_shr_i64(a: i64, b: u32) -> Option<i64> {
303        a.checked_shr(b)
304    }
305    #[allow(dead_code)]
306    pub fn fold_rem_i64(a: i64, b: i64) -> Option<i64> {
307        if b == 0 {
308            None
309        } else {
310            Some(a % b)
311        }
312    }
313    #[allow(dead_code)]
314    pub fn fold_bitand_i64(a: i64, b: i64) -> i64 {
315        a & b
316    }
317    #[allow(dead_code)]
318    pub fn fold_bitor_i64(a: i64, b: i64) -> i64 {
319        a | b
320    }
321    #[allow(dead_code)]
322    pub fn fold_bitxor_i64(a: i64, b: i64) -> i64 {
323        a ^ b
324    }
325    #[allow(dead_code)]
326    pub fn fold_bitnot_i64(a: i64) -> i64 {
327        !a
328    }
329}
330#[allow(dead_code)]
331#[derive(Debug, Clone)]
332pub struct NixWorklist {
333    pub(super) items: std::collections::VecDeque<u32>,
334    pub(super) in_worklist: std::collections::HashSet<u32>,
335}
336impl NixWorklist {
337    #[allow(dead_code)]
338    pub fn new() -> Self {
339        NixWorklist {
340            items: std::collections::VecDeque::new(),
341            in_worklist: std::collections::HashSet::new(),
342        }
343    }
344    #[allow(dead_code)]
345    pub fn push(&mut self, item: u32) -> bool {
346        if self.in_worklist.insert(item) {
347            self.items.push_back(item);
348            true
349        } else {
350            false
351        }
352    }
353    #[allow(dead_code)]
354    pub fn pop(&mut self) -> Option<u32> {
355        let item = self.items.pop_front()?;
356        self.in_worklist.remove(&item);
357        Some(item)
358    }
359    #[allow(dead_code)]
360    pub fn is_empty(&self) -> bool {
361        self.items.is_empty()
362    }
363    #[allow(dead_code)]
364    pub fn len(&self) -> usize {
365        self.items.len()
366    }
367    #[allow(dead_code)]
368    pub fn contains(&self, item: u32) -> bool {
369        self.in_worklist.contains(&item)
370    }
371}
372#[allow(dead_code)]
373pub struct NixPassRegistry {
374    pub(super) configs: Vec<NixPassConfig>,
375    pub(super) stats: std::collections::HashMap<String, NixPassStats>,
376}
377impl NixPassRegistry {
378    #[allow(dead_code)]
379    pub fn new() -> Self {
380        NixPassRegistry {
381            configs: Vec::new(),
382            stats: std::collections::HashMap::new(),
383        }
384    }
385    #[allow(dead_code)]
386    pub fn register(&mut self, config: NixPassConfig) {
387        self.stats
388            .insert(config.pass_name.clone(), NixPassStats::new());
389        self.configs.push(config);
390    }
391    #[allow(dead_code)]
392    pub fn enabled_passes(&self) -> Vec<&NixPassConfig> {
393        self.configs.iter().filter(|c| c.enabled).collect()
394    }
395    #[allow(dead_code)]
396    pub fn get_stats(&self, name: &str) -> Option<&NixPassStats> {
397        self.stats.get(name)
398    }
399    #[allow(dead_code)]
400    pub fn total_passes(&self) -> usize {
401        self.configs.len()
402    }
403    #[allow(dead_code)]
404    pub fn enabled_count(&self) -> usize {
405        self.enabled_passes().len()
406    }
407    #[allow(dead_code)]
408    pub fn update_stats(&mut self, name: &str, changes: u64, time_ms: u64, iter: u32) {
409        if let Some(stats) = self.stats.get_mut(name) {
410            stats.record_run(changes, time_ms, iter);
411        }
412    }
413}
414#[allow(dead_code)]
415#[derive(Debug, Clone)]
416pub struct NixCacheEntry {
417    pub key: String,
418    pub data: Vec<u8>,
419    pub timestamp: u64,
420    pub valid: bool,
421}
422#[allow(dead_code)]
423#[derive(Debug, Clone)]
424pub struct NixPassConfig {
425    pub phase: NixPassPhase,
426    pub enabled: bool,
427    pub max_iterations: u32,
428    pub debug_output: bool,
429    pub pass_name: String,
430}
431impl NixPassConfig {
432    #[allow(dead_code)]
433    pub fn new(name: impl Into<String>, phase: NixPassPhase) -> Self {
434        NixPassConfig {
435            phase,
436            enabled: true,
437            max_iterations: 10,
438            debug_output: false,
439            pass_name: name.into(),
440        }
441    }
442    #[allow(dead_code)]
443    pub fn disabled(mut self) -> Self {
444        self.enabled = false;
445        self
446    }
447    #[allow(dead_code)]
448    pub fn with_debug(mut self) -> Self {
449        self.debug_output = true;
450        self
451    }
452    #[allow(dead_code)]
453    pub fn max_iter(mut self, n: u32) -> Self {
454        self.max_iterations = n;
455        self
456    }
457}
458/// The Nix code generation backend.
459///
460/// Converts OxiLean IR constructs into Nix expression language output.
461pub struct NixBackend {
462    /// Indent width (default 2)
463    pub indent_width: usize,
464}
465impl NixBackend {
466    /// Create a new NixBackend with default settings.
467    pub fn new() -> Self {
468        NixBackend { indent_width: 2 }
469    }
470    /// Emit a NixExpr at the given indentation level.
471    pub fn emit_expr(&self, expr: &NixExpr, indent: usize) -> String {
472        expr.emit(indent)
473    }
474    /// Emit a complete NixModule as a `.nix` file string.
475    pub fn emit_module(&self, module: &NixModule) -> String {
476        module.emit()
477    }
478    /// Emit a NixAttr binding.
479    pub fn emit_attr(&self, attr: &NixAttr, indent: usize) -> String {
480        attr.emit(indent)
481    }
482    /// Emit a NixFunction.
483    pub fn emit_function(&self, func: &NixFunction, indent: usize) -> String {
484        func.emit(indent)
485    }
486    /// Build a `mkDerivation` call from common parameters.
487    #[allow(clippy::too_many_arguments)]
488    pub fn make_derivation(
489        &self,
490        name: &str,
491        version: &str,
492        src: NixExpr,
493        build_inputs: Vec<NixExpr>,
494        build_phase: Option<&str>,
495        install_phase: Option<&str>,
496        extra_attrs: Vec<NixAttr>,
497    ) -> NixExpr {
498        let mut attrs = vec![
499            NixAttr::new("name", NixExpr::Str(format!("{}-{}", name, version))),
500            NixAttr::new("version", NixExpr::Str(version.to_string())),
501            NixAttr::new("src", src),
502            NixAttr::new("buildInputs", NixExpr::List(build_inputs)),
503        ];
504        if let Some(bp) = build_phase {
505            attrs.push(NixAttr::new(
506                "buildPhase",
507                NixExpr::Multiline(bp.to_string()),
508            ));
509        }
510        if let Some(ip) = install_phase {
511            attrs.push(NixAttr::new(
512                "installPhase",
513                NixExpr::Multiline(ip.to_string()),
514            ));
515        }
516        attrs.extend(extra_attrs);
517        NixExpr::Apply(
518            Box::new(NixExpr::Select(
519                Box::new(NixExpr::Var("pkgs".into())),
520                "stdenv.mkDerivation".into(),
521                None,
522            )),
523            Box::new(NixExpr::AttrSet(attrs)),
524        )
525    }
526    /// Build a NixOS module skeleton with options and config sections.
527    pub fn make_nixos_module(
528        &self,
529        module_args: Vec<(String, Option<NixExpr>)>,
530        options: Vec<NixAttr>,
531        config: Vec<NixAttr>,
532    ) -> NixModule {
533        let body = NixExpr::AttrSet(vec![
534            NixAttr::new("options", NixExpr::AttrSet(options)),
535            NixAttr::new("config", NixExpr::AttrSet(config)),
536        ]);
537        let top = NixExpr::Lambda(Box::new(NixFunction {
538            pattern: NixPattern::AttrPattern {
539                attrs: module_args,
540                ellipsis: true,
541            },
542            body: Box::new(body),
543        }));
544        NixModule::nixos_module(top)
545    }
546    /// Build an overlay expression: `final: prev: { ... }`
547    pub fn make_overlay(&self, attrs: Vec<NixAttr>) -> NixExpr {
548        NixExpr::Lambda(Box::new(NixFunction {
549            pattern: NixPattern::Ident("final".into()),
550            body: Box::new(NixExpr::Lambda(Box::new(NixFunction {
551                pattern: NixPattern::Ident("prev".into()),
552                body: Box::new(NixExpr::AttrSet(attrs)),
553            }))),
554        }))
555    }
556    /// Build a `flake.nix`-style output expression skeleton.
557    pub fn make_flake(&self, description: &str, outputs_attrs: Vec<NixAttr>) -> NixExpr {
558        NixExpr::AttrSet(vec![
559            NixAttr::new("description", NixExpr::Str(description.to_string())),
560            NixAttr::new(
561                "outputs",
562                NixExpr::Lambda(Box::new(NixFunction {
563                    pattern: NixPattern::AttrPattern {
564                        attrs: vec![("self".into(), None), ("nixpkgs".into(), None)],
565                        ellipsis: true,
566                    },
567                    body: Box::new(NixExpr::AttrSet(outputs_attrs)),
568                })),
569            ),
570        ])
571    }
572}
573/// Helpers for generating flake.nix components.
574#[allow(dead_code)]
575pub struct NixFlakeHelper;
576impl NixFlakeHelper {
577    /// Generate `inputs.<name> = { url = "..."; }`.
578    pub fn input(name: &str, url: &str) -> NixAttr {
579        NixAttr::new(name, nix_set(vec![("url", nix_str(url))]))
580    }
581    /// Generate `inputs.<name> = { url = "..."; flake = false; }`.
582    pub fn non_flake_input(name: &str, url: &str) -> NixAttr {
583        NixAttr::new(
584            name,
585            nix_set(vec![("url", nix_str(url)), ("flake", nix_bool(false))]),
586        )
587    }
588    /// Generate an `inputs.nixpkgs.follows = "<other>"` override.
589    pub fn follows(input_name: &str, follows_from: &str) -> NixAttr {
590        NixAttr::new(
591            &format!("inputs.{}.follows", input_name),
592            nix_str(follows_from),
593        )
594    }
595    /// Generate a full `flake.nix` with inputs and outputs.
596    pub fn full_flake(
597        description: &str,
598        inputs: Vec<NixAttr>,
599        output_body: NixExpr,
600        input_args: Vec<(String, Option<NixExpr>)>,
601    ) -> NixExpr {
602        NixExpr::AttrSet(vec![
603            NixAttr::new("description", nix_str(description)),
604            NixAttr::new("inputs", NixExpr::AttrSet(inputs)),
605            NixAttr::new(
606                "outputs",
607                NixExpr::Lambda(Box::new(NixFunction {
608                    pattern: NixPattern::AttrPattern {
609                        attrs: input_args,
610                        ellipsis: true,
611                    },
612                    body: Box::new(output_body),
613                })),
614            ),
615        ])
616    }
617    /// Generate `perSystem = system: let pkgs = nixpkgs.legacyPackages.${system}; in ...`.
618    pub fn per_system_let(body: NixExpr) -> NixExpr {
619        NixExpr::Lambda(Box::new(NixFunction {
620            pattern: NixPattern::Ident("system".into()),
621            body: Box::new(nix_let(
622                vec![(
623                    "pkgs",
624                    NixExpr::Select(
625                        Box::new(NixExpr::Select(
626                            Box::new(nix_var("nixpkgs")),
627                            "legacyPackages".into(),
628                            None,
629                        )),
630                        "system".into(),
631                        None,
632                    ),
633                )],
634                body,
635            )),
636        }))
637    }
638}
639#[allow(dead_code)]
640#[derive(Debug, Clone)]
641pub struct NixAnalysisCache {
642    pub(super) entries: std::collections::HashMap<String, NixCacheEntry>,
643    pub(super) max_size: usize,
644    pub(super) hits: u64,
645    pub(super) misses: u64,
646}
647impl NixAnalysisCache {
648    #[allow(dead_code)]
649    pub fn new(max_size: usize) -> Self {
650        NixAnalysisCache {
651            entries: std::collections::HashMap::new(),
652            max_size,
653            hits: 0,
654            misses: 0,
655        }
656    }
657    #[allow(dead_code)]
658    pub fn get(&mut self, key: &str) -> Option<&NixCacheEntry> {
659        if self.entries.contains_key(key) {
660            self.hits += 1;
661            self.entries.get(key)
662        } else {
663            self.misses += 1;
664            None
665        }
666    }
667    #[allow(dead_code)]
668    pub fn insert(&mut self, key: String, data: Vec<u8>) {
669        if self.entries.len() >= self.max_size {
670            if let Some(oldest) = self.entries.keys().next().cloned() {
671                self.entries.remove(&oldest);
672            }
673        }
674        self.entries.insert(
675            key.clone(),
676            NixCacheEntry {
677                key,
678                data,
679                timestamp: 0,
680                valid: true,
681            },
682        );
683    }
684    #[allow(dead_code)]
685    pub fn invalidate(&mut self, key: &str) {
686        if let Some(entry) = self.entries.get_mut(key) {
687            entry.valid = false;
688        }
689    }
690    #[allow(dead_code)]
691    pub fn clear(&mut self) {
692        self.entries.clear();
693    }
694    #[allow(dead_code)]
695    pub fn hit_rate(&self) -> f64 {
696        let total = self.hits + self.misses;
697        if total == 0 {
698            return 0.0;
699        }
700        self.hits as f64 / total as f64
701    }
702    #[allow(dead_code)]
703    pub fn size(&self) -> usize {
704        self.entries.len()
705    }
706}
707/// A Nix function definition: `pattern: body`
708#[derive(Debug, Clone, PartialEq)]
709pub struct NixFunction {
710    /// Argument pattern
711    pub pattern: NixPattern,
712    /// Function body expression
713    pub body: Box<NixExpr>,
714}
715impl NixFunction {
716    /// Create a simple single-argument function.
717    pub fn new(arg: impl Into<String>, body: NixExpr) -> Self {
718        NixFunction {
719            pattern: NixPattern::Ident(arg.into()),
720            body: Box::new(body),
721        }
722    }
723    /// Create a function with an attribute set pattern.
724    pub fn with_attr_pattern(
725        attrs: Vec<(String, Option<NixExpr>)>,
726        ellipsis: bool,
727        body: NixExpr,
728    ) -> Self {
729        NixFunction {
730            pattern: NixPattern::AttrPattern { attrs, ellipsis },
731            body: Box::new(body),
732        }
733    }
734    pub(super) fn emit(&self, indent: usize) -> String {
735        format!("{}: {}", self.pattern.emit(), self.body.emit(indent))
736    }
737}
738/// A single binding inside a `let` expression: `name = expr;`
739#[derive(Debug, Clone, PartialEq)]
740pub struct NixLetBinding {
741    /// Bound name
742    pub name: String,
743    /// Bound expression
744    pub value: NixExpr,
745}
746impl NixLetBinding {
747    pub fn new(name: impl Into<String>, value: NixExpr) -> Self {
748        NixLetBinding {
749            name: name.into(),
750            value,
751        }
752    }
753}
754#[allow(dead_code)]
755#[derive(Debug, Clone)]
756pub struct NixDominatorTree {
757    pub idom: Vec<Option<u32>>,
758    pub dom_children: Vec<Vec<u32>>,
759    pub dom_depth: Vec<u32>,
760}
761impl NixDominatorTree {
762    #[allow(dead_code)]
763    pub fn new(size: usize) -> Self {
764        NixDominatorTree {
765            idom: vec![None; size],
766            dom_children: vec![Vec::new(); size],
767            dom_depth: vec![0; size],
768        }
769    }
770    #[allow(dead_code)]
771    pub fn set_idom(&mut self, node: usize, idom: u32) {
772        self.idom[node] = Some(idom);
773    }
774    #[allow(dead_code)]
775    pub fn dominates(&self, a: usize, b: usize) -> bool {
776        if a == b {
777            return true;
778        }
779        let mut cur = b;
780        loop {
781            match self.idom[cur] {
782                Some(parent) if parent as usize == a => return true,
783                Some(parent) if parent as usize == cur => return false,
784                Some(parent) => cur = parent as usize,
785                None => return false,
786            }
787        }
788    }
789    #[allow(dead_code)]
790    pub fn depth(&self, node: usize) -> u32 {
791        self.dom_depth.get(node).copied().unwrap_or(0)
792    }
793}
794/// Helpers for generating Nix expressions dealing with nullable/optional values.
795#[allow(dead_code)]
796pub struct NixOptionalHelper;
797impl NixOptionalHelper {
798    /// Generate `if x == null then default else f x`.
799    pub fn map_nullable(value: NixExpr, default: NixExpr, f: NixExpr) -> NixExpr {
800        nix_if(
801            NixExpr::BinOp(
802                "==".into(),
803                Box::new(value.clone()),
804                Box::new(NixExpr::Null),
805            ),
806            default,
807            nix_apply(f, value),
808        )
809    }
810    /// Generate `if x == null then null else f x`.
811    pub fn fmap_nullable(value: NixExpr, f: NixExpr) -> NixExpr {
812        Self::map_nullable(value.clone(), NixExpr::Null, f)
813    }
814    /// Generate `x or default` (attribute access with fallback).
815    pub fn with_default(base: NixExpr, attr: &str, default: NixExpr) -> NixExpr {
816        NixExpr::Select(Box::new(base), attr.to_string(), Some(Box::new(default)))
817    }
818    /// Generate `lib.optionals cond list`.
819    pub fn optionals(cond: NixExpr, list: NixExpr) -> NixExpr {
820        nix_apply(
821            nix_apply(nix_select(nix_var("lib"), "optionals"), cond),
822            list,
823        )
824    }
825    /// Generate `lib.optional cond value`.
826    pub fn optional(cond: NixExpr, value: NixExpr) -> NixExpr {
827        nix_apply(
828            nix_apply(nix_select(nix_var("lib"), "optional"), cond),
829            value,
830        )
831    }
832}
833/// A single part of an interpolated Nix string.
834#[allow(dead_code)]
835#[derive(Debug, Clone)]
836pub enum NixStringPart {
837    /// A literal string fragment.
838    Literal(String),
839    /// An interpolated expression `${expr}`.
840    Interp(NixExpr),
841}
842/// Nix runtime type tags (Nix is dynamically typed, so these are descriptive).
843#[derive(Debug, Clone, PartialEq, Eq)]
844pub enum NixType {
845    /// Integer type: `42`
846    Int,
847    /// Float type: `3.14`
848    Float,
849    /// Boolean type: `true` / `false`
850    Bool,
851    /// String type: `"hello"`
852    String,
853    /// Path type: `./foo/bar` or `/nix/store/...`
854    Path,
855    /// The null type: `null`
856    NullType,
857    /// Homogeneous list type: `[ T ]`
858    List(Box<NixType>),
859    /// Attribute set type (optional field type map)
860    AttrSet(Vec<(String, NixType)>),
861    /// Function type: `T1 -> T2`
862    Function(Box<NixType>, Box<NixType>),
863    /// Derivation (a special attribute set produced by `derivation { ... }`)
864    Derivation,
865}
866/// Function argument pattern in Nix.
867#[derive(Debug, Clone, PartialEq)]
868pub enum NixPattern {
869    /// Simple identifier pattern: `x: body`
870    Ident(String),
871    /// Attribute set pattern: `{ a, b, c ? default, ... }: body`
872    AttrPattern {
873        /// Named attributes to destructure, with optional defaults
874        attrs: Vec<(String, Option<NixExpr>)>,
875        /// Whether `...` (ellipsis) is present to allow extra attributes
876        ellipsis: bool,
877    },
878    /// At-pattern combining set destructuring with a binding:
879    /// `{ a, b } @ pkg: body` (or `pkg @ { a, b }: body`)
880    AtPattern {
881        /// The identifier binding for the whole set
882        ident: String,
883        /// Whether the ident comes before or after the `@`
884        ident_first: bool,
885        /// Set pattern attributes
886        attrs: Vec<(String, Option<NixExpr>)>,
887        /// Whether `...` is present
888        ellipsis: bool,
889    },
890}
891impl NixPattern {
892    pub(super) fn emit(&self) -> String {
893        match self {
894            NixPattern::Ident(x) => x.clone(),
895            NixPattern::AttrPattern { attrs, ellipsis } => {
896                let mut parts: Vec<String> = attrs
897                    .iter()
898                    .map(|(name, default)| match default {
899                        None => name.clone(),
900                        Some(d) => format!("{} ? {}", name, d.emit(0)),
901                    })
902                    .collect();
903                if *ellipsis {
904                    parts.push("...".into());
905                }
906                format!("{{ {} }}", parts.join(", "))
907            }
908            NixPattern::AtPattern {
909                ident,
910                ident_first,
911                attrs,
912                ellipsis,
913            } => {
914                let mut parts: Vec<String> = attrs
915                    .iter()
916                    .map(|(name, default)| match default {
917                        None => name.clone(),
918                        Some(d) => format!("{} ? {}", name, d.emit(0)),
919                    })
920                    .collect();
921                if *ellipsis {
922                    parts.push("...".into());
923                }
924                let set_pat = format!("{{ {} }}", parts.join(", "));
925                if *ident_first {
926                    format!("{} @ {}", ident, set_pat)
927                } else {
928                    format!("{} @ {}", set_pat, ident)
929                }
930            }
931        }
932    }
933}
934/// Helpers for generating NixOS systemd service configurations.
935#[allow(dead_code)]
936pub struct NixSystemdHelper;
937impl NixSystemdHelper {
938    /// Generate a `systemd.services.<name> = { ... }` config block.
939    #[allow(clippy::too_many_arguments)]
940    pub fn make_service(
941        description: &str,
942        exec_start: &str,
943        after: Vec<&str>,
944        wants: Vec<&str>,
945        restart: &str,
946        user: Option<&str>,
947        extra_attrs: Vec<NixAttr>,
948    ) -> NixExpr {
949        let mut attrs = vec![
950            NixAttr::new("description", nix_str(description)),
951            NixAttr::new("after", nix_list(after.into_iter().map(nix_str).collect())),
952            NixAttr::new("wants", nix_list(wants.into_iter().map(nix_str).collect())),
953            NixAttr::new(
954                "serviceConfig",
955                NixExpr::AttrSet(vec![
956                    NixAttr::new("ExecStart", nix_str(exec_start)),
957                    NixAttr::new("Restart", nix_str(restart)),
958                ]),
959            ),
960        ];
961        if let Some(u) = user {
962            if let NixExpr::AttrSet(ref mut service_attrs) = attrs[3].value {
963                service_attrs.push(NixAttr::new("User", nix_str(u)));
964            }
965        }
966        attrs.extend(extra_attrs);
967        NixExpr::AttrSet(attrs)
968    }
969    /// Generate a `systemd.timers.<name>` configuration.
970    pub fn make_timer(on_calendar: &str, description: &str) -> NixExpr {
971        nix_set(vec![
972            ("description", nix_str(description)),
973            (
974                "timerConfig",
975                nix_set(vec![
976                    ("OnCalendar", nix_str(on_calendar)),
977                    ("Persistent", nix_bool(true)),
978                ]),
979            ),
980        ])
981    }
982}
983/// Nix expression AST.
984#[derive(Debug, Clone, PartialEq)]
985pub enum NixExpr {
986    /// Integer literal: `42`
987    Int(i64),
988    /// Float literal: `3.14`
989    Float(f64),
990    /// Boolean literal: `true` / `false`
991    Bool(bool),
992    /// String literal: `"hello, world"`
993    Str(String),
994    /// Path literal: `./path/to/file` or `/absolute/path`
995    Path(String),
996    /// `null`
997    Null,
998    /// List literal: `[ e1 e2 e3 ]`
999    List(Vec<NixExpr>),
1000    /// Attribute set: `{ a = 1; b = 2; }`
1001    AttrSet(Vec<NixAttr>),
1002    /// Recursive attribute set: `rec { a = 1; b = a + 1; }`
1003    Rec(Vec<NixAttr>),
1004    /// `with expr; body` — bring all attrs of `expr` into scope
1005    With(Box<NixExpr>, Box<NixExpr>),
1006    /// `let bindings in body`
1007    Let(Vec<NixLetBinding>, Box<NixExpr>),
1008    /// `if cond then t else f`
1009    If(Box<NixExpr>, Box<NixExpr>, Box<NixExpr>),
1010    /// Function abstraction: `pat: body`
1011    Lambda(Box<NixFunction>),
1012    /// Function application: `f arg`
1013    Apply(Box<NixExpr>, Box<NixExpr>),
1014    /// Attribute selection: `e.attr` or `e.attr or default`
1015    Select(Box<NixExpr>, String, Option<Box<NixExpr>>),
1016    /// `assert cond; body`
1017    Assert(Box<NixExpr>, Box<NixExpr>),
1018    /// Variable reference: `pkgs`, `lib.mkOption`, etc.
1019    Var(String),
1020    /// Inherit expression inside a set: `inherit (src) a b;`
1021    Inherit(Option<Box<NixExpr>>, Vec<String>),
1022    /// String interpolation antiquotation: `"prefix ${expr} suffix"`
1023    Antiquote(String, Box<NixExpr>, String),
1024    /// Multi-line (indented) string: `'' ... ''`
1025    Multiline(String),
1026    /// Unary operator: `!b`, `-n`
1027    UnOp(String, Box<NixExpr>),
1028    /// Binary operator: `a + b`, `a // b`, `a ++ b`, `a == b`, etc.
1029    BinOp(String, Box<NixExpr>, Box<NixExpr>),
1030    /// `builtins.import` or any `builtins.*` call
1031    Import(Box<NixExpr>),
1032}
1033impl NixExpr {
1034    /// Emit this expression as a Nix source string.
1035    pub fn emit(&self, indent: usize) -> String {
1036        let ind = " ".repeat(indent);
1037        let ind2 = " ".repeat(indent + 2);
1038        match self {
1039            NixExpr::Int(n) => n.to_string(),
1040            NixExpr::Float(f) => {
1041                let s = format!("{}", f);
1042                if s.contains('.') || s.contains('e') {
1043                    s
1044                } else {
1045                    format!("{}.0", s)
1046                }
1047            }
1048            NixExpr::Bool(b) => if *b { "true" } else { "false" }.into(),
1049            NixExpr::Str(s) => format!("\"{}\"", escape_nix_string(s)),
1050            NixExpr::Path(p) => p.clone(),
1051            NixExpr::Null => "null".into(),
1052            NixExpr::List(elems) => {
1053                if elems.is_empty() {
1054                    "[ ]".into()
1055                } else {
1056                    let items: Vec<String> = elems.iter().map(|e| e.emit(indent + 2)).collect();
1057                    format!(
1058                        "[\n{}{}\n{}]",
1059                        ind2,
1060                        items.join(format!("\n{}", ind2).as_str()),
1061                        ind
1062                    )
1063                }
1064            }
1065            NixExpr::AttrSet(attrs) => {
1066                if attrs.is_empty() {
1067                    "{ }".into()
1068                } else {
1069                    let lines: Vec<String> = attrs.iter().map(|a| a.emit(indent + 2)).collect();
1070                    format!("{{\n{}\n{}}}", lines.join("\n"), ind)
1071                }
1072            }
1073            NixExpr::Rec(attrs) => {
1074                if attrs.is_empty() {
1075                    "rec { }".into()
1076                } else {
1077                    let lines: Vec<String> = attrs.iter().map(|a| a.emit(indent + 2)).collect();
1078                    format!("rec {{\n{}\n{}}}", lines.join("\n"), ind)
1079                }
1080            }
1081            NixExpr::With(src, body) => {
1082                format!("with {};\n{}{}", src.emit(indent), ind, body.emit(indent))
1083            }
1084            NixExpr::Let(bindings, body) => {
1085                let mut out = "let\n".to_string();
1086                for b in bindings {
1087                    out.push_str(&format!(
1088                        "{}  {} = {};\n",
1089                        ind,
1090                        b.name,
1091                        b.value.emit(indent + 2)
1092                    ));
1093                }
1094                out.push_str(&format!("{}in\n{}{}", ind, ind2, body.emit(indent + 2)));
1095                out
1096            }
1097            NixExpr::If(cond, then_e, else_e) => {
1098                format!(
1099                    "if {}\nthen {}{}\nelse {}{}",
1100                    cond.emit(indent),
1101                    ind2,
1102                    then_e.emit(indent + 2),
1103                    ind2,
1104                    else_e.emit(indent + 2),
1105                )
1106            }
1107            NixExpr::Lambda(func) => func.emit(indent),
1108            NixExpr::Apply(func, arg) => {
1109                let fs = func.emit(indent);
1110                let as_ = arg_needs_parens(arg);
1111                if as_ {
1112                    format!("{} ({})", fs, arg.emit(indent))
1113                } else {
1114                    format!("{} {}", fs, arg.emit(indent))
1115                }
1116            }
1117            NixExpr::Select(expr, attr, default) => {
1118                let base = format!("{}.{}", expr.emit(indent), attr);
1119                match default {
1120                    None => base,
1121                    Some(d) => format!("{} or {}", base, d.emit(indent)),
1122                }
1123            }
1124            NixExpr::Assert(cond, body) => {
1125                format!(
1126                    "assert {};\n{}{}",
1127                    cond.emit(indent),
1128                    ind,
1129                    body.emit(indent)
1130                )
1131            }
1132            NixExpr::Var(name) => name.clone(),
1133            NixExpr::Inherit(src, names) => {
1134                let src_s = match src {
1135                    None => String::new(),
1136                    Some(s) => format!(" ({})", s.emit(indent)),
1137                };
1138                format!("inherit{} {};", src_s, names.join(" "))
1139            }
1140            NixExpr::Antiquote(prefix, expr, suffix) => {
1141                format!(
1142                    "\"{}${{{}}}{}\"",
1143                    escape_nix_string(prefix),
1144                    expr.emit(indent),
1145                    escape_nix_string(suffix)
1146                )
1147            }
1148            NixExpr::Multiline(content) => format!("''\n{}\n{}''", content, ind),
1149            NixExpr::UnOp(op, e) => format!("{}{}", op, e.emit(indent)),
1150            NixExpr::BinOp(op, lhs, rhs) => {
1151                format!("({} {} {})", lhs.emit(indent), op, rhs.emit(indent))
1152            }
1153            NixExpr::Import(path) => format!("import {}", path.emit(indent)),
1154        }
1155    }
1156}
1157/// Identifiers for Nix built-in functions.
1158#[allow(dead_code)]
1159#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1160pub enum NixBuiltin {
1161    IsInt,
1162    IsFloat,
1163    IsBool,
1164    IsString,
1165    IsPath,
1166    IsNull,
1167    IsList,
1168    IsAttrs,
1169    IsFunction,
1170    StringLength,
1171    SubString,
1172    Concat,
1173    ConcatStringsSep,
1174    ToString,
1175    ParseInt,
1176    ParseFloat,
1177    ToLower,
1178    ToUpper,
1179    HasSuffix,
1180    HasPrefix,
1181    StringSplit,
1182    ReplaceStrings,
1183    Length,
1184    Head,
1185    Tail,
1186    Filter,
1187    Map,
1188    FoldLeft,
1189    FoldRight,
1190    Concatmap,
1191    Elem,
1192    ElemAt,
1193    Flatten,
1194    Sort,
1195    Partition,
1196    GroupBy,
1197    ZipAttrsWith,
1198    Unique,
1199    Reversal,
1200    Intersect,
1201    SubtractLists,
1202    ListToAttrs,
1203    AttrNames,
1204    AttrValues,
1205    HasAttr,
1206    GetAttr,
1207    Intersect2,
1208    RemoveAttrs,
1209    MapAttrs,
1210    FilterAttrs,
1211    Foldl2,
1212    ToJSON,
1213    FromJSON,
1214    ToTOML,
1215    ReadFile,
1216    ReadDir,
1217    PathExists,
1218    BaseName,
1219    DirOf,
1220    ToPath,
1221    StorePath,
1222    DerivationStrict,
1223    PlaceholderOf,
1224    HashString,
1225    HashFile,
1226    TypeOf,
1227    Seq,
1228    DeepSeq,
1229    Trace,
1230    Abort,
1231    Throw,
1232    CurrentSystem,
1233    CurrentTime,
1234    NixVersion,
1235}
1236/// A complete Nix file (module).
1237///
1238/// In NixOS, module files have a specific structure:
1239/// ```nix
1240/// { config, pkgs, lib, ... }:
1241/// {
1242///   options = { ... };
1243///   config  = { ... };
1244/// }
1245/// ```
1246///
1247/// Plain Nix files are just a single expression.
1248#[derive(Debug, Clone, PartialEq)]
1249pub struct NixModule {
1250    /// The top-level expression of the file
1251    pub top_level: NixExpr,
1252    /// Whether this is a NixOS module file (adds the standard module header comment)
1253    pub is_module_file: bool,
1254}
1255impl NixModule {
1256    /// Create a plain Nix expression file.
1257    pub fn new(top_level: NixExpr) -> Self {
1258        NixModule {
1259            top_level,
1260            is_module_file: false,
1261        }
1262    }
1263    /// Create a NixOS module file.
1264    pub fn nixos_module(top_level: NixExpr) -> Self {
1265        NixModule {
1266            top_level,
1267            is_module_file: true,
1268        }
1269    }
1270    /// Emit the complete `.nix` file contents.
1271    pub fn emit(&self) -> String {
1272        let mut out = String::new();
1273        if self.is_module_file {
1274            out.push_str("# NixOS module generated by OxiLean\n");
1275        } else {
1276            out.push_str("# Nix expression generated by OxiLean\n");
1277        }
1278        out.push_str(&self.top_level.emit(0));
1279        out.push('\n');
1280        out
1281    }
1282}
1283/// Builder for constructing Nix strings with multiple interpolated parts.
1284#[allow(dead_code)]
1285pub struct NixStringInterpolator {
1286    pub(super) parts: Vec<NixStringPart>,
1287}
1288impl NixStringInterpolator {
1289    /// Create a new interpolator.
1290    pub fn new() -> Self {
1291        NixStringInterpolator { parts: Vec::new() }
1292    }
1293    /// Append a literal string part.
1294    pub fn lit(mut self, s: &str) -> Self {
1295        self.parts.push(NixStringPart::Literal(s.to_string()));
1296        self
1297    }
1298    /// Append an interpolated expression part.
1299    pub fn interp(mut self, expr: NixExpr) -> Self {
1300        self.parts.push(NixStringPart::Interp(expr));
1301        self
1302    }
1303    /// Build the final NixExpr string.
1304    ///
1305    /// Produces a chain of `++` and `Antiquote` expressions, or a simple
1306    /// `Str` if there are no interpolated parts.
1307    pub fn build(self) -> NixExpr {
1308        if self.parts.is_empty() {
1309            return NixExpr::Str(String::new());
1310        }
1311        let mut collapsed: Vec<NixStringPart> = Vec::new();
1312        for part in self.parts {
1313            match (&mut collapsed.last_mut(), &part) {
1314                (Some(NixStringPart::Literal(prev)), NixStringPart::Literal(next)) => {
1315                    prev.push_str(next);
1316                }
1317                _ => collapsed.push(part),
1318            }
1319        }
1320        if collapsed.len() == 1 {
1321            if let NixStringPart::Literal(s) = &collapsed[0] {
1322                return NixExpr::Str(s.clone());
1323            }
1324        }
1325        if collapsed.len() == 3 {
1326            if let (
1327                NixStringPart::Literal(pre),
1328                NixStringPart::Interp(expr),
1329                NixStringPart::Literal(post),
1330            ) = (&collapsed[0], &collapsed[1], &collapsed[2])
1331            {
1332                return NixExpr::Antiquote(pre.clone(), Box::new(expr.clone()), post.clone());
1333            }
1334        }
1335        let mut result: NixExpr = match &collapsed[0] {
1336            NixStringPart::Literal(s) => NixExpr::Str(s.clone()),
1337            NixStringPart::Interp(e) => e.clone(),
1338        };
1339        for part in &collapsed[1..] {
1340            let part_expr = match part {
1341                NixStringPart::Literal(s) => NixExpr::Str(s.clone()),
1342                NixStringPart::Interp(e) => e.clone(),
1343            };
1344            result = NixExpr::BinOp("++".into(), Box::new(result), Box::new(part_expr));
1345        }
1346        result
1347    }
1348}
1349/// Statistics about a Nix expression tree.
1350#[allow(dead_code)]
1351#[derive(Debug, Clone, Default)]
1352pub struct NixExprStats {
1353    /// Total number of nodes.
1354    pub node_count: u64,
1355    /// Number of lambda nodes.
1356    pub lambdas: u64,
1357    /// Number of application nodes.
1358    pub applications: u64,
1359    /// Number of let-binding nodes.
1360    pub let_bindings: u64,
1361    /// Number of attribute set nodes.
1362    pub attr_sets: u64,
1363    /// Number of if-then-else nodes.
1364    pub conditionals: u64,
1365    /// Number of variable references.
1366    pub var_refs: u64,
1367    /// Number of literal nodes.
1368    pub literals: u64,
1369    /// Maximum nesting depth.
1370    pub max_depth: u64,
1371}
1372impl NixExprStats {
1373    /// Collect statistics from an expression.
1374    pub fn collect(expr: &NixExpr) -> Self {
1375        let mut stats = NixExprStats::default();
1376        stats.visit(expr, 0);
1377        stats
1378    }
1379    pub(super) fn visit(&mut self, expr: &NixExpr, depth: u64) {
1380        self.node_count += 1;
1381        if depth > self.max_depth {
1382            self.max_depth = depth;
1383        }
1384        match expr {
1385            NixExpr::Int(_)
1386            | NixExpr::Float(_)
1387            | NixExpr::Bool(_)
1388            | NixExpr::Str(_)
1389            | NixExpr::Path(_)
1390            | NixExpr::Null => {
1391                self.literals += 1;
1392            }
1393            NixExpr::Var(_) => {
1394                self.var_refs += 1;
1395            }
1396            NixExpr::Lambda(f) => {
1397                self.lambdas += 1;
1398                self.visit(&f.body, depth + 1);
1399            }
1400            NixExpr::Apply(f, arg) => {
1401                self.applications += 1;
1402                self.visit(f, depth + 1);
1403                self.visit(arg, depth + 1);
1404            }
1405            NixExpr::Let(bindings, body) => {
1406                self.let_bindings += bindings.len() as u64;
1407                for b in bindings {
1408                    self.visit(&b.value, depth + 1);
1409                }
1410                self.visit(body, depth + 1);
1411            }
1412            NixExpr::AttrSet(attrs) | NixExpr::Rec(attrs) => {
1413                self.attr_sets += 1;
1414                for a in attrs {
1415                    self.visit(&a.value, depth + 1);
1416                }
1417            }
1418            NixExpr::If(cond, t, f) => {
1419                self.conditionals += 1;
1420                self.visit(cond, depth + 1);
1421                self.visit(t, depth + 1);
1422                self.visit(f, depth + 1);
1423            }
1424            NixExpr::With(src, body) | NixExpr::Assert(src, body) => {
1425                self.visit(src, depth + 1);
1426                self.visit(body, depth + 1);
1427            }
1428            NixExpr::BinOp(_, lhs, rhs) => {
1429                self.visit(lhs, depth + 1);
1430                self.visit(rhs, depth + 1);
1431            }
1432            NixExpr::UnOp(_, e) => {
1433                self.visit(e, depth + 1);
1434            }
1435            NixExpr::Select(e, _, default) => {
1436                self.visit(e, depth + 1);
1437                if let Some(d) = default {
1438                    self.visit(d, depth + 1);
1439                }
1440            }
1441            NixExpr::List(items) => {
1442                for item in items {
1443                    self.visit(item, depth + 1);
1444                }
1445            }
1446            _ => {}
1447        }
1448    }
1449}
1450#[allow(dead_code)]
1451#[derive(Debug, Clone)]
1452pub struct NixDepGraph {
1453    pub(super) nodes: Vec<u32>,
1454    pub(super) edges: Vec<(u32, u32)>,
1455}
1456impl NixDepGraph {
1457    #[allow(dead_code)]
1458    pub fn new() -> Self {
1459        NixDepGraph {
1460            nodes: Vec::new(),
1461            edges: Vec::new(),
1462        }
1463    }
1464    #[allow(dead_code)]
1465    pub fn add_node(&mut self, id: u32) {
1466        if !self.nodes.contains(&id) {
1467            self.nodes.push(id);
1468        }
1469    }
1470    #[allow(dead_code)]
1471    pub fn add_dep(&mut self, dep: u32, dependent: u32) {
1472        self.add_node(dep);
1473        self.add_node(dependent);
1474        self.edges.push((dep, dependent));
1475    }
1476    #[allow(dead_code)]
1477    pub fn dependents_of(&self, node: u32) -> Vec<u32> {
1478        self.edges
1479            .iter()
1480            .filter(|(d, _)| *d == node)
1481            .map(|(_, dep)| *dep)
1482            .collect()
1483    }
1484    #[allow(dead_code)]
1485    pub fn dependencies_of(&self, node: u32) -> Vec<u32> {
1486        self.edges
1487            .iter()
1488            .filter(|(_, dep)| *dep == node)
1489            .map(|(d, _)| *d)
1490            .collect()
1491    }
1492    #[allow(dead_code)]
1493    pub fn topological_sort(&self) -> Vec<u32> {
1494        let mut in_degree: std::collections::HashMap<u32, u32> = std::collections::HashMap::new();
1495        for &n in &self.nodes {
1496            in_degree.insert(n, 0);
1497        }
1498        for (_, dep) in &self.edges {
1499            *in_degree.entry(*dep).or_insert(0) += 1;
1500        }
1501        let mut queue: std::collections::VecDeque<u32> = self
1502            .nodes
1503            .iter()
1504            .filter(|&&n| in_degree[&n] == 0)
1505            .copied()
1506            .collect();
1507        let mut result = Vec::new();
1508        while let Some(node) = queue.pop_front() {
1509            result.push(node);
1510            for dep in self.dependents_of(node) {
1511                let cnt = in_degree.entry(dep).or_insert(0);
1512                *cnt = cnt.saturating_sub(1);
1513                if *cnt == 0 {
1514                    queue.push_back(dep);
1515                }
1516            }
1517        }
1518        result
1519    }
1520    #[allow(dead_code)]
1521    pub fn has_cycle(&self) -> bool {
1522        self.topological_sort().len() < self.nodes.len()
1523    }
1524}
1525#[allow(dead_code)]
1526#[derive(Debug, Clone)]
1527pub struct NixLivenessInfo {
1528    pub live_in: Vec<std::collections::HashSet<u32>>,
1529    pub live_out: Vec<std::collections::HashSet<u32>>,
1530    pub defs: Vec<std::collections::HashSet<u32>>,
1531    pub uses: Vec<std::collections::HashSet<u32>>,
1532}
1533impl NixLivenessInfo {
1534    #[allow(dead_code)]
1535    pub fn new(block_count: usize) -> Self {
1536        NixLivenessInfo {
1537            live_in: vec![std::collections::HashSet::new(); block_count],
1538            live_out: vec![std::collections::HashSet::new(); block_count],
1539            defs: vec![std::collections::HashSet::new(); block_count],
1540            uses: vec![std::collections::HashSet::new(); block_count],
1541        }
1542    }
1543    #[allow(dead_code)]
1544    pub fn add_def(&mut self, block: usize, var: u32) {
1545        if block < self.defs.len() {
1546            self.defs[block].insert(var);
1547        }
1548    }
1549    #[allow(dead_code)]
1550    pub fn add_use(&mut self, block: usize, var: u32) {
1551        if block < self.uses.len() {
1552            self.uses[block].insert(var);
1553        }
1554    }
1555    #[allow(dead_code)]
1556    pub fn is_live_in(&self, block: usize, var: u32) -> bool {
1557        self.live_in
1558            .get(block)
1559            .map(|s| s.contains(&var))
1560            .unwrap_or(false)
1561    }
1562    #[allow(dead_code)]
1563    pub fn is_live_out(&self, block: usize, var: u32) -> bool {
1564        self.live_out
1565            .get(block)
1566            .map(|s| s.contains(&var))
1567            .unwrap_or(false)
1568    }
1569}
1570#[allow(dead_code)]
1571#[derive(Debug, Clone, Default)]
1572pub struct NixPassStats {
1573    pub total_runs: u32,
1574    pub successful_runs: u32,
1575    pub total_changes: u64,
1576    pub time_ms: u64,
1577    pub iterations_used: u32,
1578}
1579impl NixPassStats {
1580    #[allow(dead_code)]
1581    pub fn new() -> Self {
1582        Self::default()
1583    }
1584    #[allow(dead_code)]
1585    pub fn record_run(&mut self, changes: u64, time_ms: u64, iterations: u32) {
1586        self.total_runs += 1;
1587        self.successful_runs += 1;
1588        self.total_changes += changes;
1589        self.time_ms += time_ms;
1590        self.iterations_used = iterations;
1591    }
1592    #[allow(dead_code)]
1593    pub fn average_changes_per_run(&self) -> f64 {
1594        if self.total_runs == 0 {
1595            return 0.0;
1596        }
1597        self.total_changes as f64 / self.total_runs as f64
1598    }
1599    #[allow(dead_code)]
1600    pub fn success_rate(&self) -> f64 {
1601        if self.total_runs == 0 {
1602            return 0.0;
1603        }
1604        self.successful_runs as f64 / self.total_runs as f64
1605    }
1606    #[allow(dead_code)]
1607    pub fn format_summary(&self) -> String {
1608        format!(
1609            "Runs: {}/{}, Changes: {}, Time: {}ms",
1610            self.successful_runs, self.total_runs, self.total_changes, self.time_ms
1611        )
1612    }
1613}
1614/// A single attribute binding inside an attribute set: `name = value;`
1615#[derive(Debug, Clone, PartialEq)]
1616pub struct NixAttr {
1617    /// Attribute name (may be dotted: `"a.b.c"` for nested sets)
1618    pub name: String,
1619    /// Attribute value
1620    pub value: NixExpr,
1621}
1622impl NixAttr {
1623    /// Create a new attribute binding.
1624    pub fn new(name: impl Into<String>, value: NixExpr) -> Self {
1625        NixAttr {
1626            name: name.into(),
1627            value,
1628        }
1629    }
1630    pub(super) fn emit(&self, indent: usize) -> String {
1631        format!(
1632            "{}{} = {};",
1633            " ".repeat(indent),
1634            self.name,
1635            self.value.emit(indent)
1636        )
1637    }
1638}
1639/// Validation report for a NixOS module.
1640#[allow(dead_code)]
1641#[derive(Debug, Clone, Default)]
1642pub struct NixModuleValidationReport {
1643    /// Errors that must be fixed.
1644    pub errors: Vec<String>,
1645    /// Warnings that are informational.
1646    pub warnings: Vec<String>,
1647}
1648impl NixModuleValidationReport {
1649    /// Returns true if there are no errors.
1650    pub fn is_valid(&self) -> bool {
1651        self.errors.is_empty()
1652    }
1653}
1654/// A simplified Nix runtime value, for evaluation / interpretation stubs.
1655#[allow(dead_code)]
1656#[derive(Debug, Clone, PartialEq)]
1657pub enum NixValue {
1658    /// Integer value.
1659    Int(i64),
1660    /// Float value.
1661    Float(f64),
1662    /// Boolean value.
1663    Bool(bool),
1664    /// String value.
1665    Str(String),
1666    /// Path value (stored as string internally).
1667    Path(String),
1668    /// Null value.
1669    Null,
1670    /// List of values.
1671    List(Vec<NixValue>),
1672    /// Attribute set mapping strings to values.
1673    AttrSet(Vec<(String, NixValue)>),
1674    /// Closure (unevaluated lambda).
1675    Closure(NixPattern, Box<NixExpr>),
1676    /// Thunk (unevaluated expression, lazy evaluation placeholder).
1677    Thunk(Box<NixExpr>),
1678    /// Derivation placeholder.
1679    Derivation(Box<NixValue>),
1680}
1681impl NixValue {
1682    /// Return the Nix type tag string for this value.
1683    pub fn type_name(&self) -> &'static str {
1684        match self {
1685            NixValue::Int(_) => "int",
1686            NixValue::Float(_) => "float",
1687            NixValue::Bool(_) => "bool",
1688            NixValue::Str(_) => "string",
1689            NixValue::Path(_) => "path",
1690            NixValue::Null => "null",
1691            NixValue::List(_) => "list",
1692            NixValue::AttrSet(_) => "set",
1693            NixValue::Closure(_, _) => "lambda",
1694            NixValue::Thunk(_) => "thunk",
1695            NixValue::Derivation(_) => "derivation",
1696        }
1697    }
1698    /// Check if this value is truthy in a boolean context.
1699    pub fn is_truthy(&self) -> bool {
1700        match self {
1701            NixValue::Bool(b) => *b,
1702            NixValue::Int(n) => *n != 0,
1703            NixValue::Null => false,
1704            NixValue::Str(s) => !s.is_empty(),
1705            NixValue::List(l) => !l.is_empty(),
1706            NixValue::AttrSet(a) => !a.is_empty(),
1707            _ => true,
1708        }
1709    }
1710    /// Try to get an attribute from an AttrSet value.
1711    pub fn get_attr(&self, key: &str) -> Option<&NixValue> {
1712        if let NixValue::AttrSet(attrs) = self {
1713            attrs.iter().find(|(k, _)| k == key).map(|(_, v)| v)
1714        } else {
1715            None
1716        }
1717    }
1718    /// Convert this value to a Nix expression (for round-tripping).
1719    pub fn to_expr(&self) -> NixExpr {
1720        match self {
1721            NixValue::Int(n) => NixExpr::Int(*n),
1722            NixValue::Float(f) => NixExpr::Float(*f),
1723            NixValue::Bool(b) => NixExpr::Bool(*b),
1724            NixValue::Str(s) => NixExpr::Str(s.clone()),
1725            NixValue::Path(p) => NixExpr::Path(p.clone()),
1726            NixValue::Null => NixExpr::Null,
1727            NixValue::List(items) => NixExpr::List(items.iter().map(|v| v.to_expr()).collect()),
1728            NixValue::AttrSet(attrs) => NixExpr::AttrSet(
1729                attrs
1730                    .iter()
1731                    .map(|(k, v)| NixAttr::new(k.clone(), v.to_expr()))
1732                    .collect(),
1733            ),
1734            NixValue::Closure(pat, body) => NixExpr::Lambda(Box::new(NixFunction {
1735                pattern: pat.clone(),
1736                body: body.clone(),
1737            })),
1738            NixValue::Thunk(expr) => *expr.clone(),
1739            NixValue::Derivation(inner) => inner.to_expr(),
1740        }
1741    }
1742}
1743/// Helpers for emitting `lib.*` function calls.
1744#[allow(dead_code)]
1745pub struct NixLibHelper;
1746impl NixLibHelper {
1747    /// Generate `lib.mkOption { type; default; description; }`.
1748    pub fn mk_option(ty: NixExpr, default: Option<NixExpr>, description: Option<&str>) -> NixExpr {
1749        let mut attrs = vec![("type", ty)];
1750        if let Some(d) = default {
1751            attrs.push(("default", d));
1752        }
1753        if let Some(desc) = description {
1754            attrs.push(("description", nix_str(desc)));
1755        }
1756        nix_apply(nix_select(nix_var("lib"), "mkOption"), nix_set(attrs))
1757    }
1758    /// Generate `lib.mkIf cond value`.
1759    pub fn mk_if(cond: NixExpr, value: NixExpr) -> NixExpr {
1760        nix_apply(nix_apply(nix_select(nix_var("lib"), "mkIf"), cond), value)
1761    }
1762    /// Generate `lib.mkDefault value`.
1763    pub fn mk_default(value: NixExpr) -> NixExpr {
1764        nix_apply(nix_select(nix_var("lib"), "mkDefault"), value)
1765    }
1766    /// Generate `lib.mkForce value`.
1767    pub fn mk_force(value: NixExpr) -> NixExpr {
1768        nix_apply(nix_select(nix_var("lib"), "mkForce"), value)
1769    }
1770    /// Generate `lib.mkMerge [ ... ]`.
1771    pub fn mk_merge(items: Vec<NixExpr>) -> NixExpr {
1772        nix_apply(nix_select(nix_var("lib"), "mkMerge"), nix_list(items))
1773    }
1774    /// Generate `lib.types.str`.
1775    pub fn type_str() -> NixExpr {
1776        NixExpr::Select(
1777            Box::new(NixExpr::Select(
1778                Box::new(nix_var("lib")),
1779                "types".into(),
1780                None,
1781            )),
1782            "str".into(),
1783            None,
1784        )
1785    }
1786    /// Generate `lib.types.int`.
1787    pub fn type_int() -> NixExpr {
1788        NixExpr::Select(
1789            Box::new(NixExpr::Select(
1790                Box::new(nix_var("lib")),
1791                "types".into(),
1792                None,
1793            )),
1794            "int".into(),
1795            None,
1796        )
1797    }
1798    /// Generate `lib.types.bool`.
1799    pub fn type_bool() -> NixExpr {
1800        NixExpr::Select(
1801            Box::new(NixExpr::Select(
1802                Box::new(nix_var("lib")),
1803                "types".into(),
1804                None,
1805            )),
1806            "bool".into(),
1807            None,
1808        )
1809    }
1810    /// Generate `lib.types.listOf T`.
1811    pub fn type_list_of(ty: NixExpr) -> NixExpr {
1812        nix_apply(
1813            NixExpr::Select(
1814                Box::new(NixExpr::Select(
1815                    Box::new(nix_var("lib")),
1816                    "types".into(),
1817                    None,
1818                )),
1819                "listOf".into(),
1820                None,
1821            ),
1822            ty,
1823        )
1824    }
1825    /// Generate `lib.types.attrsOf T`.
1826    pub fn type_attrs_of(ty: NixExpr) -> NixExpr {
1827        nix_apply(
1828            NixExpr::Select(
1829                Box::new(NixExpr::Select(
1830                    Box::new(nix_var("lib")),
1831                    "types".into(),
1832                    None,
1833                )),
1834                "attrsOf".into(),
1835                None,
1836            ),
1837            ty,
1838        )
1839    }
1840    /// Generate `lib.concatMapStrings f list`.
1841    pub fn concat_map_strings(f: NixExpr, list: NixExpr) -> NixExpr {
1842        nix_apply(
1843            nix_apply(nix_select(nix_var("lib"), "concatMapStrings"), f),
1844            list,
1845        )
1846    }
1847    /// Generate `lib.concatStringsSep sep list`.
1848    pub fn concat_strings_sep(sep: &str, list: NixExpr) -> NixExpr {
1849        nix_apply(
1850            nix_apply(nix_select(nix_var("lib"), "concatStringsSep"), nix_str(sep)),
1851            list,
1852        )
1853    }
1854    /// Generate `lib.mapAttrsToList f attrs`.
1855    pub fn map_attrs_to_list(f: NixExpr, attrs: NixExpr) -> NixExpr {
1856        nix_apply(
1857            nix_apply(nix_select(nix_var("lib"), "mapAttrsToList"), f),
1858            attrs,
1859        )
1860    }
1861    /// Generate `lib.attrByPath ["a" "b"] default attrs`.
1862    pub fn attr_by_path(path: Vec<&str>, default: NixExpr, attrs: NixExpr) -> NixExpr {
1863        nix_apply(
1864            nix_apply(
1865                nix_apply(
1866                    nix_select(nix_var("lib"), "attrByPath"),
1867                    nix_list(path.into_iter().map(nix_str).collect()),
1868                ),
1869                default,
1870            ),
1871            attrs,
1872        )
1873    }
1874}