Skip to main content

spade_hir/
symbol_table.rs

1use colored::Colorize;
2use itertools::{EitherOrBoth, Itertools};
3use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};
4use serde::{Deserialize, Serialize};
5
6use spade_common::id_tracker::NameIdTracker;
7use spade_common::location_info::{Loc, WithLocation};
8use spade_common::name::{Identifier, NameID, Path, PathPrefix, PathSegment, Visibility};
9use spade_diagnostics::diagnostic::{Diagnostic, Subdiagnostic};
10use spade_types::meta_types::MetaType;
11
12use crate::{FunctionKind, ParameterList, TraitSpec, TypeParam, TypeSpec, UnitHead, UnitKind};
13
14#[derive(Debug, Clone, PartialEq)]
15pub enum LookupError {
16    NoSuchSymbol(Loc<Path>),
17    NotAThing(Loc<Path>),
18    NotATypeSymbol(Loc<Path>, Thing),
19    NotAVariable(Loc<Path>, Thing),
20    NotAUnit(Loc<Path>, Thing),
21    NotAnEnumVariant(Loc<Path>, Thing),
22    NotAPatternableType(Loc<Path>, Thing),
23    NotAStruct(Loc<Path>, Thing),
24    NotAValue(Loc<Path>, Thing),
25    NotAComptimeValue(Loc<Path>, Thing),
26    NotATrait(Loc<Path>, Thing),
27    IsAType(Loc<Path>, Loc<()>),
28    BarrierError(Diagnostic),
29    TooManySuperSegments(Loc<Path>, PathSegment),
30    NotVisible {
31        target_segment: PathSegment,
32        invisible_segment: PathSegment,
33        /// This is present if the reason the target item is invisible is because of a module
34        /// further up the stack.
35        invisible_item: Option<Loc<()>>,
36        /// Normally this loc will always be present but in some panicking condition
37        /// we may end up not finding a loc. Since this is only a helper diagnostic anyway,
38        /// this will be empty in those situations
39        target_item: Option<Loc<()>>,
40        visibility_marker: Option<Loc<()>>,
41        suggest_modifications: bool,
42    },
43}
44
45impl From<LookupError> for Diagnostic {
46    fn from(lookup_error: LookupError) -> Diagnostic {
47        match &lookup_error {
48            LookupError::NoSuchSymbol(path) => {
49                Diagnostic::error(path, format!("Use of undeclared name {path}"))
50                    .primary_label("Undeclared name")
51            }
52            LookupError::NotAThing(path) => {
53                Diagnostic::error(path, format!("Use of {path} before it was declared"))
54                    .primary_label("Undeclared name")
55            }
56            LookupError::IsAType(path, loc) => {
57                Diagnostic::error(path, format!("Unexpected type {path}"))
58                    .primary_label("Unexpected type")
59                    .secondary_label(loc, format!("{path} is defined as a type here"))
60            }
61            LookupError::BarrierError(diag) => diag.clone(),
62            LookupError::NotATypeSymbol(path, got)
63            | LookupError::NotAVariable(path, got)
64            | LookupError::NotAUnit(path, got)
65            | LookupError::NotAnEnumVariant(path, got)
66            | LookupError::NotAPatternableType(path, got)
67            | LookupError::NotAStruct(path, got)
68            | LookupError::NotAValue(path, got)
69            | LookupError::NotATrait(path, got)
70            | LookupError::NotAComptimeValue(path, got) => {
71                let expected = match lookup_error {
72                    LookupError::NotATypeSymbol(_, _) => "a type",
73                    LookupError::NotAVariable(_, _) => "a variable",
74                    LookupError::NotAUnit(_, _) => "a unit",
75                    LookupError::NotAnEnumVariant(_, _) => "an enum variant",
76                    LookupError::NotAPatternableType(_, _) => "a patternable type",
77                    LookupError::NotAStruct(_, _) => "a struct",
78                    LookupError::NotAValue(_, _) => "a value",
79                    LookupError::NotAComptimeValue(_, _) => "a compile time value",
80                    LookupError::NotATrait(_, _) => "a trait",
81                    LookupError::NoSuchSymbol(_)
82                    | LookupError::IsAType(_, _)
83                    | LookupError::BarrierError(_)
84                    | LookupError::TooManySuperSegments(_, _)
85                    | LookupError::NotVisible { .. }
86                    | LookupError::NotAThing(_) => unreachable!(),
87                };
88
89                // an entity can be instantiated, ...
90                let hint = match lookup_error {
91                    LookupError::NotAComptimeValue(_, _) => {
92                        Some("compile time values can be defined with $config <name> = value")
93                    }
94                    _ => None,
95                };
96                let mut diagnostic =
97                    Diagnostic::error(path, format!("Expected {path} to be {expected}"))
98                        .primary_label(format!("Expected {expected}"))
99                        .secondary_label(got.loc(), format!("{path} is a {}", got.kind_string()));
100
101                if let Some(hint) = hint {
102                    diagnostic.add_help(hint);
103                }
104
105                match lookup_error {
106                    LookupError::NotAValue(path, Thing::EnumVariant(v)) => diagnostic
107                        .span_suggest_insert_after(
108                            "Consider specifying the arguments to the variant",
109                            path,
110                            format!(
111                                "({})",
112                                v.inner
113                                    .params
114                                    .0
115                                    .iter()
116                                    .map(|a| format!("/* {} */", a.name))
117                                    .join(", ")
118                            ),
119                        ),
120                    LookupError::NotAValue(path, Thing::Struct(v)) => diagnostic
121                        .span_suggest_insert_after(
122                            "Consider specifying the struct parameters",
123                            path,
124                            format!(
125                                "({})",
126                                v.inner
127                                    .params
128                                    .0
129                                    .iter()
130                                    .map(|a| format!("/*{}*/", a.name))
131                                    .join(", ")
132                            ),
133                        ),
134                    _ => diagnostic,
135                }
136            }
137            LookupError::TooManySuperSegments(path, segment) => {
138                Diagnostic::error(path, "Too many `super` segments")
139                    .primary_label("Too many `super` segments")
140                    .subdiagnostic(Subdiagnostic::span_note(
141                        segment.loc(),
142                        "parent does not exist for the root namespace",
143                    ))
144            }
145            LookupError::NotVisible {
146                target_segment,
147                invisible_segment,
148                target_item,
149                invisible_item,
150                visibility_marker,
151                suggest_modifications,
152            } => {
153                let diag = Diagnostic::error(
154                    target_segment.loc(),
155                    format!("{} is inaccessible", target_segment),
156                )
157                .primary_label(format!("{} is inaccessible", target_segment));
158
159                let diag = if *suggest_modifications {
160                    if let Some(pub_insertion) = invisible_item.or(*target_item) {
161                        if let Some(marker) = visibility_marker {
162                            diag.span_suggest_replace(
163                                "Consider changing the visibility to `pub`",
164                                marker,
165                                "pub",
166                            )
167                        } else {
168                            diag.span_suggest_insert_before(
169                                "Consider adding `pub`",
170                                pub_insertion,
171                                "pub ",
172                            )
173                            .span_suggest_insert_before(
174                                "Or a more limited visibility like pub(lib) or pub(super)",
175                                pub_insertion,
176                                "pub(lib) ",
177                            )
178                        }
179                    } else {
180                        diag
181                    }
182                } else {
183                    diag
184                };
185
186                let diag = if !invisible_segment.loc().is_same_loc(&target_segment.loc()) {
187                    diag.secondary_label(
188                        invisible_segment.loc(),
189                        format!("Because {invisible_segment} is inaccessible"),
190                    )
191                } else {
192                    diag
193                };
194
195                let diag = if let Some(loc) = target_item {
196                    diag.secondary_label(loc, "The inaccessible item is defined here")
197                } else {
198                    diag
199                };
200
201                if let Some(loc) = invisible_item {
202                    diag.secondary_label(
203                        loc,
204                        format!(
205                            "The item is inaccessible because {invisible_segment} is inaccessible"
206                        ),
207                    )
208                } else {
209                    diag
210                }
211            }
212        }
213    }
214}
215
216#[derive(Debug, Clone, PartialEq)]
217pub enum UniqueNameError {
218    MultipleDefinitions { new: Loc<Path>, prev: Loc<()> },
219}
220
221#[derive(PartialEq, Debug, Clone, Serialize, Deserialize)]
222pub struct EnumVariant {
223    pub name: Loc<Identifier>,
224    pub output_type: Loc<TypeSpec>,
225    pub option: usize,
226    pub params: Loc<ParameterList>,
227    pub type_params: Vec<Loc<TypeParam>>,
228    pub documentation: String,
229}
230
231impl EnumVariant {
232    pub fn as_unit_head(&self) -> UnitHead {
233        UnitHead {
234            name: self.name.clone(),
235            is_nonstatic_method: false,
236            inputs: self.params.clone(),
237            output_type: Some(self.output_type.clone()),
238            unit_type_params: self.type_params.clone(),
239            scope_type_params: self.type_params.clone(),
240            unit_kind: UnitKind::Function(FunctionKind::Enum).at_loc(&self.name),
241            where_clauses: vec![],
242            unsafe_marker: None,
243            documentation: String::new(),
244        }
245    }
246}
247
248#[derive(PartialEq, Debug, Clone, Serialize, Deserialize)]
249pub struct StructCallable {
250    pub name: Loc<Identifier>,
251    pub self_type: Loc<TypeSpec>,
252    pub params: Loc<ParameterList>,
253    pub type_params: Vec<Loc<TypeParam>>,
254}
255impl StructCallable {
256    pub fn as_unit_head(&self) -> UnitHead {
257        UnitHead {
258            name: self.name.clone(),
259            is_nonstatic_method: false,
260            inputs: self.params.clone(),
261            output_type: Some(self.self_type.clone()),
262            unit_type_params: self.type_params.clone(),
263            scope_type_params: vec![],
264            unit_kind: UnitKind::Function(FunctionKind::Struct).at_loc(&self.name),
265            where_clauses: vec![],
266            unsafe_marker: None,
267            documentation: String::new(),
268        }
269    }
270}
271
272#[derive(PartialEq, Debug, Clone, Serialize, Deserialize)]
273pub struct TraitMarker {
274    pub name: Loc<Identifier>,
275    pub paren_sugar: bool,
276}
277
278/// Any named thing in the language which is not a type. Structs are here for instantiation
279/// under the same NameID as the type
280#[derive(PartialEq, Debug, Clone, Serialize, Deserialize)]
281pub enum Thing {
282    /// Definition of a named type
283    Struct(Loc<StructCallable>),
284    EnumVariant(Loc<EnumVariant>),
285    Unit(Loc<UnitHead>),
286    Variable(Loc<Identifier>),
287    Alias {
288        loc: Loc<()>,
289        path: Loc<Path>,
290        in_namespace: Path,
291    },
292    ArrayLabel(Loc<usize>),
293    Module(Loc<()>, Loc<Identifier>),
294    /// Actual trait definition is present in the item list. This is only a marker
295    /// for there being a trait with the item name.
296    Trait(Loc<TraitMarker>),
297    /// Dummy entry in the symbol table which helps path resolution.
298    /// Currently used for trait namespaces, whose symbols are placed in a "ghost namespace"
299    /// named `impl#N` to avoid collisions between different `impl` blocks.
300    Dummy,
301}
302
303impl Thing {
304    pub fn kind_string(&self) -> &'static str {
305        match self {
306            Thing::Struct(_) => "struct",
307            Thing::Unit(_) => "unit",
308            Thing::Variable(_) => "variable",
309            Thing::EnumVariant(_) => "enum variant",
310            Thing::Alias { .. } => "alias",
311            Thing::ArrayLabel(_) => "array label",
312            Thing::Trait(_) => "trait",
313            Thing::Module(_, _) => "module",
314            Thing::Dummy => "dummy (implementation detail)",
315        }
316    }
317
318    /// The Loc of the entire Thing.
319    pub fn loc(&self) -> Loc<()> {
320        match self {
321            Thing::Struct(i) => i.loc(),
322            Thing::Variable(i) => i.loc(),
323            Thing::Unit(i) => i.loc(),
324            Thing::EnumVariant(i) => i.loc(),
325            Thing::Alias {
326                loc,
327                path: _,
328                in_namespace: _,
329            } => loc.loc(),
330            Thing::ArrayLabel(v) => v.loc(),
331            Thing::Trait(loc) => loc.loc(),
332            Thing::Module(loc, _name) => loc.loc(),
333            Thing::Dummy => ().nowhere(),
334        }
335    }
336
337    /// The Loc where the name of the thing is defined.
338    pub fn name_loc(&self) -> Loc<()> {
339        match self {
340            Thing::Struct(s) => s.name.loc(),
341            Thing::EnumVariant(v) => v.name.loc(),
342            Thing::Unit(f) => f.name.loc(),
343            Thing::Variable(v) => v.loc(),
344            Thing::ArrayLabel(l) => l.loc(),
345            Thing::Alias {
346                loc: _,
347                path,
348                in_namespace: _,
349            } => path.loc(),
350            Thing::Trait(loc) => loc.loc(),
351            Thing::Module(_loc, name) => name.loc(),
352            Thing::Dummy => ().nowhere(),
353        }
354    }
355}
356
357#[derive(PartialEq, Debug, Clone)]
358pub enum PatternableKind {
359    Struct,
360    Enum,
361}
362#[derive(PartialEq, Debug, Clone)]
363pub struct Patternable {
364    pub kind: PatternableKind,
365    pub params: Loc<ParameterList>,
366}
367
368#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
369pub enum GenericArg {
370    TypeName {
371        name: Identifier,
372        traits: Vec<Loc<TraitSpec>>,
373    },
374    TypeWithMeta {
375        name: Identifier,
376        meta: MetaType,
377    },
378}
379
380impl GenericArg {
381    pub fn uint(name: Identifier) -> Self {
382        GenericArg::TypeWithMeta {
383            name,
384            meta: MetaType::Uint,
385        }
386    }
387}
388
389#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
390pub enum TypeDeclKind {
391    Struct { is_port: bool },
392    Enum,
393    Primitive { is_port: bool, is_inout: bool },
394}
395
396impl TypeDeclKind {
397    pub fn normal_struct() -> Self {
398        TypeDeclKind::Struct { is_port: false }
399    }
400    pub fn struct_port() -> Self {
401        TypeDeclKind::Struct { is_port: true }
402    }
403
404    pub fn name(&self) -> String {
405        match self {
406            TypeDeclKind::Struct { is_port } => {
407                format!("struct{}", if *is_port { " port" } else { "" })
408            }
409            TypeDeclKind::Enum => "enum".to_string(),
410            TypeDeclKind::Primitive { .. } => "primitive".to_string(),
411        }
412    }
413}
414
415/// A previously declared type symbol
416#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
417pub enum TypeSymbol {
418    /// A fixed type that has been declared, like a typedef, enum or struct with the
419    /// specified generic arguments
420    Declared(Vec<Loc<GenericArg>>, TypeDeclKind),
421    /// A generic type present in the current scope
422    GenericArg {
423        traits: Vec<Loc<TraitSpec>>,
424    },
425    GenericMeta(MetaType),
426    /// A type alias. This is lowered during initial AST lowering, so subsequent compilation
427    /// stages can bail on finding this
428    Alias(Loc<TypeSpec>),
429}
430
431/// The declaration/definition status of a variable
432#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
433pub enum DeclarationState {
434    /// This variable has been defined by a `decl` statement
435    Undefined(NameID),
436    /// There is a pipeline reference to this variable, but no definition or declaration (yet)
437    Undecleared(NameID),
438    /// This variable has been defined (and assigned) with a let binding.
439    /// All variables must be in the declared state before the end of the scope
440    Defined(Loc<()>),
441}
442
443pub type ScopeBarrier =
444    dyn Fn(&Loc<Path>, &Loc<NameID>, &Thing) -> Result<(), Diagnostic> + Send + Sync;
445
446#[derive(Serialize, Deserialize)]
447pub struct Scope {
448    vars: HashMap<Path, NameID>,
449    #[serde(skip)]
450    lookup_barrier: Option<Box<ScopeBarrier>>,
451}
452impl std::fmt::Debug for Scope {
453    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
454        let Self {
455            vars,
456            lookup_barrier: _,
457        } = self;
458        write!(f, "Scope({vars:?})")
459    }
460}
461
462/// A table of the symbols known to the program in the current scope. Names
463/// are mapped to IDs which are then mapped to the actual things
464///
465/// Modules are managed by a special variable in the symtab. All names in the
466/// symtab are absolute paths, that is `X` in `mod A{mod B {fn X}}` will only be
467/// stored as `A::B::X`. All variables inside X will also have the full path
468/// appended to them. This should however be invisible to the user.
469#[derive(Debug, Serialize, Deserialize)]
470pub struct SymbolTable {
471    /// Each outer vec is a scope, inner vecs are symbols in that scope
472    pub symbols: Vec<Scope>,
473    pub declarations: Vec<HashMap<Loc<Identifier>, DeclarationState>>,
474    id_tracker: NameIdTracker,
475    pub types: HashMap<NameID, Loc<TypeSymbol>>,
476    pub things: HashMap<NameID, Thing>,
477    pub visibilities: HashMap<NameID, Loc<Visibility>>,
478    /// The namespace which we are currently in. When looking up and adding symbols, this namespace
479    /// is added to the start of the path, thus ensuring all paths are absolute. If a path is not
480    /// found that path is also looked up in the global namespace
481    namespace: Path,
482    /// The namespace which `lib` refers to currently.
483    base_namespace: Path,
484}
485
486impl Default for SymbolTable {
487    fn default() -> Self {
488        Self::new()
489    }
490}
491
492impl SymbolTable {
493    pub fn new() -> Self {
494        let mut result = Self {
495            symbols: vec![Scope {
496                vars: HashMap::default(),
497                lookup_barrier: None,
498            }],
499            declarations: vec![HashMap::default()],
500            id_tracker: NameIdTracker::new(),
501            types: HashMap::default(),
502            things: HashMap::default(),
503            visibilities: HashMap::default(),
504            namespace: Path(vec![]),
505            base_namespace: Path(vec![]),
506        };
507
508        result.add_thing(
509            Path(vec![]),
510            Thing::Module(().nowhere(), Identifier::intern("<root>").nowhere()),
511            None,
512        );
513
514        result
515    }
516    #[tracing::instrument(skip_all)]
517    pub fn new_scope(&mut self) {
518        self.symbols.push(Scope {
519            vars: HashMap::default(),
520            lookup_barrier: None,
521        });
522        self.declarations.push(HashMap::default());
523    }
524
525    pub fn new_scope_with_barrier(&mut self, barrier: Box<ScopeBarrier>) {
526        self.symbols.push(Scope {
527            vars: HashMap::default(),
528            lookup_barrier: Some(barrier),
529        });
530        self.declarations.push(HashMap::default());
531    }
532
533    #[tracing::instrument(skip_all)]
534    pub fn close_scope(&mut self) {
535        self.symbols.pop();
536        self.declarations.pop();
537    }
538
539    pub fn current_scope(&self) -> usize {
540        self.symbols.len() - 1
541    }
542
543    /// Push an identifier onto the current namespace
544    ///
545    /// Prefer using `ctx.in_namespace` over calling these methods directly
546    #[tracing::instrument(skip_all, fields(%new_segment))]
547    pub fn push_namespace(&mut self, new_segment: PathSegment) {
548        self.namespace = self.namespace.push_segment(new_segment);
549    }
550
551    #[tracing::instrument(skip_all)]
552    pub fn pop_namespace(&mut self) {
553        self.namespace = self.namespace.pop();
554    }
555
556    pub fn current_namespace(&self) -> &Path {
557        &self.namespace
558    }
559
560    pub fn set_base_namespace(&mut self, base_namespace: Path) {
561        self.base_namespace = base_namespace
562    }
563
564    /// Adds a thing to the scope at `current_scope - offset`. Panics if there is no such scope
565    pub fn add_thing_with_id_at_offset(
566        &mut self,
567        offset: usize,
568        id: u64,
569        name: Path,
570        item: Thing,
571        visibility: Option<Loc<Visibility>>,
572    ) -> NameID {
573        let full_name = self.namespace.join(name);
574        let name_id = NameID(id, full_name.clone());
575
576        if self.things.contains_key(&name_id) {
577            panic!("Duplicate nameID inserted, {}", id);
578        }
579        if offset > self.symbols.len() {
580            panic!("Not enough scopes to add symbol at offset {}", offset);
581        }
582
583        let index = self.symbols.len() - 1 - offset;
584        self.symbols[index].vars.insert(full_name, name_id.clone());
585        self.add_thing_with_name_id(name_id.clone(), item, visibility);
586
587        name_id
588    }
589
590    /// Add a thing to the symtab with the specified NameID. The NameID must already be in
591    /// the symtab when calling this function
592    pub fn add_thing_with_name_id(
593        &mut self,
594        name_id: NameID,
595        item: Thing,
596        visibility: Option<Loc<Visibility>>,
597    ) {
598        self.things.insert(name_id.clone(), item);
599        if let Some(vis) = visibility {
600            self.visibilities.insert(name_id, vis);
601        }
602    }
603
604    pub fn add_thing_with_id(
605        &mut self,
606        id: u64,
607        name: Path,
608        item: Thing,
609        visibility: Option<Loc<Visibility>>,
610    ) -> NameID {
611        self.add_thing_with_id_at_offset(0, id, name, item, visibility)
612    }
613
614    #[tracing::instrument(skip_all, fields(?name))]
615    pub fn add_unique_thing(
616        &mut self,
617        name: Loc<Path>,
618        item: Thing,
619        visibility: Option<Loc<Visibility>>,
620    ) -> Result<NameID, Diagnostic> {
621        self.ensure_is_unique(&name)?;
622        Ok(self.add_thing(name.inner, item, visibility))
623    }
624
625    pub fn add_thing(
626        &mut self,
627        name: Path,
628        item: Thing,
629        visibility: Option<Loc<Visibility>>,
630    ) -> NameID {
631        let id = self.id_tracker.next();
632        self.add_thing_with_id(id, name, item, visibility)
633    }
634
635    pub fn re_add_type(&mut self, name: Loc<Identifier>, name_id: NameID) {
636        assert!(self.types.contains_key(&name_id));
637        self.symbols
638            .last_mut()
639            .unwrap()
640            .vars
641            .insert(self.namespace.join(Path::ident(name)), name_id);
642    }
643
644    pub fn add_type_with_id(
645        &mut self,
646        id: u64,
647        name: Loc<Identifier>,
648        t: Loc<TypeSymbol>,
649        visibility: Loc<Visibility>,
650    ) -> NameID {
651        let full_name = self.namespace.push_ident(name);
652        let name_id = NameID(id, full_name.clone());
653
654        if self.types.contains_key(&name_id) {
655            panic!("Duplicate nameID for types, {}", id)
656        }
657
658        self.types.insert(name_id.clone(), t);
659        self.visibilities.insert(name_id.clone(), visibility);
660        self.symbols
661            .last_mut()
662            .unwrap()
663            .vars
664            .insert(full_name, name_id.clone());
665
666        name_id
667    }
668
669    pub fn add_type(
670        &mut self,
671        name: Loc<Identifier>,
672        t: Loc<TypeSymbol>,
673        visibility: Loc<Visibility>,
674    ) -> NameID {
675        let id = self.id_tracker.next();
676        self.add_type_with_id(id, name, t, visibility)
677    }
678
679    pub fn add_traits_to_generic(
680        &mut self,
681        name_id: &NameID,
682        traits: Vec<Loc<TraitSpec>>,
683    ) -> Result<(), Diagnostic> {
684        assert!(self.types.contains_key(name_id));
685        match &mut self.types.get_mut(name_id).unwrap().inner {
686            TypeSymbol::GenericArg { traits: existing } => {
687                existing.extend(traits);
688                Ok(())
689            }
690            _ => Err(Diagnostic::bug(
691                self.type_symbol_by_id(name_id).loc(),
692                "Attempted to add trait bounds to a non-generic type",
693            )),
694        }
695    }
696
697    pub fn add_unique_type(
698        &mut self,
699        name: Loc<Identifier>,
700        t: Loc<TypeSymbol>,
701        visibility: Loc<Visibility>,
702    ) -> Result<NameID, Diagnostic> {
703        self.ensure_is_unique(&Path::ident_with_loc(name.clone()))?;
704        Ok(self.add_type(name, t, visibility))
705    }
706
707    #[tracing::instrument(skip_all, fields(?name, ?target))]
708    pub fn add_alias(
709        &mut self,
710        loc: Loc<()>,
711        name: Loc<Identifier>,
712        target: Loc<Path>,
713        visibility: Loc<Visibility>,
714    ) -> Result<NameID, Diagnostic> {
715        self.ensure_is_unique(&Path::ident_with_loc(name.clone()))?;
716
717        Ok(self.add_thing(
718            Path::ident(name),
719            Thing::Alias {
720                loc: loc,
721                path: target,
722                in_namespace: self.current_namespace().clone(),
723            },
724            Some(visibility),
725        ))
726    }
727
728    /// Adds a thing to the scope at `current_scope - offset`. Panics if there is no such scope
729    pub fn add_thing_at_offset(
730        &mut self,
731        offset: usize,
732        name: Path,
733        item: Thing,
734        visibility: Option<Loc<Visibility>>,
735    ) -> NameID {
736        let id = self.id_tracker.next();
737        self.add_thing_with_id_at_offset(offset, id, name, item, visibility)
738    }
739
740    pub fn freeze(self) -> FrozenSymtab {
741        let id_tracker = self.id_tracker.make_clone();
742        FrozenSymtab {
743            inner: self,
744            id_tracker: id_tracker,
745        }
746    }
747
748    pub fn add_local_variable(&mut self, name: Loc<Identifier>) -> NameID {
749        self.add_local_variable_at_offset(0, name)
750    }
751    pub fn add_local_variable_at_offset(&mut self, offset: usize, name: Loc<Identifier>) -> NameID {
752        self.add_thing_at_offset(
753            offset,
754            Path::ident(name.clone()),
755            Thing::Variable(name),
756            None,
757        )
758    }
759
760    pub fn add_dummy(&mut self, segment: PathSegment) -> NameID {
761        self.add_thing(Path(vec![segment]), Thing::Dummy, None)
762    }
763
764    pub fn add_declaration(&mut self, ident: Loc<Identifier>) -> Result<NameID, Diagnostic> {
765        let declared_more_than_once = |new, old| {
766            Diagnostic::error(new, "Variable declared more than once")
767                .primary_label("This variable has been declared more than once")
768                .secondary_label(old, "Previously declared here")
769        };
770        // Check if a variable with this name already exists
771        if let Some(id) = self.try_lookup_id(&Path::ident_with_loc(ident.clone())) {
772            if let Some(Thing::Variable(prev)) = self.things.get(&id) {
773                return Err(declared_more_than_once(ident, prev));
774            }
775        }
776
777        if let Some((old, _)) = self.declarations.last().unwrap().get_key_value(&ident) {
778            Err(declared_more_than_once(ident, old))
779        } else {
780            let name_id = self.add_local_variable(ident.clone());
781            self.declarations
782                .last_mut()
783                .unwrap()
784                .insert(ident, DeclarationState::Undefined(name_id.clone()));
785            Ok(name_id)
786        }
787    }
788
789    pub fn add_undecleared_at_offset(&mut self, offset: usize, name: Loc<Identifier>) -> NameID {
790        let path = Path::ident(name.clone());
791
792        let name_id = NameID(self.id_tracker.next(), path.clone());
793        let full_name = self.namespace.join(path);
794
795        let index = self.symbols.len() - 1 - offset;
796        if index > self.symbols.len() {
797            panic!("Not enough scopes to add symbol at offset {}", offset);
798        }
799        self.symbols[index].vars.insert(full_name, name_id.clone());
800        self.declarations[index].insert(name, DeclarationState::Undecleared(name_id.clone()));
801
802        name_id
803    }
804
805    pub fn get_declaration(&mut self, ident: &Loc<Identifier>) -> Option<Loc<DeclarationState>> {
806        self.declarations
807            .last()
808            .unwrap()
809            .get_key_value(ident)
810            .map(|(k, v)| v.clone().at_loc(k))
811    }
812
813    pub fn mark_declaration_defined(&mut self, ident: Loc<Identifier>, definition_point: Loc<()>) {
814        *self
815            .declarations
816            .last_mut()
817            .unwrap()
818            .get_mut(&ident)
819            .unwrap() = DeclarationState::Defined(definition_point)
820    }
821
822    pub fn get_undefined_declarations(&self) -> Vec<(Loc<Identifier>, DeclarationState)> {
823        self.declarations
824            .last()
825            .unwrap()
826            .iter()
827            .filter_map(|(ident, state)| match state {
828                DeclarationState::Undefined(_) => Some((ident.clone(), state.clone())),
829                DeclarationState::Undecleared(_) => Some((ident.clone(), state.clone())),
830                DeclarationState::Defined(_) => None,
831            })
832            .collect()
833    }
834
835    pub fn lookup_thing_impl(
836        &self,
837        path: &Loc<Path>,
838        check_visibility: bool,
839    ) -> Result<(NameID, &Thing), LookupError> {
840        let id = self.lookup_id(path, check_visibility)?;
841
842        match self.things.get(&id) {
843            Some(thing) => Ok((id, thing)),
844            None => match self.types.get(&id) {
845                Some(ty) => Err(LookupError::IsAType(path.clone(), ty.loc())),
846                None => Err(LookupError::NotAThing(path.clone())),
847            },
848        }
849    }
850
851    pub fn lookup_thing_ignore_visibility(
852        &self,
853        path: &Loc<Path>,
854    ) -> Result<(NameID, &Thing), LookupError> {
855        self.lookup_thing_impl(path, false)
856    }
857
858    pub fn lookup_thing(&self, path: &Loc<Path>) -> Result<(NameID, &Thing), LookupError> {
859        self.lookup_thing_impl(path, true)
860    }
861}
862
863macro_rules! thing_accessors {
864    (
865        $(
866            $by_id_name:ident,
867            $lookup_name:ident,
868            $lookup_ignore_visibility:ident,
869            $result:path,
870            $err:ident $(,)?
871            {$($thing:pat => $conversion:expr),*$(,)?}
872        ),*
873    ) => {
874        $(
875            /// Look up an item and panic if the item is not in the symtab or if it is the wrong
876            /// type
877            pub fn $by_id_name(&self, id: &NameID) -> Loc<$result> {
878                match self.things.get(&id) {
879                    $(
880                        Some($thing) => {$conversion}
881                    )*,
882                    Some(other) => panic!("attempted to look up {} but it was {:?}", stringify!($result), other),
883                    None => panic!("No thing entry found for {:?}", id)
884                }
885            }
886
887            /// Look up an item, with errors if the item is not currently in scope, or is not
888            /// convertible to the return type.
889            #[tracing::instrument(level = "trace", skip_all, fields(%name.inner, %name.span, %name.file_id))]
890            pub fn $lookup_name(&self, name: &Loc<Path>) -> Result<(NameID, Loc<$result>), LookupError> {
891                let (id, thing) = self.lookup_thing(name)?;
892
893                match thing {
894                    $(
895                        $thing => {Ok((id, $conversion))}
896                    )*,
897                    other => Err(LookupError::$err(name.clone(), other.clone())),
898                }
899            }
900
901            /// Look up an item without performing visibility checks, with errors if the item is not currently in scope, or is not
902            /// convertible to the return type.
903            #[tracing::instrument(level = "trace", skip_all, fields(%name.inner, %name.span, %name.file_id))]
904            pub fn $lookup_ignore_visibility(&self, name: &Loc<Path>) -> Result<(NameID, Loc<$result>), LookupError> {
905                let (id, thing) = self.lookup_thing_ignore_visibility(name)?;
906
907                match thing {
908                    $(
909                        $thing => {Ok((id, $conversion))}
910                    )*,
911                    other => Err(LookupError::$err(name.clone(), other.clone())),
912                }
913            }
914        )*
915    }
916}
917
918impl SymbolTable {
919    // Define accessors for accessing items. *_by_id looks up things under the
920    // assumption that the name is in the symtab, and that it is the specified type.
921    // If this is not true, it panics.
922    //
923    // lookup_* looks up items by path, and returns the NameID and item if successful.
924    // If the path is not in scope, or the item is not the right kind, returns an error.
925    thing_accessors! {
926        unit_by_id, lookup_unit, lookup_unit_ignore_visibility, UnitHead, NotAUnit {
927            Thing::Unit(head) => head.clone(),
928            Thing::EnumVariant(variant) => variant.as_unit_head().at_loc(variant),
929            Thing::Struct(s) => s.as_unit_head().at_loc(s),
930        },
931        enum_variant_by_id, lookup_enum_variant, lookup_enum_variant_ignore_visibility, EnumVariant, NotAnEnumVariant {
932            Thing::EnumVariant(variant) => variant.clone()
933        },
934        patternable_type_by_id, lookup_patternable_type, lookup_patternable_type_ignore_visibility, Patternable, NotAPatternableType {
935            Thing::EnumVariant(variant) => Patternable{
936                kind: PatternableKind::Enum,
937                params: variant.params.clone()
938            }.at_loc(variant),
939            Thing::Struct(variant) => Patternable {
940                kind: PatternableKind::Struct,
941                params: variant.params.clone()
942            }.at_loc(variant),
943        },
944        struct_by_id, lookup_struct, lookup_struct_ignore_visibility, StructCallable, NotAStruct {
945            Thing::Struct(s) => s.clone()
946        },
947        trait_by_id, lookup_trait, lookup_trait_ignore_visibility, TraitMarker, NotATrait {
948            Thing::Trait(t) => t.clone()
949        }
950    }
951
952    pub fn type_symbol_by_id(&self, id: &NameID) -> Loc<TypeSymbol> {
953        match self.types.get(id) {
954            Some(inner) => inner.clone(),
955            None => panic!("No thing entry found for {:?}", id),
956        }
957    }
958
959    pub fn try_type_symbol_by_id(&self, id: &NameID) -> Option<&Loc<TypeSymbol>> {
960        self.types.get(id)
961    }
962
963    pub fn thing_by_id(&self, id: &NameID) -> Option<&Thing> {
964        self.things.get(id)
965    }
966
967    pub fn lookup_type_symbol(
968        &self,
969        name: &Loc<Path>,
970    ) -> Result<(NameID, Loc<TypeSymbol>), LookupError> {
971        let id = self.lookup_id(name, true)?;
972
973        match self.types.get(&id) {
974            Some(tsym) => Ok((id, tsym.clone())),
975            None => match self.things.get(&id) {
976                Some(thing) => Err(LookupError::NotATypeSymbol(name.clone(), thing.clone())),
977                None => panic!("{:?} was in symtab but is neither a type nor a thing", id),
978            },
979        }
980    }
981
982    pub fn has_symbol(&self, name: Path) -> bool {
983        match self.lookup_id(&name.nowhere(), false) {
984            Ok(_) => true,
985            Err(LookupError::NoSuchSymbol(_)) => false,
986            Err(LookupError::BarrierError(_)) => unreachable!(),
987            Err(LookupError::NotATypeSymbol(_, _)) => unreachable!(),
988            Err(LookupError::NotAVariable(_, _)) => unreachable!(),
989            Err(LookupError::NotAUnit(_, _)) => unreachable!(),
990            Err(LookupError::NotAPatternableType(_, _)) => unreachable!(),
991            Err(LookupError::NotAnEnumVariant(_, _)) => unreachable!(),
992            Err(LookupError::NotAStruct(_, _)) => unreachable!(),
993            Err(LookupError::NotAValue(_, _)) => unreachable!(),
994            Err(LookupError::NotAComptimeValue(_, _)) => unreachable!(),
995            Err(LookupError::NotATrait(_, _)) => unreachable!(),
996            Err(LookupError::IsAType(_, _)) => unreachable!(),
997            Err(LookupError::NotAThing(_)) => unreachable!(),
998            Err(LookupError::TooManySuperSegments(_, _)) => unreachable!(),
999            Err(LookupError::NotVisible { .. }) => unreachable!(),
1000        }
1001    }
1002
1003    /// Look up the previous definition of `name` returning a "Multiple items with the same name" error if
1004    /// such definition already exists. Only an absolute path in the root name space is checked
1005    /// as this is intended to be used for item definitions
1006    pub fn ensure_is_unique(&self, name: &Loc<Path>) -> Result<(), Diagnostic> {
1007        let full_path = self.current_namespace().join(name.inner.clone());
1008
1009        let prev = self
1010            .symbols
1011            .first()
1012            .unwrap()
1013            .vars
1014            .get(&full_path)
1015            .and_then(|id| {
1016                self.things
1017                    .get(id)
1018                    .map(|thing| thing.name_loc())
1019                    .or_else(|| self.types.get(id).map(|t| t.loc()))
1020            });
1021
1022        match prev {
1023            Some(prev) => Err(Diagnostic::error(name, "Multiple items with the same name")
1024                .primary_label(format!("{} is defined multiple times", name))
1025                .secondary_label(prev, "Previous definition here")),
1026            None => Ok(()),
1027        }
1028    }
1029
1030    pub fn lookup_variable(&self, name: &Loc<Path>) -> Result<NameID, LookupError> {
1031        let id = self.lookup_id(name, false)?;
1032
1033        match self.things.get(&id) {
1034            Some(Thing::Variable(_)) => Ok(id),
1035            Some(other) => Err(LookupError::NotAVariable(name.clone(), other.clone())),
1036            None => match self.types.get(&id) {
1037                Some(ty) => Err(LookupError::IsAType(name.clone(), ty.loc())),
1038                None => Err(LookupError::NotAThing(name.clone())),
1039            },
1040        }
1041    }
1042
1043    /// Look up `name`. If it is defined and a variable, return that name. If it is defined
1044    /// but not a variable, return an error. If it is undefined, return None
1045    ///
1046    /// Intended for use if undefined variables should be declared
1047    pub fn try_lookup_variable(&self, name: &Loc<Path>) -> Result<Option<NameID>, LookupError> {
1048        let id = self.try_lookup_id(name);
1049        match id {
1050            Some(id) => match self.things.get(&id) {
1051                Some(Thing::Variable(_)) => Ok(Some(id)),
1052                Some(other) => Err(LookupError::NotAVariable(name.clone(), other.clone())),
1053                None => match self.types.get(&id) {
1054                    Some(ty) => Err(LookupError::IsAType(name.clone(), ty.loc())),
1055                    None => Ok(None),
1056                },
1057            },
1058            None => Ok(None),
1059        }
1060    }
1061
1062    pub fn try_lookup_id(&self, name: &Loc<Path>) -> Option<NameID> {
1063        match self.lookup_id(name, true) {
1064            Ok(id) => Some(id),
1065            Err(LookupError::NoSuchSymbol(_)) => None,
1066            Err(err) => unreachable!("Got {err:?} when looking up final ID"),
1067        }
1068    }
1069
1070    /// Returns the name ID of the provided path if that path exists and resolving
1071    /// all aliases along the way.
1072    pub fn lookup_id(
1073        &self,
1074        name: &Loc<Path>,
1075        check_visibility: bool,
1076    ) -> Result<NameID, LookupError> {
1077        self.lookup_id_in_namespace(name, &self.namespace, check_visibility)
1078    }
1079
1080    /// Returns the name ID of the provided path if that path exists and resolving
1081    /// all aliases along the way.
1082    pub fn lookup_id_in_namespace(
1083        &self,
1084        name: &Loc<Path>,
1085        namespace: &Path,
1086        check_visibility: bool,
1087    ) -> Result<NameID, LookupError> {
1088        let (offset, path) = self.canonicalize_in_namespace(
1089            name,
1090            namespace,
1091            &Default::default(),
1092            0,
1093            check_visibility,
1094        )?;
1095
1096        let id = self.symbols[self.current_scope() - offset]
1097            .vars
1098            .get(&path)
1099            .expect("Canonical path not present in symbol table (that is impossible)")
1100            .clone();
1101
1102        if let Some(thing) = self.things.get(&id) {
1103            self.symbols
1104                .iter()
1105                .rev()
1106                .take(offset)
1107                .flat_map(|s| &s.lookup_barrier)
1108                .try_for_each(|b| (b)(name, &id.clone().at_loc(&thing.name_loc()), thing))
1109                .map_err(LookupError::BarrierError)?;
1110        }
1111
1112        Ok(id)
1113    }
1114
1115    fn canonicalize_in_namespace(
1116        &self,
1117        path: &Loc<Path>,
1118        namespace: &Path,
1119        forbidden: &HashSet<NameID>,
1120        offset: usize,
1121        check_visibility: bool,
1122    ) -> Result<(usize, Path), LookupError> {
1123        // Find which namespace this path belongs (lib-relative, local or dep-relative)
1124        let (mut scope_offset, mut working_path, segments) = match path.extract_prefix() {
1125            (PathPrefix::FromLib, lib_relative_path) => {
1126                let segments = lib_relative_path.0.clone();
1127                let off = self.current_scope();
1128                let ns = self.base_namespace.clone();
1129
1130                (off, ns, segments)
1131            }
1132            (PathPrefix::FromSuper(levels), super_relative_path) => {
1133                let segments = super_relative_path.0.clone();
1134                let off = self.current_scope();
1135
1136                let ns = if namespace.0.len() >= levels {
1137                    Path(Vec::from(&namespace.0[0..namespace.0.len() - levels]))
1138                } else {
1139                    let segment = path.0[levels - 1].clone();
1140                    return Err(LookupError::TooManySuperSegments(path.clone(), segment));
1141                };
1142
1143                (off, ns, segments)
1144            }
1145            (PathPrefix::FromSelf, self_relative_path) => {
1146                let mut segments = self_relative_path.0.clone();
1147                let mut off = self.current_scope();
1148                let ns = namespace.clone();
1149
1150                // If `path` is just `self` it may be a method `self` parameter.
1151                // Try to find it, otherwise fall back to `self` namespace.
1152                if self_relative_path.0.is_empty() {
1153                    let head = path.0[0].clone();
1154                    let absolute_head = namespace.push_segment(head);
1155
1156                    if let Some((new_offset, _)) = self
1157                        .symbols
1158                        .iter()
1159                        .rev()
1160                        .enumerate()
1161                        .skip(offset)
1162                        .find(|(_, s)| s.vars.contains_key(&absolute_head))
1163                    {
1164                        segments = path.0.clone();
1165                        off = new_offset;
1166                    }
1167                }
1168
1169                (off, ns, segments)
1170            }
1171            (PathPrefix::None, _) => {
1172                if path.0.is_empty() {
1173                    (0, self.current_namespace().clone(), vec![])
1174                } else {
1175                    let segments = path.0.clone();
1176                    let head = path.0[0].clone();
1177                    let absolute_head = namespace.push_segment(head.clone());
1178
1179                    let (off, ns) = self
1180                        .symbols
1181                        .iter()
1182                        .rev()
1183                        .enumerate()
1184                        .skip(offset)
1185                        .find_map(|(o, s)| {
1186                            s.vars.get(&absolute_head).map(|_| (o, namespace.clone()))
1187                        })
1188                        .unwrap_or((self.current_scope(), Path(vec![])));
1189
1190                    (off, ns, segments)
1191                }
1192            }
1193        };
1194
1195        // Walk through all segments, resolving any aliases found along the way,
1196        // until the final thing is found or hitting a roadblock.
1197        for segment in segments.iter() {
1198            let mut local_forbidden = forbidden.clone();
1199            let idx = self.current_scope() - scope_offset;
1200            let scope = &self.symbols[idx];
1201            working_path = working_path.push_segment(segment.clone());
1202
1203            loop {
1204                // If the (partially extended) working path does not exist,
1205                // a missing item has been found inside it.
1206                let Some(id) = scope.vars.get(&working_path) else {
1207                    let segment_path = working_path.at_loc(&segment.loc());
1208                    return Err(LookupError::NoSuchSymbol(segment_path));
1209                };
1210
1211                // Forbid cyclic aliases, declaring them as missing items.
1212                if forbidden.contains(id) {
1213                    let segment_path = working_path.at_loc(&segment.loc());
1214                    return Err(LookupError::NoSuchSymbol(segment_path));
1215                }
1216
1217                if check_visibility {
1218                    // Visibility information may not apply for the path target (e.g., local variables).
1219                    // If visibility information does not exist, the check is ignored.
1220                    if let Some(visibility) = self.visibilities.get(id) {
1221                        let vis_path = match visibility.inner {
1222                            Visibility::Public => Path(vec![]),
1223                            Visibility::Implicit => working_path.prelude(),
1224                            Visibility::AtLib => self.base_namespace.clone(),
1225                            Visibility::AtSelf => working_path.prelude(),
1226                            Visibility::AtSuper => working_path.prelude().prelude(),
1227                            Visibility::AtSuperSuper => working_path.prelude().prelude().prelude(),
1228                        };
1229
1230                        let is_visible =
1231                            vis_path
1232                                .0
1233                                .iter()
1234                                .zip_longest(&namespace.0)
1235                                .all(|entry| match entry {
1236                                    EitherOrBoth::Both(l, r) => l == r,
1237                                    EitherOrBoth::Left(_) => false,
1238                                    EitherOrBoth::Right(_) => true,
1239                                });
1240
1241                        if !is_visible {
1242                            let target_id =
1243                                self.lookup_id_in_namespace(path, namespace, false).ok();
1244
1245                            let target_item = target_id.as_ref().and_then(|id| {
1246                                self.thing_by_id(id)
1247                                    .map(Thing::loc)
1248                                    .or_else(|| self.try_type_symbol_by_id(id).map(|ty| ty.loc()))
1249                            });
1250
1251                            let invisible_item = if target_id != Some(id.clone()) {
1252                                self.things.get(id)
1253                            } else {
1254                                None
1255                            };
1256
1257                            return Err(LookupError::NotVisible {
1258                                target_segment: path.tail(),
1259                                invisible_segment: segment.clone(),
1260                                target_item,
1261                                invisible_item: invisible_item.map(Thing::loc),
1262                                visibility_marker: if visibility.inner != Visibility::Implicit {
1263                                    Some(visibility.loc())
1264                                } else {
1265                                    None
1266                                },
1267                                // We don't want to suggest modifications outside the current project
1268                                // We can't know that for sure currently, but a decent heuristic is if
1269                                // the lookuped item is in the same base namespace. This will give false positives
1270                                // if there is a visibility issue fully in a single dependency, which I think
1271                                // is an OK tradeoff
1272                                suggest_modifications: target_id
1273                                    .map(|id| id.1.starts_with(&self.base_namespace))
1274                                    .unwrap_or(false),
1275                            });
1276                        }
1277                    }
1278                }
1279
1280                if self.types.contains_key(id) {
1281                    break;
1282                }
1283
1284                match self.things.get(id) {
1285                    Some(Thing::Alias {
1286                        loc: _,
1287                        path,
1288                        in_namespace,
1289                    }) => {
1290                        local_forbidden.insert(id.clone());
1291
1292                        // Alias target is itself a non-canonical path, it must be resolved first.
1293                        (scope_offset, working_path) = self.canonicalize_in_namespace(
1294                            path,
1295                            in_namespace,
1296                            &local_forbidden,
1297                            offset,
1298                            check_visibility,
1299                        )?;
1300                    }
1301                    _ => break,
1302                }
1303            }
1304        }
1305
1306        // The working path has been walked and all aliases have been resolved.
1307        // It now definitely points to the thing (that is not an alias).
1308        Ok((scope_offset, working_path))
1309    }
1310
1311    pub fn print_symbols(&self) {
1312        println!("=============================================================");
1313        println!("                      Symtab dump");
1314        println!("=============================================================");
1315        println!("Current namespace is `{}`", self.namespace);
1316        println!("Current base_namespace is `{}`", self.base_namespace);
1317        for (level, scope) in self.symbols.iter().enumerate() {
1318            let indent = (1..level + 1).map(|_| "\t").collect::<Vec<_>>().join("");
1319            println!(">>> new_scope",);
1320
1321            for (path, name) in &scope.vars {
1322                println!(
1323                    "{indent}{path} => {name}",
1324                    path = format!("{path}").cyan(),
1325                    name = format!("{name:?}").yellow()
1326                );
1327            }
1328        }
1329
1330        println!("Things:");
1331        for (name, thing) in &self.things {
1332            print!("{}: ", format!("{name:?}").purple());
1333            match thing {
1334                Thing::Struct(_) => println!("struct"),
1335                Thing::EnumVariant(_) => println!("enum variant"),
1336                Thing::Unit(_) => println!("unit"),
1337                Thing::Variable(_) => println!("variable"),
1338                Thing::ArrayLabel(_) => println!("array label"),
1339                Thing::Alias {
1340                    loc: _,
1341                    path,
1342                    in_namespace,
1343                } => {
1344                    println!("{}", format!("alias => {path} in {in_namespace}").green())
1345                }
1346                Thing::Trait(t) => println!("trait {}", t.name),
1347                Thing::Module(_, name) => println!("mod {name}"),
1348                Thing::Dummy => println!("dummy"),
1349            }
1350        }
1351
1352        println!("Types:");
1353        for name in self.types.keys() {
1354            print!("{}: ", format!("{name:?}").purple());
1355            println!("type");
1356        }
1357    }
1358}
1359
1360/// A symbol table that cannot have any new symbols added to it. The ID tracker can be used to
1361/// generate new names for for intermediate variables during codegen.
1362///
1363/// Mutable references to `SymbolTable` are never given out, ensuring that nothing can be added to
1364/// the symtab, thus avoiding collisions with things added using the Id tracker.
1365#[derive(Serialize, Deserialize)]
1366pub struct FrozenSymtab {
1367    inner: SymbolTable,
1368    pub id_tracker: NameIdTracker,
1369}
1370
1371impl FrozenSymtab {
1372    pub fn symtab(&self) -> &SymbolTable {
1373        &self.inner
1374    }
1375
1376    pub fn new_name(&self, description: Path) -> NameID {
1377        NameID(self.id_tracker.next(), description)
1378    }
1379
1380    /// Unfreeze the symtab, removing access to the underlying id_tracker and
1381    /// giving ownership of the symtab again
1382    pub fn unfreeze(self) -> SymbolTable {
1383        // Ensure that we will not generate any conflicting IDs by re combining
1384        // this with the new ID trakcer by ensuring that the new ID tracker is further
1385        // along than the symtabs
1386        SymbolTable {
1387            id_tracker: self.id_tracker,
1388            ..self.inner
1389        }
1390    }
1391}