Skip to main content

miden_assembly_syntax/sema/
context.rs

1use alloc::{
2    boxed::Box,
3    collections::{BTreeMap, BTreeSet},
4    sync::Arc,
5    vec::Vec,
6};
7
8use miden_debug_types::{SourceFile, SourceManager, SourceSpan, Span, Spanned};
9use miden_utils_diagnostics::{Diagnostic, Severity};
10
11use super::{SemanticAnalysisError, SyntaxError};
12use crate::ast::{
13    constants::{ConstEvalError, eval::CachedConstantValue},
14    *,
15};
16
17/// This maintains the state for semantic analysis of a single [Module].
18pub struct AnalysisContext {
19    constants: BTreeMap<Ident, Constant>,
20    imported: BTreeSet<Ident>,
21    procedures: BTreeSet<ProcedureName>,
22    errors: Vec<SemanticAnalysisError>,
23    source_file: Arc<SourceFile>,
24    source_manager: Arc<dyn SourceManager>,
25    warnings_as_errors: bool,
26}
27
28impl constants::ConstEnvironment for AnalysisContext {
29    type Error = SemanticAnalysisError;
30
31    fn get_source_file_for(&self, span: SourceSpan) -> Option<Arc<SourceFile>> {
32        if span.source_id() == self.source_file.id() {
33            Some(self.source_file.clone())
34        } else {
35            None
36        }
37    }
38    #[inline]
39    fn get(&mut self, name: &Ident) -> Result<Option<CachedConstantValue<'_>>, Self::Error> {
40        if let Some(constant) = self.constants.get(name) {
41            Ok(Some(CachedConstantValue::Miss(&constant.value)))
42        } else if self.imported.contains(name) {
43            // We don't have the definition available yet
44            Ok(None)
45        } else {
46            Err(ConstEvalError::UndefinedSymbol {
47                symbol: name.clone(),
48                source_file: self.get_source_file_for(name.span()),
49            }
50            .into())
51        }
52    }
53    #[inline(always)]
54    fn get_by_path(
55        &mut self,
56        path: Span<&Path>,
57    ) -> Result<Option<CachedConstantValue<'_>>, Self::Error> {
58        if let Some(name) = path.as_ident() {
59            self.get(&name)
60        } else {
61            Ok(None)
62        }
63    }
64}
65
66impl AnalysisContext {
67    pub fn new(source_file: Arc<SourceFile>, source_manager: Arc<dyn SourceManager>) -> Self {
68        Self {
69            constants: Default::default(),
70            imported: Default::default(),
71            procedures: Default::default(),
72            errors: Default::default(),
73            source_file,
74            source_manager,
75            warnings_as_errors: false,
76        }
77    }
78
79    pub fn set_warnings_as_errors(&mut self, yes: bool) {
80        self.warnings_as_errors = yes;
81    }
82
83    #[inline(always)]
84    pub fn warnings_as_errors(&self) -> bool {
85        self.warnings_as_errors
86    }
87
88    #[inline(always)]
89    pub fn source_manager(&self) -> Arc<dyn SourceManager> {
90        self.source_manager.clone()
91    }
92
93    pub fn register_procedure_name(&mut self, name: ProcedureName) {
94        self.procedures.insert(name);
95    }
96
97    pub fn register_imported_name(&mut self, name: Ident) {
98        self.imported.insert(name);
99    }
100
101    /// Define a new constant `constant`
102    ///
103    /// Returns `Err` if a constant with the same name is already defined
104    pub fn define_constant(&mut self, module: &mut Module, constant: Constant) {
105        if let Err(err) = module.define_constant(constant.clone()) {
106            self.errors.push(err);
107        } else {
108            let name = constant.name.clone();
109            self.constants.insert(name, constant);
110        }
111    }
112
113    /// Register a constant for semantic analysis without defining it in the module.
114    ///
115    /// This is used for enum variants so we can fold discriminants without
116    /// attempting to define the same constant twice.
117    pub fn register_constant(&mut self, constant: Constant) {
118        let name = constant.name.clone();
119        if let Some(prev) = self.constants.get(&name) {
120            self.errors.push(SemanticAnalysisError::SymbolConflict {
121                span: constant.span,
122                prev_span: prev.span,
123            });
124        } else {
125            self.constants.insert(name, constant);
126        }
127    }
128
129    /// Rewrite all constant declarations by performing const evaluation of their expressions.
130    ///
131    /// This also has the effect of validating that the constant expressions themselves are valid.
132    pub fn simplify_constants(&mut self) {
133        let constants = self.constants.keys().cloned().collect::<Vec<_>>();
134
135        for constant in constants.iter() {
136            let expr = ConstantExpr::Var(Span::new(
137                constant.span(),
138                PathBuf::from(constant.clone()).into(),
139            ));
140            match crate::ast::constants::eval::expr(&expr, self) {
141                Ok(value) => {
142                    self.constants.get_mut(constant).unwrap().value = value;
143                },
144                Err(err) => {
145                    self.errors.push(err);
146                },
147            }
148        }
149    }
150
151    /// Get the constant value bound to `name`
152    ///
153    /// Returns `Err` if the symbol is undefined
154    pub fn get_constant(&self, name: &Ident) -> Result<&ConstantExpr, SemanticAnalysisError> {
155        if let Some(expr) = self.constants.get(name) {
156            Ok(&expr.value)
157        } else {
158            Err(SemanticAnalysisError::SymbolResolutionError(Box::new(
159                SymbolResolutionError::undefined(name.span(), &self.source_manager),
160            )))
161        }
162    }
163
164    pub fn error(&mut self, diagnostic: SemanticAnalysisError) {
165        self.errors.push(diagnostic);
166    }
167
168    pub fn has_errors(&self) -> bool {
169        if self.warnings_as_errors() {
170            return !self.errors.is_empty();
171        }
172        self.errors
173            .iter()
174            .any(|err| matches!(err.severity().unwrap_or(Severity::Error), Severity::Error))
175    }
176
177    pub fn has_failed(&mut self) -> Result<(), SyntaxError> {
178        if self.has_errors() {
179            Err(SyntaxError {
180                source_file: self.source_file.clone(),
181                errors: core::mem::take(&mut self.errors),
182            })
183        } else {
184            Ok(())
185        }
186    }
187
188    pub fn into_result(self) -> Result<(), SyntaxError> {
189        if self.has_errors() {
190            Err(SyntaxError {
191                source_file: self.source_file.clone(),
192                errors: self.errors,
193            })
194        } else {
195            self.emit_warnings();
196            Ok(())
197        }
198    }
199
200    #[cfg(feature = "std")]
201    fn emit_warnings(self) {
202        use crate::diagnostics::Report;
203
204        if !self.errors.is_empty() {
205            // Emit warnings to stderr
206            let warning = Report::from(super::errors::SyntaxWarning {
207                source_file: self.source_file,
208                errors: self.errors,
209            });
210            std::eprintln!("{warning}");
211        }
212    }
213
214    #[cfg(not(feature = "std"))]
215    fn emit_warnings(self) {}
216}