Skip to main content

presolve_compiler/
runtime_computed.rs

1use std::collections::{BTreeMap, BTreeSet};
2
3use crate::{
4    ApplicationSemanticModel, IntermediateRepresentation, SemanticId, SerializationCompatibility,
5    SourceProvenance,
6};
7
8/// Compiler-owned runtime registry for canonical computed values.
9#[derive(Debug, Clone, PartialEq, Eq, Default)]
10pub struct RuntimeComputedRegistry {
11    pub records: BTreeMap<SemanticId, RuntimeComputedRecord>,
12}
13
14/// Runtime metadata for one compiler-lowered computed evaluation.
15#[derive(Debug, Clone, PartialEq, Eq)]
16pub struct RuntimeComputedRecord {
17    pub computed: SemanticId,
18    pub cache_slot: ComputedCacheSlotId,
19    pub dirty_flag: RuntimeComputedDirtyFlag,
20    pub dependencies: Vec<SemanticId>,
21    pub evaluation_function: SemanticId,
22    pub serialization: SerializationCompatibility,
23    pub provenance: SourceProvenance,
24}
25
26/// Stable runtime cache-slot identity for one computed value.
27#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
28pub struct ComputedCacheSlotId(String);
29
30impl ComputedCacheSlotId {
31    #[must_use]
32    pub fn for_computed(computed: &SemanticId) -> Self {
33        Self(format!("{computed}/runtime:cache"))
34    }
35
36    #[must_use]
37    pub fn as_str(&self) -> &str {
38        &self.0
39    }
40}
41
42/// Compatibility spelling for the declaration-level E12 cache identity.
43pub type RuntimeComputedCacheSlot = ComputedCacheSlotId;
44
45/// Stable declaration-level dirty-flag identity for one computed value.
46#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
47pub struct ComputedDirtyFlagId(String);
48
49impl ComputedDirtyFlagId {
50    #[must_use]
51    pub fn for_computed(computed: &SemanticId) -> Self {
52        Self(format!("{computed}/runtime:dirty"))
53    }
54
55    #[must_use]
56    pub fn as_str(&self) -> &str {
57        &self.0
58    }
59}
60
61/// Stable runtime dirty-flag identity and compiler-provided initial state.
62#[derive(Debug, Clone, PartialEq, Eq)]
63pub struct RuntimeComputedDirtyFlag {
64    pub id: ComputedDirtyFlagId,
65    pub initial_value: bool,
66}
67
68impl RuntimeComputedDirtyFlag {
69    #[must_use]
70    pub fn for_computed(computed: &SemanticId) -> Self {
71        Self {
72            id: ComputedDirtyFlagId::for_computed(computed),
73            initial_value: true,
74        }
75    }
76}
77
78impl RuntimeComputedRegistry {
79    #[must_use]
80    pub fn record(&self, computed: &SemanticId) -> Option<&RuntimeComputedRecord> {
81        self.records.get(computed)
82    }
83}
84
85/// Build deterministic runtime computed records from canonical ASM and IR
86/// products.
87#[must_use]
88pub fn build_runtime_computed_registry(
89    model: &ApplicationSemanticModel,
90    ir: &IntermediateRepresentation,
91) -> RuntimeComputedRegistry {
92    let evaluations = ir
93        .modules
94        .iter()
95        .flat_map(|module| module.computed_evaluations.iter())
96        .map(|evaluation| (evaluation.computed.clone(), evaluation.function.clone()))
97        .collect::<BTreeMap<_, _>>();
98    let records = evaluations
99        .into_iter()
100        .filter_map(|(computed, evaluation_function)| {
101            let value = model.computed_values.get(&computed)?;
102            let computed_type = model.semantic_types.computed_values.get(&computed)?;
103            Some((
104                computed.clone(),
105                RuntimeComputedRecord {
106                    computed: computed.clone(),
107                    cache_slot: ComputedCacheSlotId::for_computed(&computed),
108                    dirty_flag: RuntimeComputedDirtyFlag::for_computed(&computed),
109                    dependencies: computed_dependencies(model, &computed),
110                    evaluation_function,
111                    serialization: computed_type.serialization,
112                    provenance: value.provenance.clone(),
113                },
114            ))
115        })
116        .collect();
117
118    RuntimeComputedRegistry { records }
119}
120
121fn computed_dependencies(
122    model: &ApplicationSemanticModel,
123    computed: &SemanticId,
124) -> Vec<SemanticId> {
125    model
126        .references_from(computed)
127        .into_iter()
128        .filter(|reference| {
129            matches!(
130                reference.kind,
131                crate::SemanticReferenceKind::ComputedState
132                    | crate::SemanticReferenceKind::ComputedComputed
133                    | crate::SemanticReferenceKind::ComputedResource
134            )
135        })
136        .map(|reference| reference.target.clone())
137        .collect::<BTreeSet<_>>()
138        .into_iter()
139        .collect()
140}
141
142#[cfg(test)]
143mod tests {
144    use crate::{
145        build_application_semantic_model, build_runtime_computed_registry, lower_components_to_ir,
146        SerializationCompatibility,
147    };
148
149    #[test]
150    fn builds_deterministic_runtime_records_from_computed_ir() {
151        let parsed = presolve_parser::parse_file(
152            "src/RuntimeComputed.tsx",
153            r#"
154@component("x-runtime-computed")
155class RuntimeComputed extends Component {
156  count = state(1);
157
158  @computed()
159  get doubled() { return this.count * 2; }
160
161  @computed()
162  get label() { return this.doubled + 1; }
163}
164"#,
165        );
166        let model = build_application_semantic_model(&parsed);
167        let component = &model.components[0];
168        let count = component.id.state_field("count");
169        let doubled = component.id.computed("doubled");
170        let label = component.id.computed("label");
171        let registry = build_runtime_computed_registry(&model, &lower_components_to_ir(&model));
172        let doubled_record = registry.record(&doubled).expect("doubled record");
173        let label_record = registry.record(&label).expect("label record");
174
175        assert_eq!(registry.records.len(), 2);
176        assert_eq!(doubled_record.dependencies, vec![count]);
177        assert_eq!(label_record.dependencies, vec![doubled.clone()]);
178        assert_eq!(doubled_record.evaluation_function, doubled);
179        assert_eq!(label_record.evaluation_function, label);
180        assert_eq!(
181            doubled_record.cache_slot.as_str(),
182            format!("{}/runtime:cache", doubled_record.computed)
183        );
184        assert!(doubled_record.dirty_flag.initial_value);
185        assert_eq!(
186            doubled_record.dirty_flag.id.as_str(),
187            format!("{}/runtime:dirty", doubled_record.computed)
188        );
189        assert_eq!(
190            label_record.serialization,
191            SerializationCompatibility::Serializable
192        );
193        assert_eq!(
194            doubled_record.provenance,
195            model
196                .computed_value(&doubled)
197                .expect("doubled value")
198                .provenance
199        );
200    }
201}