Skip to main content

presolve_compiler/
runtime_context.rs

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
10/// Frozen G12 schema contract for compiler-owned Context runtime metadata.
11pub const RUNTIME_CONTEXT_REGISTRY_SCHEMA_CONTRACT_VERSION: u32 = 1;
12
13/// Immutable compiler-owned Context metadata. It is a projection of G9-G11
14/// products and is not a runtime Provider, Context, or dependency registry.
15#[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/// One executable Context source, keyed by its compiler-generated slot.
24#[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/// The two authored Context source kinds remain distinct at runtime.
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
44pub enum RuntimeContextSourceKind {
45    Provider,
46    ContextDefault,
47}
48
49/// One available Consumer's immutable, exact Context-slot binding.
50#[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/// One G9-scheduled initial Context evaluation batch.
64#[derive(Debug, Clone, PartialEq, Eq)]
65pub struct RuntimeContextEvaluationBatch {
66    pub id: ContextEvaluationBatchId,
67    pub sources: Vec<ContextValueSourceId>,
68}
69
70/// One compiler-side G12 registry validation failure.
71#[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/// Build immutable G12 runtime Context metadata from existing G9-G11 products.
92/// This never resolves a Provider, reads a Context name, or reconstructs a
93/// dependency/ownership graph.
94#[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/// Validate that the G12 registry is one exact, deterministic projection of
235/// G9-G11 facts. Validation does not recover or synthesize any missing record.
236#[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, &registry).is_empty());
340    }
341}