presolve_compiler/
computed_instance_slots.rs1use 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 instance.status != ComponentInstanceStatus::Planned {
36 continue;
37 }
38 for computed in model
39 .computed_values
40 .values()
41 .filter(|computed| computed.owner.entity_id() == Some(&instance.component))
42 {
43 let Some(declaration) = declaration_records.record(&computed.id) else {
44 continue;
45 };
46 records.push(ComputedInstanceSlotRecord {
47 component_instance_id: instance.id.clone(),
48 component_id: instance.component.clone(),
49 computed_id: computed.id.clone(),
50 cache_slot_id: ComputedInstanceCacheSlotId::for_component_instance_cache_slot(
51 instance.id.clone(),
52 declaration.cache_slot.clone(),
53 ),
54 dirty_slot_id: ComputedInstanceDirtySlotId::for_component_instance_dirty_flag(
55 instance.id.clone(),
56 declaration.dirty_flag.id.clone(),
57 ),
58 dirty_initial_value: declaration.dirty_flag.initial_value,
59 });
60 }
61 }
62 records.sort_by(|left, right| {
63 (&left.component_instance_id, &left.computed_id)
64 .cmp(&(&right.component_instance_id, &right.computed_id))
65 });
66 ComputedInstanceSlotRegistry {
67 version: COMPUTED_INSTANCE_SLOT_REGISTRY_VERSION,
68 records,
69 }
70}
71
72pub fn validate_computed_instance_slot_registry(
76 model: &ApplicationSemanticModel,
77 ir: &IntermediateRepresentation,
78 registry: &ComputedInstanceSlotRegistry,
79) -> Result<(), String> {
80 if registry.version != COMPUTED_INSTANCE_SLOT_REGISTRY_VERSION {
81 return Err("unsupported computed instance slot registry version".to_string());
82 }
83 if registry != &build_computed_instance_slot_registry(model, ir) {
84 return Err("computed instance slot registry drifted from canonical products".to_string());
85 }
86 Ok(())
87}
88
89#[cfg(test)]
90mod tests {
91 use super::*;
92
93 #[test]
94 fn projects_distinct_cache_and_dirty_slots_for_repeated_instances() {
95 let model = crate::build_application_semantic_model(&presolve_parser::parse_file(
96 "src/RepeatedComputed.tsx",
97 r#"@component("x-child") class Child { count = state(1); @computed() get doubled() { return this.count * 2; } render() { return <span>{this.doubled}</span>; } }
98@component("x-parent") class Parent { render() { return <><Child /><Child /></>; } }"#,
99 ));
100 let ir = crate::lower_components_to_ir(&model);
101 let registry = build_computed_instance_slot_registry(&model, &ir);
102 assert_eq!(registry.records.len(), 2);
103 assert_ne!(
104 registry.records[0].cache_slot_id,
105 registry.records[1].cache_slot_id
106 );
107 assert_ne!(
108 registry.records[0].dirty_slot_id,
109 registry.records[1].dirty_slot_id
110 );
111 assert_eq!(
112 registry.records[0].computed_id,
113 registry.records[1].computed_id
114 );
115 assert!(registry
116 .records
117 .iter()
118 .all(|record| record.dirty_initial_value));
119 assert!(validate_computed_instance_slot_registry(&model, &ir, ®istry).is_ok());
120 }
121}