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(&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        &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    /// Rewrite all constant declarations by performing const evaluation of their expressions.
114    ///
115    /// This also has the effect of validating that the constant expressions themselves are valid.
116    pub fn simplify_constants(&mut self) {
117        let constants = self.constants.keys().cloned().collect::<Vec<_>>();
118
119        for constant in constants.iter() {
120            let expr = ConstantExpr::Var(Span::new(
121                constant.span(),
122                PathBuf::from(constant.clone()).into(),
123            ));
124            match crate::ast::constants::eval::expr(&expr, self) {
125                Ok(value) => {
126                    self.constants.get_mut(constant).unwrap().value = value;
127                },
128                Err(err) => {
129                    self.errors.push(err);
130                },
131            }
132        }
133    }
134
135    /// Get the constant value bound to `name`
136    ///
137    /// Returns `Err` if the symbol is undefined
138    pub fn get_constant(&self, name: &Ident) -> Result<&ConstantExpr, SemanticAnalysisError> {
139        if let Some(expr) = self.constants.get(name) {
140            Ok(&expr.value)
141        } else {
142            Err(SemanticAnalysisError::SymbolResolutionError(Box::new(
143                SymbolResolutionError::undefined(name.span(), &self.source_manager),
144            )))
145        }
146    }
147
148    pub fn error(&mut self, diagnostic: SemanticAnalysisError) {
149        self.errors.push(diagnostic);
150    }
151
152    pub fn has_errors(&self) -> bool {
153        if self.warnings_as_errors() {
154            return !self.errors.is_empty();
155        }
156        self.errors
157            .iter()
158            .any(|err| matches!(err.severity().unwrap_or(Severity::Error), Severity::Error))
159    }
160
161    pub fn has_failed(&mut self) -> Result<(), SyntaxError> {
162        if self.has_errors() {
163            Err(SyntaxError {
164                source_file: self.source_file.clone(),
165                errors: core::mem::take(&mut self.errors),
166            })
167        } else {
168            Ok(())
169        }
170    }
171
172    pub fn into_result(self) -> Result<(), SyntaxError> {
173        if self.has_errors() {
174            Err(SyntaxError {
175                source_file: self.source_file.clone(),
176                errors: self.errors,
177            })
178        } else {
179            self.emit_warnings();
180            Ok(())
181        }
182    }
183
184    #[cfg(feature = "std")]
185    fn emit_warnings(self) {
186        use crate::diagnostics::Report;
187
188        if !self.errors.is_empty() {
189            // Emit warnings to stderr
190            let warning = Report::from(super::errors::SyntaxWarning {
191                source_file: self.source_file,
192                errors: self.errors,
193            });
194            std::eprintln!("{warning}");
195        }
196    }
197
198    #[cfg(not(feature = "std"))]
199    fn emit_warnings(self) {}
200}