1use std::collections::BTreeMap;
4
5use crate::{
6 serialization_compatibility, ApplicationSemanticModel, ComponentInstanceId,
7 ComponentInstanceStatus, IntermediateRepresentation, IrStorage, IrStorageId, SemanticId,
8 SemanticType, SerializableValue, SerializationCompatibility, SourceProvenance,
9 StateInstanceSlotId,
10};
11
12pub const STATE_INSTANCE_STORAGE_REGISTRY_VERSION: u32 = 1;
13
14#[derive(Debug, Clone, PartialEq, Eq)]
15pub struct StateInstanceStorageRecord {
16 pub slot_id: StateInstanceSlotId,
17 pub component_instance_id: ComponentInstanceId,
18 pub component_id: SemanticId,
19 pub state_id: SemanticId,
20 pub storage_id: IrStorageId,
21 pub initial_value: SerializableValue,
22 pub semantic_type: SemanticType,
23 pub serialization: SerializationCompatibility,
24 pub provenance: SourceProvenance,
25}
26
27#[derive(Debug, Clone, PartialEq, Eq)]
28pub struct StateInstanceStorageRegistry {
29 pub version: u32,
30 pub records: Vec<StateInstanceStorageRecord>,
31}
32
33impl StateInstanceStorageRegistry {
34 #[must_use]
35 pub fn record(
36 &self,
37 component_instance_id: &ComponentInstanceId,
38 storage_id: &IrStorageId,
39 ) -> Option<&StateInstanceStorageRecord> {
40 self.records.iter().find(|record| {
41 record.component_instance_id == *component_instance_id
42 && record.storage_id == *storage_id
43 })
44 }
45
46 pub fn records_for_instance<'a>(
47 &'a self,
48 component_instance_id: &'a ComponentInstanceId,
49 ) -> impl Iterator<Item = &'a StateInstanceStorageRecord> + 'a {
50 self.records
51 .iter()
52 .filter(move |record| record.component_instance_id == *component_instance_id)
53 }
54}
55
56#[must_use]
57pub fn build_state_instance_storage_registry(
58 model: &ApplicationSemanticModel,
59 ir: &IntermediateRepresentation,
60) -> StateInstanceStorageRegistry {
61 let storages = ir
62 .modules
63 .iter()
64 .flat_map(|module| &module.storages)
65 .map(|storage| (storage.semantic_origin.clone(), storage))
66 .collect::<BTreeMap<_, _>>();
67 let mut records = Vec::new();
68
69 for instance in model.component_instance_plan.instances.values() {
70 if instance.status != ComponentInstanceStatus::Planned {
71 continue;
72 }
73 let Some(component) = model
74 .components
75 .iter()
76 .find(|component| component.id == instance.component)
77 else {
78 continue;
79 };
80 let mut component_storages = component
81 .state_fields
82 .iter()
83 .filter_map(|state| {
84 canonical_state_storage_record(model, instance.id.clone(), &storages, state)
85 })
86 .collect::<Vec<_>>();
87 component_storages.sort_by(|left, right| left.storage_id.cmp(&right.storage_id));
88 records.extend(component_storages);
89 }
90
91 StateInstanceStorageRegistry {
92 version: STATE_INSTANCE_STORAGE_REGISTRY_VERSION,
93 records,
94 }
95}
96
97fn canonical_state_storage_record(
98 model: &ApplicationSemanticModel,
99 component_instance_id: ComponentInstanceId,
100 storages: &BTreeMap<SemanticId, &IrStorage>,
101 state: &crate::StateField,
102) -> Option<StateInstanceStorageRecord> {
103 let storage = storages.get(&state.id)?;
104 let initial_value = storage.initial_value.clone()?;
105 let semantic_type = model.semantic_type_of(&state.id)?.clone();
106 let component_id = state.owner.entity_id()?.clone();
107 Some(StateInstanceStorageRecord {
108 slot_id: StateInstanceSlotId::for_component_instance_storage(
109 component_instance_id.clone(),
110 storage.id.clone(),
111 ),
112 component_instance_id,
113 component_id,
114 state_id: state.id.clone(),
115 storage_id: storage.id.clone(),
116 initial_value,
117 serialization: serialization_compatibility(&semantic_type),
118 semantic_type,
119 provenance: storage.provenance.clone(),
120 })
121}
122
123pub fn validate_state_instance_storage_registry(
128 model: &ApplicationSemanticModel,
129 ir: &IntermediateRepresentation,
130 registry: &StateInstanceStorageRegistry,
131) -> Result<(), String> {
132 if registry.version != STATE_INSTANCE_STORAGE_REGISTRY_VERSION {
133 return Err("unsupported State instance storage registry version".to_string());
134 }
135 if registry != &build_state_instance_storage_registry(model, ir) {
136 return Err("State instance storage registry drifted from canonical products".to_string());
137 }
138 Ok(())
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144
145 #[test]
146 fn repeated_instances_receive_distinct_slots_for_one_ir_storage() {
147 let model = crate::build_application_semantic_model(&presolve_parser::parse_file(
148 "src/RepeatedState.tsx",
149 r#"@component("x-child") class Child { count = state(1); render() { return <button>{this.count}</button>; } }
150@component("x-parent") class Parent { render() { return <><Child /><Child /></>; } }"#,
151 ));
152 let ir = crate::lower_components_to_ir(&model);
153 let registry = build_state_instance_storage_registry(&model, &ir);
154 assert_eq!(registry.records.len(), 2);
155 assert_eq!(
156 registry.records[0].storage_id,
157 registry.records[1].storage_id
158 );
159 assert_ne!(registry.records[0].slot_id, registry.records[1].slot_id);
160 assert_eq!(
161 registry
162 .records_for_instance(®istry.records[0].component_instance_id)
163 .count(),
164 1
165 );
166 assert_eq!(
167 registry
168 .record(
169 ®istry.records[1].component_instance_id,
170 ®istry.records[1].storage_id
171 )
172 .map(|record| &record.slot_id),
173 Some(®istry.records[1].slot_id)
174 );
175 assert!(validate_state_instance_storage_registry(&model, &ir, ®istry).is_ok());
176 }
177
178 #[test]
179 fn registry_is_deterministic_under_reversed_compilation_input() {
180 let first = presolve_parser::parse_file(
181 "src/Child.tsx",
182 r#"@component("x-child") class Child { count = state(1); render() { return <b>{this.count}</b>; } }"#,
183 );
184 let second = presolve_parser::parse_file(
185 "src/Page.tsx",
186 r#"@component("x-page") class Page { render() { return <><Child /><Child /></>; } }"#,
187 );
188 let forward = crate::build_application_semantic_model_for_unit(
189 &crate::CompilationUnit::from_parsed_files(vec![first.clone(), second.clone()]),
190 );
191 let reverse = crate::build_application_semantic_model_for_unit(
192 &crate::CompilationUnit::from_parsed_files(vec![second, first]),
193 );
194 assert_eq!(
195 build_state_instance_storage_registry(
196 &forward,
197 &crate::lower_components_to_ir(&forward)
198 ),
199 build_state_instance_storage_registry(
200 &reverse,
201 &crate::lower_components_to_ir(&reverse)
202 )
203 );
204 }
205}