Skip to main content

presolve_compiler/
computed_instance_slots.rs

1//! J1-C compiler-owned instance qualification for E12 computed runtime slots.
2
3use crate::{
4    build_runtime_computed_registry, ApplicationSemanticModel, ComponentInstanceId,
5    ComponentInstanceStatus, ComputedInstanceCacheSlotId, ComputedInstanceDirtySlotId,
6    IntermediateRepresentation, SemanticId,
7};
8
9pub const COMPUTED_INSTANCE_SLOT_REGISTRY_VERSION: u32 = 1;
10
11#[derive(Debug, Clone, PartialEq, Eq)]
12pub struct ComputedInstanceSlotRecord {
13    pub component_instance_id: ComponentInstanceId,
14    pub component_id: SemanticId,
15    pub computed_id: SemanticId,
16    pub cache_slot_id: ComputedInstanceCacheSlotId,
17    pub dirty_slot_id: ComputedInstanceDirtySlotId,
18    pub dirty_initial_value: bool,
19}
20
21#[derive(Debug, Clone, PartialEq, Eq)]
22pub struct ComputedInstanceSlotRegistry {
23    pub version: u32,
24    pub records: Vec<ComputedInstanceSlotRecord>,
25}
26
27#[must_use]
28pub fn build_computed_instance_slot_registry(
29    model: &ApplicationSemanticModel,
30    ir: &IntermediateRepresentation,
31) -> ComputedInstanceSlotRegistry {
32    let declaration_records = build_runtime_computed_registry(model, ir);
33    let mut records = Vec::new();
34    for instance in model.component_instance_plan.instances.values() {
35        if !matches!(
36            instance.status,
37            ComponentInstanceStatus::Planned | ComponentInstanceStatus::StructuralTemplate
38        ) {
39            continue;
40        }
41        for computed in model
42            .computed_values
43            .values()
44            .filter(|computed| computed.owner.entity_id() == Some(&instance.component))
45        {
46            let Some(declaration) = declaration_records.record(&computed.id) else {
47                continue;
48            };
49            records.push(ComputedInstanceSlotRecord {
50                component_instance_id: instance.id.clone(),
51                component_id: instance.component.clone(),
52                computed_id: computed.id.clone(),
53                cache_slot_id: ComputedInstanceCacheSlotId::for_component_instance_cache_slot(
54                    instance.id.clone(),
55                    declaration.cache_slot.clone(),
56                ),
57                dirty_slot_id: ComputedInstanceDirtySlotId::for_component_instance_dirty_flag(
58                    instance.id.clone(),
59                    declaration.dirty_flag.id.clone(),
60                ),
61                dirty_initial_value: declaration.dirty_flag.initial_value,
62            });
63        }
64    }
65    records.sort_by(|left, right| {
66        (&left.component_instance_id, &left.computed_id)
67            .cmp(&(&right.component_instance_id, &right.computed_id))
68    });
69    ComputedInstanceSlotRegistry {
70        version: COMPUTED_INSTANCE_SLOT_REGISTRY_VERSION,
71        records,
72    }
73}
74
75/// # Errors
76///
77/// Returns an error when a registry is not an exact immutable projection.
78pub fn validate_computed_instance_slot_registry(
79    model: &ApplicationSemanticModel,
80    ir: &IntermediateRepresentation,
81    registry: &ComputedInstanceSlotRegistry,
82) -> Result<(), String> {
83    if registry.version != COMPUTED_INSTANCE_SLOT_REGISTRY_VERSION {
84        return Err("unsupported computed instance slot registry version".to_string());
85    }
86    if registry != &build_computed_instance_slot_registry(model, ir) {
87        return Err("computed instance slot registry drifted from canonical products".to_string());
88    }
89    Ok(())
90}
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95
96    #[test]
97    fn projects_distinct_cache_and_dirty_slots_for_repeated_instances() {
98        let model = crate::build_application_semantic_model(&presolve_parser::parse_file(
99            "src/RepeatedComputed.tsx",
100            r#"@component("x-child") class Child { count = state(1); @computed() get doubled() { return this.count * 2; } render() { return <span>{this.doubled}</span>; } }
101@component("x-parent") class Parent { render() { return <><Child /><Child /></>; } }"#,
102        ));
103        let ir = crate::lower_components_to_ir(&model);
104        let registry = build_computed_instance_slot_registry(&model, &ir);
105        assert_eq!(registry.records.len(), 2);
106        assert_ne!(
107            registry.records[0].cache_slot_id,
108            registry.records[1].cache_slot_id
109        );
110        assert_ne!(
111            registry.records[0].dirty_slot_id,
112            registry.records[1].dirty_slot_id
113        );
114        assert_eq!(
115            registry.records[0].computed_id,
116            registry.records[1].computed_id
117        );
118        assert!(registry
119            .records
120            .iter()
121            .all(|record| record.dirty_initial_value));
122        assert!(validate_computed_instance_slot_registry(&model, &ir, &registry).is_ok());
123    }
124}