presolve_compiler/
context_inspection.rs1use std::collections::BTreeMap;
2
3use serde::Serialize;
4
5use crate::{
6 build_context_resume_plan, build_context_update_plan, build_runtime_context_registry,
7 lower_components_to_ir, optimize_context_ir, ApplicationSemanticModel, SemanticId,
8};
9
10#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
12pub struct ContextInspection {
13 pub target_context: Option<String>,
14 pub selected_source: Option<String>,
15 pub source_plan_status: Option<String>,
16 pub evaluation_batch: Option<u32>,
17 pub slot_id: Option<String>,
18 pub ir_function_id: Option<String>,
19 pub load_identity: Option<String>,
20 pub runtime_registration: bool,
21 pub action_update_membership: Vec<String>,
22 pub resumable: bool,
23}
24
25#[derive(Debug, Clone, PartialEq, Eq, Default)]
26pub struct ContextInspectionRegistry {
27 pub records: BTreeMap<SemanticId, ContextInspection>,
28}
29
30#[must_use]
31pub fn build_context_inspection_registry(
32 model: &ApplicationSemanticModel,
33) -> ContextInspectionRegistry {
34 let ir = lower_components_to_ir(model);
35 let optimized = optimize_context_ir(&ir);
36 let runtime = build_runtime_context_registry(model, &optimized);
37 let updates = build_context_update_plan(model, &optimized);
38 let resume = build_context_resume_plan(&runtime, &updates);
39 let mut records = BTreeMap::new();
40 for context in model.contexts.values() {
41 let source = runtime
42 .sources
43 .iter()
44 .find(|source| source.context == context.id);
45 records.insert(
46 context.id.as_semantic_id().clone(),
47 ContextInspection {
48 target_context: Some(context.id.to_string()),
49 selected_source: source.map(|source| source_id(&source.source)),
50 source_plan_status: source
51 .and_then(|source| model.context_evaluation.context_source_plan(&source.source))
52 .map(|entry| format!("{:?}", entry.status)),
53 evaluation_batch: source.map(|source| source.evaluation_batch.index),
54 slot_id: source.map(|source| source.slot.as_str().to_string()),
55 ir_function_id: source.map(|source| source.function.as_semantic_id().to_string()),
56 load_identity: None,
57 runtime_registration: source.is_some(),
58 action_update_membership: Vec::new(),
59 resumable: source.is_some(),
60 },
61 );
62 }
63 for provider in model.providers.values() {
64 let source_id_value = crate::ContextValueSourceId::Provider(provider.id.clone());
65 let source = runtime.source(&source_id_value);
66 records.insert(
67 provider.id.as_semantic_id().clone(),
68 ContextInspection {
69 target_context: Some(provider.context.to_string()),
70 selected_source: source.map(|_| source_id(&source_id_value)),
71 source_plan_status: model
72 .context_evaluation
73 .context_source_plan(&source_id_value)
74 .map(|entry| format!("{:?}", entry.status)),
75 evaluation_batch: source.map(|source| source.evaluation_batch.index),
76 slot_id: source.map(|source| source.slot.as_str().to_string()),
77 ir_function_id: source.map(|source| source.function.as_semantic_id().to_string()),
78 load_identity: None,
79 runtime_registration: source.is_some(),
80 action_update_membership: updates
81 .actions
82 .iter()
83 .filter(|action| action.invalidated_sources.contains(&source_id_value))
84 .map(|action| action.action_batch.to_string())
85 .collect(),
86 resumable: resume
87 .records
88 .iter()
89 .any(|record| record.source == source_id_value),
90 },
91 );
92 }
93 for consumer in model.consumers.values() {
94 let binding = optimized
95 .optimized_module
96 .context_ir
97 .context_consumer_binding(&consumer.id);
98 records.insert(
99 consumer.id.as_semantic_id().clone(),
100 ContextInspection {
101 target_context: consumer.context().map(ToString::to_string),
102 selected_source: binding.map(|binding| source_id(&binding.source)),
103 source_plan_status: binding
104 .and_then(|binding| {
105 model
106 .context_evaluation
107 .context_source_plan(&binding.source)
108 })
109 .map(|entry| format!("{:?}", entry.status)),
110 evaluation_batch: binding
111 .and_then(|binding| runtime.source(&binding.source))
112 .map(|source| source.evaluation_batch.index),
113 slot_id: binding.map(|binding| binding.slot.as_str().to_string()),
114 ir_function_id: None,
115 load_identity: binding.map(|binding| binding.load.id.as_semantic_id().to_string()),
116 runtime_registration: binding.is_some(),
117 action_update_membership: Vec::new(),
118 resumable: binding.is_some(),
119 },
120 );
121 }
122 ContextInspectionRegistry { records }
123}
124
125fn source_id(source: &crate::ContextValueSourceId) -> String {
126 match source {
127 crate::ContextValueSourceId::Provider(provider) => provider.to_string(),
128 crate::ContextValueSourceId::ContextDefault(context) => format!("{context}/default"),
129 }
130}
131
132#[cfg(test)]
133mod tests {
134 use crate::{build_application_semantic_model, build_context_inspection_registry, ProviderId};
135
136 #[test]
137 fn projects_context_provider_and_consumer_from_one_compiler_owned_registry() {
138 let model = build_application_semantic_model(&presolve_parser::parse_file(
139 "src/App.tsx",
140 r#"
141@component("x-app") class App extends Component {
142 @context() theme: string = "dark";
143 @provide(App.theme) providedTheme: string = "light";
144 @consume(App.theme) theme!: string;
145 render() { return <main />; }
146}
147"#,
148 ));
149 let component = &model.components[0].id;
150 let registry = build_context_inspection_registry(&model);
151 assert!(registry.records.contains_key(&component.context("theme")));
152 let provider = ProviderId::for_component(component, "providedTheme");
153 let record = registry
154 .records
155 .get(provider.as_semantic_id())
156 .expect("Provider inspection");
157 assert!(record.runtime_registration);
158 assert!(record.slot_id.is_some());
159 }
160}