fe_analyzer/namespace/
scopes.rs

1#![allow(unstable_name_collisions)] // expect_none, which ain't gonna be stabilized
2
3use crate::context::{
4    AnalyzerContext, CallType, Constant, ExpressionAttributes, FunctionBody, NamedThing,
5};
6use crate::errors::{AlreadyDefined, FatalError, IncompleteItem, TypeError};
7use crate::namespace::items::{FunctionId, ModuleId};
8use crate::namespace::items::{Item, TypeDef};
9use crate::namespace::types::{Type, TypeId};
10use crate::pattern_analysis::PatternMatrix;
11use crate::AnalyzerDb;
12use fe_common::diagnostics::Diagnostic;
13use fe_common::Span;
14use fe_parser::{ast, node::NodeId, Label};
15use fe_parser::{ast::Expr, node::Node};
16use indexmap::IndexMap;
17use std::cell::RefCell;
18use std::collections::BTreeMap;
19
20pub struct ItemScope<'a> {
21    db: &'a dyn AnalyzerDb,
22    module: ModuleId,
23    expressions: RefCell<IndexMap<NodeId, ExpressionAttributes>>,
24    pub diagnostics: RefCell<Vec<Diagnostic>>,
25}
26impl<'a> ItemScope<'a> {
27    pub fn new(db: &'a dyn AnalyzerDb, module: ModuleId) -> Self {
28        Self {
29            db,
30            module,
31            expressions: RefCell::new(IndexMap::default()),
32            diagnostics: RefCell::new(vec![]),
33        }
34    }
35}
36
37impl<'a> AnalyzerContext for ItemScope<'a> {
38    fn db(&self) -> &dyn AnalyzerDb {
39        self.db
40    }
41
42    fn add_expression(&self, node: &Node<ast::Expr>, attributes: ExpressionAttributes) {
43        self.expressions
44            .borrow_mut()
45            .insert(node.id, attributes)
46            .expect_none("expression attributes already exist");
47    }
48
49    fn update_expression(&self, node: &Node<ast::Expr>, f: &dyn Fn(&mut ExpressionAttributes)) {
50        f(self.expressions.borrow_mut().get_mut(&node.id).unwrap())
51    }
52
53    fn expr_typ(&self, expr: &Node<Expr>) -> Type {
54        self.expressions
55            .borrow()
56            .get(&expr.id)
57            .unwrap()
58            .typ
59            .typ(self.db())
60    }
61
62    fn add_constant(
63        &self,
64        _name: &Node<ast::SmolStr>,
65        _expr: &Node<ast::Expr>,
66        _value: crate::context::Constant,
67    ) {
68        // We use salsa query to get constant. So no need to add constant
69        // explicitly.
70    }
71
72    fn constant_value_by_name(
73        &self,
74        name: &ast::SmolStr,
75        _span: Span,
76    ) -> Result<Option<Constant>, IncompleteItem> {
77        if let Some(constant) = self.module.resolve_constant(self.db, name)? {
78            // It's ok to ignore an error.
79            // Diagnostics are already emitted when an error occurs.
80            Ok(constant.constant_value(self.db).ok())
81        } else {
82            Ok(None)
83        }
84    }
85
86    fn parent(&self) -> Item {
87        Item::Module(self.module)
88    }
89
90    fn module(&self) -> ModuleId {
91        self.module
92    }
93
94    fn parent_function(&self) -> FunctionId {
95        panic!("ItemContext has no parent function")
96    }
97
98    fn add_call(&self, _node: &Node<ast::Expr>, _call_type: CallType) {
99        unreachable!("Can't call function outside of function")
100    }
101    fn get_call(&self, _node: &Node<ast::Expr>) -> Option<CallType> {
102        unreachable!("Can't call function outside of function")
103    }
104
105    fn is_in_function(&self) -> bool {
106        false
107    }
108
109    fn inherits_type(&self, _typ: BlockScopeType) -> bool {
110        false
111    }
112
113    fn resolve_name(&self, name: &str, span: Span) -> Result<Option<NamedThing>, IncompleteItem> {
114        let resolved = self.module.resolve_name(self.db, name)?;
115
116        if let Some(named_thing) = &resolved {
117            check_visibility(self, named_thing, span);
118        }
119
120        Ok(resolved)
121    }
122
123    fn resolve_path(&self, path: &ast::Path, span: Span) -> Result<NamedThing, FatalError> {
124        let resolved = self.module.resolve_path_internal(self.db(), path);
125
126        let mut err = None;
127        for diagnostic in resolved.diagnostics.iter() {
128            err = Some(self.register_diag(diagnostic.clone()));
129        }
130
131        if let Some(err) = err {
132            return Err(FatalError::new(err));
133        }
134
135        if let Some(named_thing) = resolved.value {
136            check_visibility(self, &named_thing, span);
137            Ok(named_thing)
138        } else {
139            let err = self.error("unresolved path item", span, "not found");
140            Err(FatalError::new(err))
141        }
142    }
143
144    fn resolve_visible_path(&self, path: &ast::Path) -> Option<NamedThing> {
145        let resolved = self.module.resolve_path_internal(self.db(), path);
146
147        if resolved.diagnostics.len() > 0 {
148            return None;
149        }
150
151        let named_thing = resolved.value?;
152        if is_visible(self, &named_thing) {
153            Some(named_thing)
154        } else {
155            None
156        }
157    }
158
159    fn resolve_any_path(&self, path: &ast::Path) -> Option<NamedThing> {
160        let resolved = self.module.resolve_path_internal(self.db(), path);
161
162        if resolved.diagnostics.len() > 0 {
163            return None;
164        }
165
166        resolved.value
167    }
168
169    fn add_diagnostic(&self, diag: Diagnostic) {
170        self.diagnostics.borrow_mut().push(diag)
171    }
172
173    /// Gets `std::context::Context` if it exists
174    fn get_context_type(&self) -> Option<TypeId> {
175        if let Ok(Some(NamedThing::Item(Item::Type(TypeDef::Struct(id))))) =
176            // `Context` is guaranteed to be public, so we can use `Span::dummy()` .
177            self.resolve_name("Context", Span::dummy())
178        {
179            // we just assume that there is only one `Context` defined in `std`
180            if id.module(self.db()).ingot(self.db()).name(self.db()) == "std" {
181                return Some(self.db().intern_type(Type::Struct(id)));
182            }
183        }
184
185        None
186    }
187}
188
189pub struct FunctionScope<'a> {
190    pub db: &'a dyn AnalyzerDb,
191    pub function: FunctionId,
192    pub body: RefCell<FunctionBody>,
193    pub diagnostics: RefCell<Vec<Diagnostic>>,
194}
195
196impl<'a> FunctionScope<'a> {
197    pub fn new(db: &'a dyn AnalyzerDb, function: FunctionId) -> Self {
198        Self {
199            db,
200            function,
201            body: RefCell::new(FunctionBody::default()),
202            diagnostics: RefCell::new(vec![]),
203        }
204    }
205
206    pub fn function_return_type(&self) -> Result<TypeId, TypeError> {
207        self.function.signature(self.db).return_type.clone()
208    }
209
210    pub fn map_variable_type<T>(&self, node: &Node<T>, typ: TypeId) {
211        self.add_node(node);
212        self.body
213            .borrow_mut()
214            .var_types
215            .insert(node.id, typ)
216            .expect_none("variable has already registered")
217    }
218
219    pub fn map_pattern_matrix(&self, node: &Node<ast::FuncStmt>, matrix: PatternMatrix) {
220        debug_assert!(matches!(node.kind, ast::FuncStmt::Match { .. }));
221        self.body
222            .borrow_mut()
223            .matches
224            .insert(node.id, matrix)
225            .expect_none("match statement attributes already exists")
226    }
227
228    fn add_node<T>(&self, node: &Node<T>) {
229        self.body.borrow_mut().spans.insert(node.id, node.span);
230    }
231}
232
233impl<'a> AnalyzerContext for FunctionScope<'a> {
234    fn db(&self) -> &dyn AnalyzerDb {
235        self.db
236    }
237
238    fn add_diagnostic(&self, diag: Diagnostic) {
239        self.diagnostics.borrow_mut().push(diag)
240    }
241
242    fn add_expression(&self, node: &Node<ast::Expr>, attributes: ExpressionAttributes) {
243        self.add_node(node);
244        self.body
245            .borrow_mut()
246            .expressions
247            .insert(node.id, attributes)
248            .expect_none("expression attributes already exist");
249    }
250
251    fn update_expression(&self, node: &Node<ast::Expr>, f: &dyn Fn(&mut ExpressionAttributes)) {
252        f(self
253            .body
254            .borrow_mut()
255            .expressions
256            .get_mut(&node.id)
257            .unwrap())
258    }
259
260    fn expr_typ(&self, expr: &Node<Expr>) -> Type {
261        self.body
262            .borrow()
263            .expressions
264            .get(&expr.id)
265            .unwrap()
266            .typ
267            .typ(self.db())
268    }
269
270    fn add_constant(&self, _name: &Node<ast::SmolStr>, expr: &Node<ast::Expr>, value: Constant) {
271        self.body
272            .borrow_mut()
273            .expressions
274            .get_mut(&expr.id)
275            .expect("expression attributes must exist before adding constant value")
276            .const_value = Some(value);
277    }
278
279    fn constant_value_by_name(
280        &self,
281        _name: &ast::SmolStr,
282        _span: Span,
283    ) -> Result<Option<Constant>, IncompleteItem> {
284        Ok(None)
285    }
286
287    fn parent(&self) -> Item {
288        self.function.parent(self.db())
289    }
290
291    fn module(&self) -> ModuleId {
292        self.function.module(self.db())
293    }
294
295    fn parent_function(&self) -> FunctionId {
296        self.function
297    }
298
299    fn add_call(&self, node: &Node<ast::Expr>, call_type: CallType) {
300        // TODO: should probably take the Expr::Call node, rather than the function node
301        self.add_node(node);
302        self.body
303            .borrow_mut()
304            .calls
305            .insert(node.id, call_type)
306            .expect_none("call attributes already exist");
307    }
308    fn get_call(&self, node: &Node<ast::Expr>) -> Option<CallType> {
309        self.body.borrow().calls.get(&node.id).cloned()
310    }
311
312    fn is_in_function(&self) -> bool {
313        true
314    }
315
316    fn inherits_type(&self, _typ: BlockScopeType) -> bool {
317        false
318    }
319
320    fn resolve_name(&self, name: &str, span: Span) -> Result<Option<NamedThing>, IncompleteItem> {
321        let sig = self.function.signature(self.db);
322
323        if name == "self" {
324            return Ok(Some(NamedThing::SelfValue {
325                decl: sig.self_decl,
326                parent: self.function.sig(self.db).self_item(self.db),
327                span: self.function.self_span(self.db),
328            }));
329        }
330
331        // Getting param names and spans should be simpler
332        let param = sig.params.iter().find_map(|param| {
333            (param.name == name).then(|| {
334                let span = self
335                    .function
336                    .data(self.db)
337                    .ast
338                    .kind
339                    .sig
340                    .kind
341                    .args
342                    .iter()
343                    .find_map(|param| (param.name() == name).then(|| param.name_span()))
344                    .expect("found param type but not span");
345
346                NamedThing::Variable {
347                    name: name.into(),
348                    typ: param.typ.clone(),
349                    is_const: false,
350                    span,
351                }
352            })
353        });
354
355        if let Some(param) = param {
356            Ok(Some(param))
357        } else {
358            let resolved =
359                if let Item::Type(TypeDef::Contract(contract)) = self.function.parent(self.db) {
360                    contract.resolve_name(self.db, name)
361                } else {
362                    self.function.module(self.db).resolve_name(self.db, name)
363                }?;
364
365            if let Some(named_thing) = &resolved {
366                check_visibility(self, named_thing, span);
367            }
368
369            Ok(resolved)
370        }
371    }
372
373    fn resolve_path(&self, path: &ast::Path, span: Span) -> Result<NamedThing, FatalError> {
374        let resolved = self
375            .function
376            .module(self.db())
377            .resolve_path_internal(self.db(), path);
378
379        let mut err = None;
380        for diagnostic in resolved.diagnostics.iter() {
381            err = Some(self.register_diag(diagnostic.clone()));
382        }
383
384        if let Some(err) = err {
385            return Err(FatalError::new(err));
386        }
387
388        if let Some(named_thing) = resolved.value {
389            check_visibility(self, &named_thing, span);
390            Ok(named_thing)
391        } else {
392            let err = self.error("unresolved path item", span, "not found");
393            Err(FatalError::new(err))
394        }
395    }
396
397    fn resolve_visible_path(&self, path: &ast::Path) -> Option<NamedThing> {
398        let resolved = self
399            .function
400            .module(self.db())
401            .resolve_path_internal(self.db(), path);
402
403        if resolved.diagnostics.len() > 0 {
404            return None;
405        }
406
407        let named_thing = resolved.value?;
408        if is_visible(self, &named_thing) {
409            Some(named_thing)
410        } else {
411            None
412        }
413    }
414
415    fn resolve_any_path(&self, path: &ast::Path) -> Option<NamedThing> {
416        let resolved = self
417            .function
418            .module(self.db())
419            .resolve_path_internal(self.db(), path);
420
421        if resolved.diagnostics.len() > 0 {
422            return None;
423        }
424
425        resolved.value
426    }
427
428    fn get_context_type(&self) -> Option<TypeId> {
429        if let Ok(Some(NamedThing::Item(Item::Type(TypeDef::Struct(id))))) =
430            self.resolve_name("Context", Span::dummy())
431        {
432            if id.module(self.db()).ingot(self.db()).name(self.db()) == "std" {
433                return Some(self.db().intern_type(Type::Struct(id)));
434            }
435        }
436
437        None
438    }
439}
440
441pub struct BlockScope<'a, 'b> {
442    pub root: &'a FunctionScope<'b>,
443    pub parent: Option<&'a BlockScope<'a, 'b>>,
444    /// Maps Name -> (Type, is_const, span)
445    pub variable_defs: BTreeMap<String, (TypeId, bool, Span)>,
446    pub constant_defs: RefCell<BTreeMap<String, Constant>>,
447    pub typ: BlockScopeType,
448}
449
450#[derive(Clone, Debug, PartialEq, Eq)]
451pub enum BlockScopeType {
452    Function,
453    IfElse,
454    Match,
455    MatchArm,
456    Loop,
457    Unsafe,
458}
459
460impl AnalyzerContext for BlockScope<'_, '_> {
461    fn db(&self) -> &dyn AnalyzerDb {
462        self.root.db
463    }
464
465    fn resolve_name(&self, name: &str, span: Span) -> Result<Option<NamedThing>, IncompleteItem> {
466        if let Some(var) =
467            self.variable_defs
468                .get(name)
469                .map(|(typ, is_const, span)| NamedThing::Variable {
470                    name: name.into(),
471                    typ: Ok(*typ),
472                    is_const: *is_const,
473                    span: *span,
474                })
475        {
476            Ok(Some(var))
477        } else if let Some(parent) = self.parent {
478            parent.resolve_name(name, span)
479        } else {
480            self.root.resolve_name(name, span)
481        }
482    }
483
484    fn add_expression(&self, node: &Node<ast::Expr>, attributes: ExpressionAttributes) {
485        self.root.add_expression(node, attributes)
486    }
487
488    fn update_expression(&self, node: &Node<ast::Expr>, f: &dyn Fn(&mut ExpressionAttributes)) {
489        self.root.update_expression(node, f)
490    }
491
492    fn expr_typ(&self, expr: &Node<Expr>) -> Type {
493        self.root.expr_typ(expr)
494    }
495
496    fn add_constant(&self, name: &Node<ast::SmolStr>, expr: &Node<ast::Expr>, value: Constant) {
497        self.constant_defs
498            .borrow_mut()
499            .insert(name.kind.clone().to_string(), value.clone())
500            .expect_none("expression attributes already exist");
501
502        self.root.add_constant(name, expr, value)
503    }
504
505    fn constant_value_by_name(
506        &self,
507        name: &ast::SmolStr,
508        span: Span,
509    ) -> Result<Option<Constant>, IncompleteItem> {
510        if let Some(constant) = self.constant_defs.borrow().get(name.as_str()) {
511            Ok(Some(constant.clone()))
512        } else if let Some(parent) = self.parent {
513            parent.constant_value_by_name(name, span)
514        } else {
515            match self.resolve_name(name, span)? {
516                Some(NamedThing::Item(Item::Constant(constant))) => {
517                    Ok(constant.constant_value(self.db()).ok())
518                }
519                _ => Ok(None),
520            }
521        }
522    }
523
524    fn parent(&self) -> Item {
525        Item::Function(self.root.function)
526    }
527
528    fn module(&self) -> ModuleId {
529        self.root.module()
530    }
531
532    fn parent_function(&self) -> FunctionId {
533        self.root.function
534    }
535
536    fn add_call(&self, node: &Node<ast::Expr>, call_type: CallType) {
537        self.root.add_call(node, call_type)
538    }
539
540    fn get_call(&self, node: &Node<ast::Expr>) -> Option<CallType> {
541        self.root.get_call(node)
542    }
543
544    fn is_in_function(&self) -> bool {
545        true
546    }
547
548    fn inherits_type(&self, typ: BlockScopeType) -> bool {
549        self.typ == typ || self.parent.map_or(false, |scope| scope.inherits_type(typ))
550    }
551
552    fn resolve_path(&self, path: &ast::Path, span: Span) -> Result<NamedThing, FatalError> {
553        self.root.resolve_path(path, span)
554    }
555
556    fn resolve_visible_path(&self, path: &ast::Path) -> Option<NamedThing> {
557        self.root.resolve_visible_path(path)
558    }
559
560    fn resolve_any_path(&self, path: &ast::Path) -> Option<NamedThing> {
561        self.root.resolve_any_path(path)
562    }
563
564    fn add_diagnostic(&self, diag: Diagnostic) {
565        self.root.diagnostics.borrow_mut().push(diag)
566    }
567
568    fn get_context_type(&self) -> Option<TypeId> {
569        self.root.get_context_type()
570    }
571}
572
573impl<'a, 'b> BlockScope<'a, 'b> {
574    pub fn new(root: &'a FunctionScope<'b>, typ: BlockScopeType) -> Self {
575        BlockScope {
576            root,
577            parent: None,
578            variable_defs: BTreeMap::new(),
579            constant_defs: RefCell::new(BTreeMap::new()),
580            typ,
581        }
582    }
583
584    pub fn new_child(&'a self, typ: BlockScopeType) -> Self {
585        BlockScope {
586            root: self.root,
587            parent: Some(self),
588            variable_defs: BTreeMap::new(),
589            constant_defs: RefCell::new(BTreeMap::new()),
590            typ,
591        }
592    }
593
594    /// Add a variable to the block scope.
595    pub fn add_var(
596        &mut self,
597        name: &str,
598        typ: TypeId,
599        is_const: bool,
600        span: Span,
601    ) -> Result<(), AlreadyDefined> {
602        match self.resolve_name(name, span) {
603            Ok(Some(NamedThing::SelfValue { .. })) => {
604                let err = self.error(
605                    "`self` can't be used as a variable name",
606                    span,
607                    "expected a name, found keyword `self`",
608                );
609                Err(AlreadyDefined::new(err))
610            }
611
612            Ok(Some(named_item)) => {
613                if named_item.is_builtin() {
614                    let err = self.error(
615                        &format!(
616                            "variable name conflicts with built-in {}",
617                            named_item.item_kind_display_name(),
618                        ),
619                        span,
620                        &format!(
621                            "`{}` is a built-in {}",
622                            name,
623                            named_item.item_kind_display_name()
624                        ),
625                    );
626                    Err(AlreadyDefined::new(err))
627                } else {
628                    // It's (currently) an error to shadow a variable in a nested scope
629                    let err = self.duplicate_name_error(
630                        &format!("duplicate definition of variable `{name}`"),
631                        name,
632                        named_item
633                            .name_span(self.db())
634                            .expect("missing name_span of non-builtin"),
635                        span,
636                    );
637                    Err(AlreadyDefined::new(err))
638                }
639            }
640            _ => {
641                self.variable_defs
642                    .insert(name.to_string(), (typ, is_const, span));
643                Ok(())
644            }
645        }
646    }
647}
648
649/// temporary helper until `BTreeMap::try_insert` is stabilized
650trait OptionExt {
651    fn expect_none(self, msg: &str);
652}
653
654impl<T> OptionExt for Option<T> {
655    fn expect_none(self, msg: &str) {
656        if self.is_some() {
657            panic!("{}", msg)
658        }
659    }
660}
661
662/// Check an item visibility and sink diagnostics if an item is invisible from
663/// the scope.
664pub fn check_visibility(context: &dyn AnalyzerContext, named_thing: &NamedThing, span: Span) {
665    if let NamedThing::Item(item) = named_thing {
666        let item_module = item
667            .module(context.db())
668            .unwrap_or_else(|| context.module());
669        if !item.is_public(context.db()) && item_module != context.module() {
670            let module_name = item_module.name(context.db());
671            let item_name = item.name(context.db());
672            let item_span = item.name_span(context.db()).unwrap_or(span);
673            let item_kind_name = item.item_kind_display_name();
674            context.fancy_error(
675                &format!("the {item_kind_name} `{item_name}` is private",),
676                vec![
677                    Label::primary(span, format!("this {item_kind_name} is not `pub`")),
678                    Label::secondary(item_span, format!("`{item_name}` is defined here")),
679                ],
680                vec![
681                    format!("`{item_name}` can only be used within `{module_name}`"),
682                    format!(
683                        "Hint: use `pub` to make `{item_name}` visible from outside of `{module_name}`",
684                    ),
685                ],
686            );
687        }
688    }
689}
690
691fn is_visible(context: &dyn AnalyzerContext, named_thing: &NamedThing) -> bool {
692    if let NamedThing::Item(item) = named_thing {
693        let item_module = item
694            .module(context.db())
695            .unwrap_or_else(|| context.module());
696        item.is_public(context.db()) || item_module == context.module()
697    } else {
698        true
699    }
700}