baobao_codegen/pipeline/
context.rs

1//! Compilation context passed through pipeline phases.
2
3use baobao_ir::AppIR;
4use baobao_manifest::Manifest;
5
6use super::diagnostic::{Diagnostic, Severity};
7use crate::schema::ComputedData;
8
9/// Context passed through all pipeline phases.
10///
11/// This struct carries the state of compilation through each phase,
12/// accumulating results and diagnostics along the way.
13#[derive(Debug)]
14pub struct CompilationContext {
15    /// The original manifest being compiled.
16    pub manifest: Manifest,
17    /// The lowered Application IR (populated by LowerPhase).
18    pub ir: Option<AppIR>,
19    /// Pre-computed analysis data (populated by AnalyzePhase).
20    pub computed: Option<ComputedData>,
21    /// Diagnostics collected during compilation.
22    pub diagnostics: Vec<Diagnostic>,
23}
24
25impl CompilationContext {
26    /// Create a new compilation context from a manifest.
27    pub fn new(manifest: Manifest) -> Self {
28        Self {
29            manifest,
30            ir: None,
31            computed: None,
32            diagnostics: Vec::new(),
33        }
34    }
35
36    /// Check if any error diagnostics have been recorded.
37    pub fn has_errors(&self) -> bool {
38        self.diagnostics.iter().any(|d| d.severity.is_error())
39    }
40
41    /// Check if any warning diagnostics have been recorded.
42    pub fn has_warnings(&self) -> bool {
43        self.diagnostics.iter().any(|d| d.severity.is_warning())
44    }
45
46    /// Count the number of error diagnostics.
47    pub fn error_count(&self) -> usize {
48        self.diagnostics
49            .iter()
50            .filter(|d| d.severity.is_error())
51            .count()
52    }
53
54    /// Count the number of warning diagnostics.
55    pub fn warning_count(&self) -> usize {
56        self.diagnostics
57            .iter()
58            .filter(|d| d.severity.is_warning())
59            .count()
60    }
61
62    /// Add an error diagnostic.
63    pub fn add_error(&mut self, phase: &str, message: impl Into<String>) {
64        self.diagnostics.push(Diagnostic::error(phase, message));
65    }
66
67    /// Add a warning diagnostic.
68    pub fn add_warning(&mut self, phase: &str, message: impl Into<String>) {
69        self.diagnostics.push(Diagnostic::warning(phase, message));
70    }
71
72    /// Add an info diagnostic.
73    pub fn add_info(&mut self, phase: &str, message: impl Into<String>) {
74        self.diagnostics.push(Diagnostic::info(phase, message));
75    }
76
77    /// Add a diagnostic with a location.
78    pub fn add_diagnostic(&mut self, diagnostic: Diagnostic) {
79        self.diagnostics.push(diagnostic);
80    }
81
82    /// Get all error diagnostics.
83    pub fn errors(&self) -> impl Iterator<Item = &Diagnostic> {
84        self.diagnostics
85            .iter()
86            .filter(|d| matches!(d.severity, Severity::Error))
87    }
88
89    /// Get all warning diagnostics.
90    pub fn warnings(&self) -> impl Iterator<Item = &Diagnostic> {
91        self.diagnostics
92            .iter()
93            .filter(|d| matches!(d.severity, Severity::Warning))
94    }
95
96    /// Take the IR out of the context, consuming it.
97    ///
98    /// # Panics
99    ///
100    /// Panics if the IR has not been set (i.e., LowerPhase hasn't run).
101    pub fn take_ir(&mut self) -> AppIR {
102        self.ir.take().expect("IR not set - did LowerPhase run?")
103    }
104
105    /// Take the computed data out of the context, consuming it.
106    ///
107    /// # Panics
108    ///
109    /// Panics if computed data has not been set (i.e., AnalyzePhase hasn't run).
110    pub fn take_computed(&mut self) -> ComputedData {
111        self.computed
112            .take()
113            .expect("ComputedData not set - did AnalyzePhase run?")
114    }
115}
116
117#[cfg(test)]
118mod tests {
119    use super::*;
120
121    fn parse_manifest(content: &str) -> Manifest {
122        toml::from_str(content).expect("Failed to parse test manifest")
123    }
124
125    fn make_test_manifest() -> Manifest {
126        parse_manifest(
127            r#"
128            [cli]
129            name = "test"
130            language = "rust"
131        "#,
132        )
133    }
134
135    #[test]
136    fn test_context_creation() {
137        let manifest = make_test_manifest();
138        let ctx = CompilationContext::new(manifest);
139
140        assert!(ctx.ir.is_none());
141        assert!(ctx.computed.is_none());
142        assert!(ctx.diagnostics.is_empty());
143    }
144
145    #[test]
146    fn test_context_diagnostics() {
147        let manifest = make_test_manifest();
148        let mut ctx = CompilationContext::new(manifest);
149
150        ctx.add_error("test", "test error");
151        ctx.add_warning("test", "test warning");
152
153        assert!(ctx.has_errors());
154        assert!(ctx.has_warnings());
155        assert_eq!(ctx.error_count(), 1);
156        assert_eq!(ctx.warning_count(), 1);
157    }
158
159    #[test]
160    fn test_context_no_errors() {
161        let manifest = make_test_manifest();
162        let mut ctx = CompilationContext::new(manifest);
163
164        ctx.add_warning("test", "just a warning");
165        ctx.add_info("test", "just info");
166
167        assert!(!ctx.has_errors());
168        assert!(ctx.has_warnings());
169    }
170}