1use std::collections::BTreeSet;
2
3use crate::{
4 ApplicationSemanticModel, ContextConsumerAvailabilityStatus, ContextConsumerLoadId,
5 ContextEvaluationBatchId, ContextSerializationCompatibility, ContextSourceFunctionId,
6 ContextSourcePlanStatus, ContextValueSlotId, ContextValueSourceId, ExecutionBoundary,
7 OptimizedContextIrReport, SemanticId, SemanticTypeId, SourceProvenance,
8};
9
10pub const RUNTIME_CONTEXT_REGISTRY_SCHEMA_CONTRACT_VERSION: u32 = 1;
12
13#[derive(Debug, Clone, PartialEq, Eq)]
16pub struct RuntimeContextRegistry {
17 pub schema_contract_version: u32,
18 pub sources: Vec<RuntimeContextSourceRecord>,
19 pub consumers: Vec<RuntimeContextConsumerRecord>,
20 pub initial_batches: Vec<RuntimeContextEvaluationBatch>,
21}
22
23#[derive(Debug, Clone, PartialEq, Eq)]
25pub struct RuntimeContextSourceRecord {
26 pub source: ContextValueSourceId,
27 pub context: crate::ContextId,
28 pub owner_component: SemanticId,
29 pub function: ContextSourceFunctionId,
30 pub slot: ContextValueSlotId,
31 pub semantic_type: SemanticTypeId,
32 pub source_kind: RuntimeContextSourceKind,
33 pub required_state: Vec<SemanticId>,
34 pub required_computed: Vec<SemanticId>,
35 pub prerequisite_computed_batches: Vec<u32>,
36 pub evaluation_batch: ContextEvaluationBatchId,
37 pub boundary: ExecutionBoundary,
38 pub serialization: ContextSerializationCompatibility,
39 pub provenance: SourceProvenance,
40}
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq)]
44pub enum RuntimeContextSourceKind {
45 Provider,
46 ContextDefault,
47}
48
49#[derive(Debug, Clone, PartialEq, Eq)]
51pub struct RuntimeContextConsumerRecord {
52 pub consumer: crate::ConsumerId,
53 pub context: crate::ContextId,
54 pub owner_component: SemanticId,
55 pub selected_source: ContextValueSourceId,
56 pub slot: ContextValueSlotId,
57 pub load_identity: ContextConsumerLoadId,
58 pub semantic_type: SemanticTypeId,
59 pub source_batch: ContextEvaluationBatchId,
60 pub provenance: SourceProvenance,
61}
62
63#[derive(Debug, Clone, PartialEq, Eq)]
65pub struct RuntimeContextEvaluationBatch {
66 pub id: ContextEvaluationBatchId,
67 pub sources: Vec<ContextValueSourceId>,
68}
69
70#[derive(Debug, Clone, PartialEq, Eq)]
72pub struct RuntimeContextRegistryValidationDiagnostic {
73 pub code: &'static str,
74 pub message: String,
75}
76
77impl RuntimeContextRegistry {
78 #[must_use]
79 pub fn source(&self, source: &ContextValueSourceId) -> Option<&RuntimeContextSourceRecord> {
80 self.sources.iter().find(|record| record.source == *source)
81 }
82
83 #[must_use]
84 pub fn consumer(&self, consumer: &crate::ConsumerId) -> Option<&RuntimeContextConsumerRecord> {
85 self.consumers
86 .iter()
87 .find(|record| record.consumer == *consumer)
88 }
89}
90
91#[must_use]
95pub fn build_runtime_context_registry(
96 model: &ApplicationSemanticModel,
97 optimized: &OptimizedContextIrReport,
98) -> RuntimeContextRegistry {
99 let mut sources = optimized
100 .source_evaluations
101 .iter()
102 .filter_map(|evaluation| source_record(model, optimized, evaluation))
103 .collect::<Vec<_>>();
104 sources.sort_by(|left, right| left.source.cmp(&right.source));
105 let source_ids = sources
106 .iter()
107 .map(|record| record.source.clone())
108 .collect::<BTreeSet<_>>();
109
110 let mut consumers = optimized
111 .optimized_module
112 .context_ir
113 .consumer_bindings
114 .iter()
115 .filter_map(|binding| consumer_record(model, binding, &sources))
116 .collect::<Vec<_>>();
117 consumers.sort_by(|left, right| left.consumer.cmp(&right.consumer));
118
119 let initial_batches = model
120 .context_evaluation
121 .evaluation_batches
122 .iter()
123 .filter(|batch| {
124 batch
125 .sources
126 .iter()
127 .all(|source| source_ids.contains(source))
128 })
129 .map(|batch| RuntimeContextEvaluationBatch {
130 id: batch.id.clone(),
131 sources: batch.sources.clone(),
132 })
133 .collect();
134
135 RuntimeContextRegistry {
136 schema_contract_version: RUNTIME_CONTEXT_REGISTRY_SCHEMA_CONTRACT_VERSION,
137 sources,
138 consumers,
139 initial_batches,
140 }
141}
142
143fn source_record(
144 model: &ApplicationSemanticModel,
145 optimized: &OptimizedContextIrReport,
146 evaluation: &crate::OptimizedIrContextSourceEvaluation,
147) -> Option<RuntimeContextSourceRecord> {
148 let plan = model
149 .context_evaluation
150 .context_source_plan(&evaluation.source)?;
151 if plan.status != ContextSourcePlanStatus::Planned {
152 return None;
153 }
154 let function_count = optimized
155 .optimized_module
156 .modules
157 .iter()
158 .flat_map(|module| &module.functions)
159 .filter(|function| function.id == *evaluation.function.as_semantic_id())
160 .count();
161 if function_count != 1 {
162 return None;
163 }
164 let (semantic_type, source_kind, boundary, serialization) = match &evaluation.source {
165 ContextValueSourceId::Provider(provider) => {
166 let types = model.provider_types.get(provider)?;
167 (
168 types.inferred_value_type.clone(),
169 RuntimeContextSourceKind::Provider,
170 types.boundary,
171 types.serialization,
172 )
173 }
174 ContextValueSourceId::ContextDefault(context) => {
175 let types = model.context_types.get(context)?;
176 (
177 types.default_type.clone()?,
178 RuntimeContextSourceKind::ContextDefault,
179 types.boundary,
180 types.serialization,
181 )
182 }
183 };
184 (serialization == ContextSerializationCompatibility::Serializable).then_some(
185 RuntimeContextSourceRecord {
186 source: evaluation.source.clone(),
187 context: evaluation.context.clone(),
188 owner_component: plan.owner_component.clone(),
189 function: evaluation.function.clone(),
190 slot: evaluation.slot.clone(),
191 semantic_type,
192 source_kind,
193 required_state: plan.required_state.clone(),
194 required_computed: plan.required_computed.clone(),
195 prerequisite_computed_batches: evaluation.prerequisite_computed_batches.clone(),
196 evaluation_batch: evaluation.evaluation_batch.clone(),
197 boundary,
198 serialization,
199 provenance: evaluation.provenance.clone(),
200 },
201 )
202}
203
204fn consumer_record(
205 model: &ApplicationSemanticModel,
206 binding: &crate::IrContextConsumerBinding,
207 sources: &[RuntimeContextSourceRecord],
208) -> Option<RuntimeContextConsumerRecord> {
209 let availability = model
210 .context_evaluation
211 .context_consumer_availability(&binding.consumer)?;
212 if availability.status != ContextConsumerAvailabilityStatus::Available
213 || availability.selected_source.as_ref() != Some(&binding.source)
214 {
215 return None;
216 }
217 let consumer = model.consumers.get(&binding.consumer)?;
218 let source = sources
219 .iter()
220 .find(|source| source.source == binding.source)?;
221 Some(RuntimeContextConsumerRecord {
222 consumer: binding.consumer.clone(),
223 context: binding.context.clone(),
224 owner_component: consumer.owner.entity_id()?.clone(),
225 selected_source: binding.source.clone(),
226 slot: binding.slot.clone(),
227 load_identity: binding.load.id.clone(),
228 semantic_type: binding.semantic_type.clone(),
229 source_batch: source.evaluation_batch.clone(),
230 provenance: binding.provenance.clone(),
231 })
232}
233
234#[must_use]
237pub fn validate_runtime_context_registry(
238 model: &ApplicationSemanticModel,
239 optimized: &OptimizedContextIrReport,
240 registry: &RuntimeContextRegistry,
241) -> Vec<RuntimeContextRegistryValidationDiagnostic> {
242 let expected = build_runtime_context_registry(model, optimized);
243 let mut diagnostics = Vec::new();
244 if registry.schema_contract_version != RUNTIME_CONTEXT_REGISTRY_SCHEMA_CONTRACT_VERSION {
245 diagnostics.push(RuntimeContextRegistryValidationDiagnostic {
246 code: "PSCTX1200",
247 message: "Context runtime registry has an unsupported schema contract version"
248 .to_string(),
249 });
250 }
251 if registry.sources != expected.sources {
252 diagnostics.push(RuntimeContextRegistryValidationDiagnostic {
253 code: "PSCTX1201",
254 message: "Context runtime registry sources do not exactly join planned G9 and optimized G11 identities".to_string(),
255 });
256 }
257 if registry.consumers != expected.consumers {
258 diagnostics.push(RuntimeContextRegistryValidationDiagnostic {
259 code: "PSCTX1202",
260 message:
261 "Context runtime registry Consumers do not retain exact available G10 bindings"
262 .to_string(),
263 });
264 }
265 if registry.initial_batches != expected.initial_batches {
266 diagnostics.push(RuntimeContextRegistryValidationDiagnostic {
267 code: "PSCTX1203",
268 message: "Context runtime registry batches do not retain G9 scheduler order"
269 .to_string(),
270 });
271 }
272 diagnostics
273}
274
275#[cfg(test)]
276mod tests {
277 use crate::{
278 build_application_semantic_model, build_runtime_context_registry, lower_components_to_ir,
279 optimize_context_ir, validate_runtime_context_registry, ContextValueSourceId, ProviderId,
280 RuntimeContextSourceKind, RUNTIME_CONTEXT_REGISTRY_SCHEMA_CONTRACT_VERSION,
281 };
282
283 #[test]
284 fn projects_only_planned_sources_and_available_exact_slot_bindings() {
285 let model = build_application_semantic_model(&presolve_parser::parse_file(
286 "src/App.tsx",
287 r#"
288@component("x-app")
289class App extends Component {
290 count = state(1);
291 @context()
292 total!: number;
293 @provide(App.total)
294 providedTotal: number = this.count + 2;
295 @consume(App.total)
296 total!: number;
297 @context()
298 locale: string = "en";
299 @consume(App.locale)
300 locale!: string;
301 @context()
302 unused!: string;
303 @provide(App.unused)
304 unusedProvider: string = "unused";
305 @consume(App.missing)
306 missing!: string;
307 render() { return <main />; }
308}
309"#,
310 ));
311 let component = &model.components[0].id;
312 let total =
313 ContextValueSourceId::Provider(ProviderId::for_component(component, "providedTotal"));
314 let optimized = optimize_context_ir(&lower_components_to_ir(&model));
315 let registry = build_runtime_context_registry(&model, &optimized);
316
317 assert_eq!(
318 registry.schema_contract_version,
319 RUNTIME_CONTEXT_REGISTRY_SCHEMA_CONTRACT_VERSION
320 );
321 assert_eq!(registry.sources.len(), 2);
322 assert_eq!(registry.consumers.len(), 2);
323 assert!(registry.source(&total).is_some_and(|record| {
324 record.source_kind == RuntimeContextSourceKind::Provider
325 && record.required_state == vec![component.state_field("count")]
326 }));
327 assert!(registry.sources.iter().all(|record| record.source
328 != ContextValueSourceId::Provider(ProviderId::for_component(
329 component,
330 "unusedProvider"
331 ))));
332 assert!(registry.consumers.iter().all(|record| {
333 registry
334 .source(&record.selected_source)
335 .is_some_and(|source| {
336 source.slot == record.slot && source.evaluation_batch == record.source_batch
337 })
338 }));
339 assert!(validate_runtime_context_registry(&model, &optimized, ®istry).is_empty());
340 }
341}