presolve_compiler/
runtime_computed.rs1use std::collections::{BTreeMap, BTreeSet};
2
3use crate::{
4 ApplicationSemanticModel, IntermediateRepresentation, SemanticId, SerializationCompatibility,
5 SourceProvenance,
6};
7
8#[derive(Debug, Clone, PartialEq, Eq, Default)]
10pub struct RuntimeComputedRegistry {
11 pub records: BTreeMap<SemanticId, RuntimeComputedRecord>,
12}
13
14#[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#[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
42pub type RuntimeComputedCacheSlot = ComputedCacheSlotId;
44
45#[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#[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#[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}