Skip to main content

presolve_compiler/
state_instance_storage.rs

1//! J1-A compiler-owned instance qualification for canonical State storage.
2
3use 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
123/// # Errors
124///
125/// Returns an error when the retained registry is not the exact canonical
126/// projection of component instances and definition-level IR storage.
127pub 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(&registry.records[0].component_instance_id)
163                .count(),
164            1
165        );
166        assert_eq!(
167            registry
168                .record(
169                    &registry.records[1].component_instance_id,
170                    &registry.records[1].storage_id
171                )
172                .map(|record| &record.slot_id),
173            Some(&registry.records[1].slot_id)
174        );
175        assert!(validate_state_instance_storage_registry(&model, &ir, &registry).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}