Skip to main content

presolve_compiler/
context_inspection.rs

1use 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/// One shared compiler projection used by all Context inspection consumers.
11#[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}