fe_analyzer/
context.rs

1use crate::{
2    display::Displayable,
3    errors::FatalError,
4    namespace::items::{EnumVariantId, TypeDef},
5    pattern_analysis::PatternMatrix,
6};
7
8use crate::namespace::items::{
9    ContractId, DiagnosticSink, FunctionId, FunctionSigId, Item, TraitId,
10};
11use crate::namespace::types::{Generic, SelfDecl, Type, TypeId};
12use crate::AnalyzerDb;
13use crate::{
14    builtins::{ContractTypeMethod, GlobalFunction, Intrinsic, ValueMethod},
15    namespace::scopes::BlockScopeType,
16};
17use crate::{
18    errors::{self, IncompleteItem, TypeError},
19    namespace::items::ModuleId,
20};
21use fe_common::diagnostics::Diagnostic;
22pub use fe_common::diagnostics::Label;
23use fe_common::Span;
24use fe_parser::ast;
25use fe_parser::node::{Node, NodeId};
26
27use indexmap::IndexMap;
28use num_bigint::BigInt;
29use smol_str::SmolStr;
30use std::fmt::{self, Debug};
31use std::hash::Hash;
32use std::marker::PhantomData;
33use std::rc::Rc;
34use std::{cell::RefCell, collections::HashMap};
35
36#[derive(Debug, PartialEq, Eq, Hash, Clone)]
37pub struct Analysis<T> {
38    pub value: T,
39    pub diagnostics: Rc<[Diagnostic]>,
40}
41impl<T> Analysis<T> {
42    pub fn new(value: T, diagnostics: Rc<[Diagnostic]>) -> Self {
43        Self { value, diagnostics }
44    }
45    pub fn sink_diagnostics(&self, sink: &mut impl DiagnosticSink) {
46        self.diagnostics.iter().for_each(|diag| sink.push(diag))
47    }
48    pub fn has_diag(&self) -> bool {
49        !self.diagnostics.is_empty()
50    }
51}
52
53pub trait AnalyzerContext {
54    fn resolve_name(&self, name: &str, span: Span) -> Result<Option<NamedThing>, IncompleteItem>;
55    /// Resolves the given path and registers all errors
56    fn resolve_path(&self, path: &ast::Path, span: Span) -> Result<NamedThing, FatalError>;
57    /// Resolves the given path only if it is visible. Does not register any errors
58    fn resolve_visible_path(&self, path: &ast::Path) -> Option<NamedThing>;
59    /// Resolves the given path. Does not register any errors
60    fn resolve_any_path(&self, path: &ast::Path) -> Option<NamedThing>;
61
62    fn add_diagnostic(&self, diag: Diagnostic);
63    fn db(&self) -> &dyn AnalyzerDb;
64
65    fn error(&self, message: &str, label_span: Span, label: &str) -> DiagnosticVoucher {
66        self.register_diag(errors::error(message, label_span, label))
67    }
68
69    /// Attribute contextual information to an expression node.
70    ///
71    /// # Panics
72    ///
73    /// Panics if an entry already exists for the node id.
74    fn add_expression(&self, node: &Node<ast::Expr>, attributes: ExpressionAttributes);
75
76    /// Update the expression attributes.
77    ///
78    /// # Panics
79    ///
80    /// Panics if an entry does not already exist for the node id.
81    fn update_expression(&self, node: &Node<ast::Expr>, f: &dyn Fn(&mut ExpressionAttributes));
82
83    /// Returns a type of an expression.
84    ///
85    /// # Panics
86    ///
87    /// Panics if type analysis is not performed for an `expr`.
88    fn expr_typ(&self, expr: &Node<ast::Expr>) -> Type;
89
90    /// Add evaluated constant value in a constant declaration to the context.
91    fn add_constant(&self, name: &Node<ast::SmolStr>, expr: &Node<ast::Expr>, value: Constant);
92
93    /// Returns constant value from variable name.
94    fn constant_value_by_name(
95        &self,
96        name: &ast::SmolStr,
97        span: Span,
98    ) -> Result<Option<Constant>, IncompleteItem>;
99
100    /// Returns an item enclosing current context.
101    ///
102    /// # Example
103    ///
104    /// ```fe
105    /// contract Foo:
106    ///     fn foo():
107    ///        if ...:
108    ///            ...
109    ///        else:
110    ///            ...
111    /// ```
112    /// If the context is in `then` block, then this function returns
113    /// `Item::Function(..)`.
114    fn parent(&self) -> Item;
115
116    /// Returns the module enclosing current context.
117    fn module(&self) -> ModuleId;
118
119    /// Returns a function id that encloses a context.
120    ///
121    /// # Panics
122    ///
123    /// Panics if a context is not in a function. Use [`Self::is_in_function`]
124    /// to determine whether a context is in a function.
125    fn parent_function(&self) -> FunctionId;
126
127    /// Returns a non-function item that encloses a context.
128    ///
129    /// # Example
130    ///
131    /// ```fe
132    /// contract Foo:
133    ///     fn foo():
134    ///        if ...:
135    ///            ...
136    ///        else:
137    ///            ...
138    /// ```
139    /// If the context is in `then` block, then this function returns
140    /// `Item::Type(TypeDef::Contract(..))`.
141    fn root_item(&self) -> Item {
142        let mut item = self.parent();
143        while let Item::Function(func_id) = item {
144            item = func_id.parent(self.db());
145        }
146        item
147    }
148
149    /// # Panics
150    ///
151    /// Panics if a context is not in a function. Use [`Self::is_in_function`]
152    /// to determine whether a context is in a function.
153    fn add_call(&self, node: &Node<ast::Expr>, call_type: CallType);
154    fn get_call(&self, node: &Node<ast::Expr>) -> Option<CallType>;
155
156    /// Returns `true` if the context is in function scope.
157    fn is_in_function(&self) -> bool;
158
159    /// Returns `true` if the scope or any of its parents is of the given type.
160    fn inherits_type(&self, typ: BlockScopeType) -> bool;
161
162    /// Returns the `Context` type, if it is defined.
163    fn get_context_type(&self) -> Option<TypeId>;
164
165    fn type_error(
166        &self,
167        message: &str,
168        span: Span,
169        expected: TypeId,
170        actual: TypeId,
171    ) -> DiagnosticVoucher {
172        self.register_diag(errors::type_error(
173            message,
174            span,
175            expected.display(self.db()),
176            actual.display(self.db()),
177        ))
178    }
179
180    fn not_yet_implemented(&self, feature: &str, span: Span) -> DiagnosticVoucher {
181        self.register_diag(errors::not_yet_implemented(feature, span))
182    }
183
184    fn fancy_error(
185        &self,
186        message: &str,
187        labels: Vec<Label>,
188        notes: Vec<String>,
189    ) -> DiagnosticVoucher {
190        self.register_diag(errors::fancy_error(message, labels, notes))
191    }
192
193    fn duplicate_name_error(
194        &self,
195        message: &str,
196        name: &str,
197        original: Span,
198        duplicate: Span,
199    ) -> DiagnosticVoucher {
200        self.register_diag(errors::duplicate_name_error(
201            message, name, original, duplicate,
202        ))
203    }
204
205    fn name_conflict_error(
206        &self,
207        name_kind: &str, // Eg "function parameter" or "variable name"
208        name: &str,
209        original: &NamedThing,
210        original_span: Option<Span>,
211        duplicate_span: Span,
212    ) -> DiagnosticVoucher {
213        self.register_diag(errors::name_conflict_error(
214            name_kind,
215            name,
216            original,
217            original_span,
218            duplicate_span,
219        ))
220    }
221
222    fn register_diag(&self, diag: Diagnostic) -> DiagnosticVoucher {
223        self.add_diagnostic(diag);
224        DiagnosticVoucher(PhantomData)
225    }
226}
227
228#[derive(Clone, Debug, PartialEq, Eq)]
229pub enum NamedThing {
230    Item(Item),
231    EnumVariant(EnumVariantId),
232    SelfValue {
233        /// Function `self` parameter.
234        decl: Option<SelfDecl>,
235
236        /// The function's parent, if any. If `None`, `self` has been
237        /// used in a module-level function.
238        parent: Option<Item>,
239        span: Option<Span>,
240    },
241    // SelfType // when/if we add a `Self` type keyword
242    Variable {
243        name: SmolStr,
244        typ: Result<TypeId, TypeError>,
245        is_const: bool,
246        span: Span,
247    },
248}
249
250impl NamedThing {
251    pub fn name(&self, db: &dyn AnalyzerDb) -> SmolStr {
252        match self {
253            NamedThing::Item(item) => item.name(db),
254            NamedThing::EnumVariant(variant) => variant.name(db),
255            NamedThing::SelfValue { .. } => "self".into(),
256            NamedThing::Variable { name, .. } => name.clone(),
257        }
258    }
259
260    pub fn name_span(&self, db: &dyn AnalyzerDb) -> Option<Span> {
261        match self {
262            NamedThing::Item(item) => item.name_span(db),
263            NamedThing::EnumVariant(variant) => Some(variant.span(db)),
264            NamedThing::SelfValue { span, .. } => *span,
265            NamedThing::Variable { span, .. } => Some(*span),
266        }
267    }
268
269    pub fn is_builtin(&self) -> bool {
270        match self {
271            NamedThing::Item(item) => item.is_builtin(),
272            NamedThing::EnumVariant(_)
273            | NamedThing::Variable { .. }
274            | NamedThing::SelfValue { .. } => false,
275        }
276    }
277
278    pub fn item_kind_display_name(&self) -> &str {
279        match self {
280            NamedThing::Item(item) => item.item_kind_display_name(),
281            NamedThing::EnumVariant(_) => "enum variant",
282            NamedThing::Variable { .. } => "variable",
283            NamedThing::SelfValue { .. } => "value",
284        }
285    }
286
287    pub fn resolve_path_segment(
288        &self,
289        db: &dyn AnalyzerDb,
290        segment: &SmolStr,
291    ) -> Option<NamedThing> {
292        if let Self::Item(Item::Type(TypeDef::Enum(enum_))) = self {
293            if let Some(variant) = enum_.variant(db, segment) {
294                return Some(NamedThing::EnumVariant(variant));
295            }
296        }
297
298        match self {
299            Self::Item(item) => item
300                .items(db)
301                .get(segment)
302                .map(|resolved| NamedThing::Item(*resolved)),
303
304            _ => None,
305        }
306    }
307}
308
309/// This should only be created by [`AnalyzerContext`].
310#[derive(Debug, Clone, PartialEq, Eq, Hash)]
311pub struct DiagnosticVoucher(PhantomData<()>);
312
313impl DiagnosticVoucher {
314    pub fn assume_the_parser_handled_it() -> Self {
315        Self(PhantomData)
316    }
317}
318
319#[derive(Default)]
320pub struct TempContext {
321    pub diagnostics: RefCell<Vec<Diagnostic>>,
322}
323impl AnalyzerContext for TempContext {
324    fn db(&self) -> &dyn AnalyzerDb {
325        panic!("TempContext has no analyzer db")
326    }
327
328    fn resolve_name(&self, _name: &str, _span: Span) -> Result<Option<NamedThing>, IncompleteItem> {
329        panic!("TempContext can't resolve names")
330    }
331
332    fn resolve_path(&self, _path: &ast::Path, _span: Span) -> Result<NamedThing, FatalError> {
333        panic!("TempContext can't resolve paths")
334    }
335
336    fn resolve_visible_path(&self, _path: &ast::Path) -> Option<NamedThing> {
337        panic!("TempContext can't resolve paths")
338    }
339
340    fn resolve_any_path(&self, _path: &ast::Path) -> Option<NamedThing> {
341        panic!("TempContext can't resolve paths")
342    }
343
344    fn add_expression(&self, _node: &Node<ast::Expr>, _attributes: ExpressionAttributes) {
345        panic!("TempContext can't store expression")
346    }
347
348    fn update_expression(&self, _node: &Node<ast::Expr>, _f: &dyn Fn(&mut ExpressionAttributes)) {
349        panic!("TempContext can't update expression");
350    }
351
352    fn expr_typ(&self, _expr: &Node<ast::Expr>) -> Type {
353        panic!("TempContext can't return expression type")
354    }
355
356    fn add_constant(&self, _name: &Node<ast::SmolStr>, _expr: &Node<ast::Expr>, _value: Constant) {
357        panic!("TempContext can't store constant")
358    }
359
360    fn constant_value_by_name(
361        &self,
362        _name: &ast::SmolStr,
363        _span: Span,
364    ) -> Result<Option<Constant>, IncompleteItem> {
365        Ok(None)
366    }
367
368    fn parent(&self) -> Item {
369        panic!("TempContext has no root item")
370    }
371
372    fn module(&self) -> ModuleId {
373        panic!("TempContext has no module")
374    }
375
376    fn parent_function(&self) -> FunctionId {
377        panic!("TempContext has no parent function")
378    }
379
380    fn add_call(&self, _node: &Node<ast::Expr>, _call_type: CallType) {
381        panic!("TempContext can't add call");
382    }
383
384    fn get_call(&self, _node: &Node<ast::Expr>) -> Option<CallType> {
385        panic!("TempContext can't have calls");
386    }
387
388    fn is_in_function(&self) -> bool {
389        false
390    }
391
392    fn inherits_type(&self, _typ: BlockScopeType) -> bool {
393        false
394    }
395
396    fn add_diagnostic(&self, diag: Diagnostic) {
397        self.diagnostics.borrow_mut().push(diag)
398    }
399
400    fn get_context_type(&self) -> Option<TypeId> {
401        panic!("TempContext can't resolve Context")
402    }
403}
404
405#[derive(Default, Clone, Debug, PartialEq, Eq)]
406pub struct FunctionBody {
407    pub expressions: IndexMap<NodeId, ExpressionAttributes>,
408    // Map match statements to the corresponding [`PatternMatrix`]
409    pub matches: IndexMap<NodeId, PatternMatrix>,
410    // Map lhs of variable declaration to type.
411    pub var_types: IndexMap<NodeId, TypeId>,
412    pub calls: IndexMap<NodeId, CallType>,
413    pub spans: HashMap<NodeId, Span>,
414}
415
416/// Contains contextual information relating to an expression AST node.
417#[derive(Clone, Debug, PartialEq, Eq)]
418pub struct ExpressionAttributes {
419    pub typ: TypeId,
420    // Evaluated constant value of const local definition.
421    pub const_value: Option<Constant>,
422    pub type_adjustments: Vec<Adjustment>,
423}
424impl ExpressionAttributes {
425    pub fn original_type(&self) -> TypeId {
426        self.typ
427    }
428    pub fn adjusted_type(&self) -> TypeId {
429        if let Some(adj) = self.type_adjustments.last() {
430            adj.into
431        } else {
432            self.typ
433        }
434    }
435}
436
437#[derive(Copy, Clone, Debug, PartialEq, Eq)]
438pub struct Adjustment {
439    pub into: TypeId,
440    pub kind: AdjustmentKind,
441}
442
443#[derive(Copy, Clone, Debug, PartialEq, Eq)]
444pub enum AdjustmentKind {
445    Copy,
446    /// Load from storage ptr
447    Load,
448    IntSizeIncrease,
449    StringSizeIncrease,
450}
451
452impl ExpressionAttributes {
453    pub fn new(typ: TypeId) -> Self {
454        Self {
455            typ,
456            const_value: None,
457            type_adjustments: vec![],
458        }
459    }
460}
461
462impl crate::display::DisplayWithDb for ExpressionAttributes {
463    fn format(&self, db: &dyn AnalyzerDb, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
464        let ExpressionAttributes {
465            typ,
466            const_value,
467            type_adjustments,
468        } = self;
469        write!(f, "{}", typ.display(db))?;
470        if let Some(val) = &const_value {
471            write!(f, " = {val:?}")?;
472        }
473        for adj in type_adjustments {
474            write!(f, " -{:?}-> {}", adj.kind, adj.into.display(db))?;
475        }
476        Ok(())
477    }
478}
479
480/// The type of a function call.
481#[derive(Clone, Debug, PartialEq, Eq)]
482pub enum CallType {
483    BuiltinFunction(GlobalFunction),
484    Intrinsic(Intrinsic),
485    BuiltinValueMethod {
486        method: ValueMethod,
487        typ: TypeId,
488    },
489
490    // create, create2 (will be methods of the context struct soon)
491    BuiltinAssociatedFunction {
492        contract: ContractId,
493        function: ContractTypeMethod,
494    },
495
496    // MyStruct.foo() (soon MyStruct::foo())
497    AssociatedFunction {
498        typ: TypeId,
499        function: FunctionId,
500    },
501    // some_struct_or_contract.foo()
502    ValueMethod {
503        typ: TypeId,
504        method: FunctionId,
505    },
506    // some_trait.foo()
507    // The reason this can not use `ValueMethod` is mainly because the trait might not have a
508    // function implementation and even if it had it might not be the one that ends up getting
509    // executed. An `impl` block will decide that.
510    TraitValueMethod {
511        trait_id: TraitId,
512        method: FunctionSigId,
513        // Traits can not directly be used as types but can act as bounds for generics. This is the
514        // generic type that the method is called on.
515        generic_type: Generic,
516    },
517    External {
518        contract: ContractId,
519        function: FunctionId,
520    },
521    Pure(FunctionId),
522    TypeConstructor(TypeId),
523    EnumConstructor(EnumVariantId),
524}
525
526impl CallType {
527    pub fn function(&self) -> Option<FunctionId> {
528        use CallType::*;
529        match self {
530            BuiltinFunction(_)
531            | BuiltinValueMethod { .. }
532            | TypeConstructor(_)
533            | EnumConstructor(_)
534            | Intrinsic(_)
535            | TraitValueMethod { .. }
536            | BuiltinAssociatedFunction { .. } => None,
537            AssociatedFunction { function: id, .. }
538            | ValueMethod { method: id, .. }
539            | External { function: id, .. }
540            | Pure(id) => Some(*id),
541        }
542    }
543
544    pub fn function_name(&self, db: &dyn AnalyzerDb) -> SmolStr {
545        match self {
546            CallType::BuiltinFunction(f) => f.as_ref().into(),
547            CallType::Intrinsic(f) => f.as_ref().into(),
548            CallType::BuiltinValueMethod { method, .. } => method.as_ref().into(),
549            CallType::BuiltinAssociatedFunction { function, .. } => function.as_ref().into(),
550            CallType::AssociatedFunction { function: id, .. }
551            | CallType::ValueMethod { method: id, .. }
552            | CallType::External { function: id, .. }
553            | CallType::Pure(id) => id.name(db),
554            CallType::TraitValueMethod { method: id, .. } => id.name(db),
555            CallType::TypeConstructor(typ) => typ.display(db).to_string().into(),
556            CallType::EnumConstructor(variant) => {
557                let enum_name = variant.parent(db).name(db);
558                let variant_name = variant.name(db);
559                format!("{enum_name}::{variant_name}").into()
560            }
561        }
562    }
563
564    pub fn is_unsafe(&self, db: &dyn AnalyzerDb) -> bool {
565        if let CallType::Intrinsic(_) = self {
566            true
567        } else if let CallType::TypeConstructor(type_id) = self {
568            // check that this is the `Context` struct defined in `std`
569            // this should be deleted once associated functions are supported and we can
570            // define unsafe constructors in Fe
571            if let Type::Struct(struct_) = type_id.typ(db) {
572                struct_.name(db) == "Context" && struct_.module(db).ingot(db).name(db) == "std"
573            } else {
574                false
575            }
576        } else {
577            self.function().map(|id| id.is_unsafe(db)).unwrap_or(false)
578        }
579    }
580}
581
582impl fmt::Display for CallType {
583    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
584        write!(f, "{self:?}")
585    }
586}
587
588/// Represents constant value.
589#[derive(Debug, Clone, PartialEq, Eq)]
590pub enum Constant {
591    Int(BigInt),
592    Address(BigInt),
593    Bool(bool),
594    Str(SmolStr),
595}