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 !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
126/// # Errors
127///
128/// Returns an error when the retained registry is not the exact canonical
129/// projection of component instances and definition-level IR storage.
130pub 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(&registry.records[0].component_instance_id)
166                .count(),
167            1
168        );
169        assert_eq!(
170            registry
171                .record(
172                    &registry.records[1].component_instance_id,
173                    &registry.records[1].storage_id
174                )
175                .map(|record| &record.slot_id),
176            Some(&registry.records[1].slot_id)
177        );
178        assert!(validate_state_instance_storage_registry(&model, &ir, &registry).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}