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 !matches!(
71 instance.status,
72 ComponentInstanceStatus::Planned | ComponentInstanceStatus::StructuralTemplate
73 ) {
74 continue;
75 }
76 let Some(component) = model
77 .components
78 .iter()
79 .find(|component| component.id == instance.component)
80 else {
81 continue;
82 };
83 let mut component_storages = component
84 .state_fields
85 .iter()
86 .filter_map(|state| {
87 canonical_state_storage_record(model, instance.id.clone(), &storages, state)
88 })
89 .collect::<Vec<_>>();
90 component_storages.sort_by(|left, right| left.storage_id.cmp(&right.storage_id));
91 records.extend(component_storages);
92 }
93
94 StateInstanceStorageRegistry {
95 version: STATE_INSTANCE_STORAGE_REGISTRY_VERSION,
96 records,
97 }
98}
99
100fn canonical_state_storage_record(
101 model: &ApplicationSemanticModel,
102 component_instance_id: ComponentInstanceId,
103 storages: &BTreeMap<SemanticId, &IrStorage>,
104 state: &crate::StateField,
105) -> Option<StateInstanceStorageRecord> {
106 let storage = storages.get(&state.id)?;
107 let initial_value = storage.initial_value.clone()?;
108 let semantic_type = model.semantic_type_of(&state.id)?.clone();
109 let component_id = state.owner.entity_id()?.clone();
110 Some(StateInstanceStorageRecord {
111 slot_id: StateInstanceSlotId::for_component_instance_storage(
112 component_instance_id.clone(),
113 storage.id.clone(),
114 ),
115 component_instance_id,
116 component_id,
117 state_id: state.id.clone(),
118 storage_id: storage.id.clone(),
119 initial_value,
120 serialization: serialization_compatibility(&semantic_type),
121 semantic_type,
122 provenance: storage.provenance.clone(),
123 })
124}
125
126pub fn validate_state_instance_storage_registry(
131 model: &ApplicationSemanticModel,
132 ir: &IntermediateRepresentation,
133 registry: &StateInstanceStorageRegistry,
134) -> Result<(), String> {
135 if registry.version != STATE_INSTANCE_STORAGE_REGISTRY_VERSION {
136 return Err("unsupported State instance storage registry version".to_string());
137 }
138 if registry != &build_state_instance_storage_registry(model, ir) {
139 return Err("State instance storage registry drifted from canonical products".to_string());
140 }
141 Ok(())
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147
148 #[test]
149 fn repeated_instances_receive_distinct_slots_for_one_ir_storage() {
150 let model = crate::build_application_semantic_model(&presolve_parser::parse_file(
151 "src/RepeatedState.tsx",
152 r#"@component("x-child") class Child { count = state(1); render() { return <button>{this.count}</button>; } }
153@component("x-parent") class Parent { render() { return <><Child /><Child /></>; } }"#,
154 ));
155 let ir = crate::lower_components_to_ir(&model);
156 let registry = build_state_instance_storage_registry(&model, &ir);
157 assert_eq!(registry.records.len(), 2);
158 assert_eq!(
159 registry.records[0].storage_id,
160 registry.records[1].storage_id
161 );
162 assert_ne!(registry.records[0].slot_id, registry.records[1].slot_id);
163 assert_eq!(
164 registry
165 .records_for_instance(®istry.records[0].component_instance_id)
166 .count(),
167 1
168 );
169 assert_eq!(
170 registry
171 .record(
172 ®istry.records[1].component_instance_id,
173 ®istry.records[1].storage_id
174 )
175 .map(|record| &record.slot_id),
176 Some(®istry.records[1].slot_id)
177 );
178 assert!(validate_state_instance_storage_registry(&model, &ir, ®istry).is_ok());
179 }
180
181 #[test]
182 fn registry_is_deterministic_under_reversed_compilation_input() {
183 let first = presolve_parser::parse_file(
184 "src/Child.tsx",
185 r#"@component("x-child") class Child { count = state(1); render() { return <b>{this.count}</b>; } }"#,
186 );
187 let second = presolve_parser::parse_file(
188 "src/Page.tsx",
189 r#"@component("x-page") class Page { render() { return <><Child /><Child /></>; } }"#,
190 );
191 let forward = crate::build_application_semantic_model_for_unit(
192 &crate::CompilationUnit::from_parsed_files(vec![first.clone(), second.clone()]),
193 );
194 let reverse = crate::build_application_semantic_model_for_unit(
195 &crate::CompilationUnit::from_parsed_files(vec![second, first]),
196 );
197 assert_eq!(
198 build_state_instance_storage_registry(
199 &forward,
200 &crate::lower_components_to_ir(&forward)
201 ),
202 build_state_instance_storage_registry(
203 &reverse,
204 &crate::lower_components_to_ir(&reverse)
205 )
206 );
207 }
208}