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 !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
75pub 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, ®istry).is_ok());
123 }
124}