Skip to main content

helm_schema_ir/symbolic/
mod.rs

1use std::collections::BTreeMap;
2use std::rc::Rc;
3
4use helm_schema_ast::DefineIndex;
5
6use crate::analysis_db::IrAnalysisDb;
7use crate::contract::ContractIr;
8
9/// Immutable non-values inputs supplied by the Helm render environment.
10///
11/// These inputs may decide control flow, but never become `.Values` schema
12/// evidence themselves. Static root strings use path segments rather than a
13/// dotted spelling so literal map keys containing dots remain unambiguous.
14#[derive(Clone, Debug, Default)]
15pub struct SymbolicPolicy {
16    /// Chart-authored string defaults executed through `tpl`.
17    pub chart_default_strings: BTreeMap<String, String>,
18    /// Normalized Kubernetes version used by `.Capabilities.KubeVersion`.
19    pub kubernetes_version: Option<String>,
20    /// Exact scalar strings beneath immutable roots such as `.Chart`.
21    pub static_root_strings: BTreeMap<Vec<String>, String>,
22}
23
24/// Reusable state for generating symbolic IR across many templates that
25/// share one [`DefineIndex`].
26///
27/// The context owns exact parse/helper-analysis caches. Reusing it across
28/// templates avoids recomputing helper bodies without changing analysis
29/// semantics; a cache miss and cache hit return the same structural facts.
30#[derive(Clone)]
31pub struct SymbolicIrContext {
32    inner: Rc<SymbolicIrContextInner>,
33}
34
35struct SymbolicIrContextInner {
36    analysis_db: IrAnalysisDb,
37}
38
39impl SymbolicIrContext {
40    /// Creates a reusable symbolic-analysis context for indexed chart sources.
41    #[tracing::instrument(skip_all)]
42    pub fn new(defines: &DefineIndex) -> Self {
43        Self {
44            inner: Rc::new(SymbolicIrContextInner {
45                analysis_db: IrAnalysisDb::new(defines),
46            }),
47        }
48    }
49
50    /// Build a context that can execute chart-authored string defaults when
51    /// a `tpl` call selects them. The strings are not general inference
52    /// evidence; they are consulted only at that explicit execution boundary.
53    #[must_use]
54    pub fn with_chart_default_strings(
55        defines: &DefineIndex,
56        chart_default_strings: BTreeMap<String, String>,
57    ) -> Self {
58        Self::with_policy(
59            defines,
60            SymbolicPolicy {
61                chart_default_strings,
62                ..SymbolicPolicy::default()
63            },
64        )
65    }
66
67    /// Build a context carrying immutable render-policy inputs.
68    #[must_use]
69    pub fn with_policy(defines: &DefineIndex, policy: SymbolicPolicy) -> Self {
70        Self {
71            inner: Rc::new(SymbolicIrContextInner {
72                analysis_db: IrAnalysisDb::with_policy(defines, policy),
73            }),
74        }
75    }
76
77    /// Generate the opaque contract graph without finalizing it.
78    ///
79    /// Callers that need to combine, scope, or otherwise transform chart-local
80    /// contracts should use this method and derive schema facts with
81    /// [`ContractIr::finalize`]. Inspection output can finalize the graph once
82    /// and ask the resulting contract for its stable document.
83    #[must_use]
84    pub fn generate_contract_ir(&self, src: &str) -> ContractIr {
85        self.generate_contract_ir_with_provenance(src, None)
86    }
87
88    /// Builds contract IR while attaching a logical template source path.
89    #[must_use]
90    pub fn generate_contract_ir_for_source(&self, src: &str, source_path: &str) -> ContractIr {
91        self.generate_contract_ir_with_provenance(src, Some(source_path))
92    }
93
94    /// Evaluate a template into the abstract fragment domain, reusing this
95    /// context's memoized helper analyses.
96    #[must_use]
97    pub fn eval_document_fragment(&self, src: &str) -> crate::fragment_eval::EvaluatedDocument {
98        crate::fragment_eval::eval_document(src, None, &self.inner.analysis_db)
99    }
100
101    fn generate_contract_ir_with_provenance(
102        &self,
103        src: &str,
104        source_path: Option<&str>,
105    ) -> ContractIr {
106        let document =
107            crate::fragment_eval::eval_document(src, source_path, &self.inner.analysis_db);
108        let mut contract = crate::fragment_eval::contract_ir_from_document(&document);
109        for name in &document.observed_facts.values_root_helper_includes {
110            contract.extend_values_program_wrappers(
111                self.inner
112                    .analysis_db
113                    .program_wrapper_sentinels(name)
114                    .into_iter()
115                    .map(|(key, spread)| helm_schema_core::ValuesProgramWrapper {
116                        scope_path: String::new(),
117                        key,
118                        spread,
119                    }),
120            );
121        }
122        contract.extend_values_program_wrapper_exclusions(
123            document.pre_rewrite_strict_paths.iter().cloned(),
124        );
125        contract
126    }
127}