llmcc_core/
symbol.rs

1//! Symbol and scope management for the code graph.
2//!
3//! This module defines the core data structures for tracking named entities (symbols) in source code:
4//! - `Symbol`: Represents a named entity (function, struct, variable, etc.) with metadata
5//! - `SymId`: Unique identifier for symbols
6//! - `ScopeId`: Unique identifier for scopes
7//!
8//! Symbols are allocated in an arena for efficient memory management and are thread-safe via RwLock.
9//! Names are interned for fast equality comparisons.
10
11use parking_lot::RwLock;
12use std::fmt;
13use strum_macros::EnumIter;
14
15use crate::graph_builder::BlockId;
16use crate::interner::InternedStr;
17use crate::ir::HirId;
18use std::sync::atomic::{AtomicBool, AtomicU8, AtomicU32, AtomicU64, AtomicUsize, Ordering};
19
20/// Sentinel value for "not set" in unit/crate index.
21pub const INDEX_NONE: u32 = u32::MAX;
22
23/// Global atomic counter for assigning unique symbol IDs.
24/// Incremented on each new symbol creation to ensure uniqueness.
25static NEXT_SYMBOL_ID: AtomicUsize = AtomicUsize::new(0);
26
27/// Resets the global symbol ID counter to 0.
28/// Use this only during testing or when resetting the entire symbol table.
29#[inline]
30pub fn reset_symbol_id_counter() {
31    NEXT_SYMBOL_ID.store(0, Ordering::Relaxed);
32}
33
34/// Global atomic counter for assigning unique scope IDs.
35/// Incremented on each new scope creation to ensure uniqueness.
36pub(crate) static NEXT_SCOPE_ID: AtomicUsize = AtomicUsize::new(0);
37
38/// Resets the global scope ID counter to 0.
39/// Use this only during testing or when resetting the entire scope table.
40#[inline]
41pub fn reset_scope_id_counter() {
42    NEXT_SCOPE_ID.store(0, Ordering::Relaxed);
43}
44
45/// Unique identifier for symbols within a compilation unit.
46/// Symbols are allocated sequentially, starting from ID 1.
47#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Default, PartialOrd, Ord)]
48pub struct SymId(pub usize);
49
50impl std::fmt::Display for SymId {
51    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52        write!(f, "{}", self.0)
53    }
54}
55
56/// Unique identifier for scopes within a compilation unit.
57/// Scopes are allocated sequentially, starting from ID 1.
58#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Default, PartialOrd, Ord)]
59pub struct ScopeId(pub usize);
60
61impl std::fmt::Display for ScopeId {
62    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63        write!(f, "{}", self.0)
64    }
65}
66
67/// Classification of what kind of named entity a symbol represents.
68#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, EnumIter, Default)]
69#[repr(u8)]
70pub enum SymKind {
71    #[default]
72    Unknown = 0,
73    UnresolvedType = 1,
74    Crate = 2,
75    Module = 3,
76    File = 4,
77    Namespace = 5,
78    Struct = 6,
79    Enum = 7,
80    Function = 8,
81    Method = 9,
82    Closure = 10,
83    Macro = 11,
84    Variable = 12,
85    Field = 13,
86    Const = 14,
87    Static = 15,
88    Trait = 16,
89    Interface = 17,
90    Impl = 18,
91    EnumVariant = 19,
92    Primitive = 20,
93    TypeAlias = 21,
94    TypeParameter = 22,
95    GenericType = 23,
96    CompositeType = 24,
97}
98
99/// A bitset representing a set of SymKind values for efficient O(1) containment checks.
100/// Uses a u32 internally since we have < 32 SymKind variants.
101#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
102pub struct SymKindSet(u32);
103
104impl SymKindSet {
105    /// Create an empty set.
106    #[inline]
107    pub const fn empty() -> Self {
108        Self(0)
109    }
110
111    /// Create a set containing all kinds.
112    #[inline]
113    pub const fn all() -> Self {
114        // Set bits 0-24 (all current SymKind values)
115        Self(0x01FFFFFF)
116    }
117
118    /// Create a set from a single kind.
119    #[inline]
120    pub const fn from_kind(kind: SymKind) -> Self {
121        Self(1 << (kind as u32))
122    }
123
124    /// Create a set from multiple kinds using const builder pattern.
125    #[inline]
126    pub const fn with(self, kind: SymKind) -> Self {
127        Self(self.0 | (1 << (kind as u32)))
128    }
129
130    /// Check if the set contains a kind (O(1) operation).
131    #[inline]
132    pub const fn contains(&self, kind: SymKind) -> bool {
133        (self.0 & (1 << (kind as u32))) != 0
134    }
135
136    /// Check if the set is empty.
137    #[inline]
138    pub const fn is_empty(&self) -> bool {
139        self.0 == 0
140    }
141}
142
143/// Pre-computed constant for all symbol kinds (used when no filtering needed).
144pub const SYM_KIND_ALL: SymKindSet = SymKindSet::all();
145
146/// Pre-computed constant for type kinds (used in type resolution).
147pub const SYM_KIND_TYPES: SymKindSet = SymKindSet::empty()
148    .with(SymKind::Struct)
149    .with(SymKind::Enum)
150    .with(SymKind::Trait)
151    .with(SymKind::Interface)
152    .with(SymKind::Function)
153    .with(SymKind::Const)
154    .with(SymKind::Static)
155    .with(SymKind::Primitive)
156    .with(SymKind::GenericType)
157    .with(SymKind::CompositeType)
158    .with(SymKind::TypeAlias)
159    .with(SymKind::Namespace)
160    .with(SymKind::TypeParameter);
161
162/// Pre-computed constant for impl target kinds.
163pub const SYM_KIND_IMPL_TARGETS: SymKindSet = SymKindSet::empty()
164    .with(SymKind::Struct)
165    .with(SymKind::Enum);
166
167/// Pre-computed constant for callable kinds.
168pub const SYM_KIND_CALLABLE: SymKindSet = SymKindSet::empty()
169    .with(SymKind::Struct)
170    .with(SymKind::Enum)
171    .with(SymKind::Trait)
172    .with(SymKind::Function)
173    .with(SymKind::Const);
174
175impl SymKind {
176    pub fn is_resolved(&self) -> bool {
177        !matches!(self, SymKind::UnresolvedType)
178    }
179
180    pub fn is_const(&self) -> bool {
181        matches!(self, SymKind::Const | SymKind::Static)
182    }
183
184    /// Checks if the symbol kind represents a user-defined type (struct, enum, trait, type alias).
185    /// These are types that are explicitly defined in user code, not primitives or generics.
186    /// Checks if the symbol kind represents a user-defined type.
187    /// These are types that appear in type annotations and can have impl blocks.
188    pub fn is_defined_type(&self) -> bool {
189        matches!(
190            self,
191            SymKind::Struct
192                | SymKind::Enum
193                | SymKind::Trait
194                | SymKind::TypeAlias
195                | SymKind::Interface
196        )
197    }
198
199    /// Returns kinds that can be looked up as types in type annotations.
200    /// Used for resolving type references in function signatures, fields, etc.
201    #[deprecated(note = "Use SYM_KIND_TYPES constant instead")]
202    pub fn type_kinds() -> Vec<SymKind> {
203        vec![
204            SymKind::Struct,
205            SymKind::Enum,
206            SymKind::Trait,
207            SymKind::Function,
208            SymKind::Const,
209            SymKind::Static,
210            SymKind::Primitive,
211            SymKind::GenericType,
212            SymKind::CompositeType,
213            SymKind::TypeAlias,
214            SymKind::Namespace,
215            SymKind::TypeParameter,
216        ]
217    }
218
219    /// Returns kinds that can be targets of impl blocks (impl X for Target).
220    /// In Rust, you can impl traits for structs and enums.
221    #[deprecated(note = "Use SYM_KIND_IMPL_TARGETS constant instead")]
222    pub fn impl_target_kinds() -> Vec<SymKind> {
223        vec![SymKind::Struct, SymKind::Enum]
224    }
225
226    /// Returns kinds that can be called like functions.
227    #[deprecated(note = "Use SYM_KIND_CALLABLE constant instead")]
228    pub fn callable_kinds() -> Vec<SymKind> {
229        vec![
230            SymKind::Struct,
231            SymKind::Enum,
232            SymKind::Trait,
233            SymKind::Function,
234            SymKind::Const,
235        ]
236    }
237}
238
239/// Represents a named entity in source code.
240///
241/// Symbols track metadata about functions, structs, variables, and other named elements.
242/// Each symbol has:
243/// - An immutable unique ID
244/// - A name (interned for fast comparison)
245/// - Metadata (kind, location, type, dependencies)
246/// - Relationships to other symbols (dependencies, scope hierarchy)
247///
248/// Symbols support shadowing and multi-definition tracking via the `previous` field,
249/// which forms a chain of symbol definitions in nested scopes.
250///
251/// # Thread Safety
252/// Most fields use `RwLock` for thread-safe interior mutability.
253/// The ID and name are immutable once created.
254///
255/// # Example
256/// ```ignore
257/// let symbol = Symbol::new(id, interned_name);
258/// symbol.set_kind(SymKind::Function);
259/// symbol.set_is_global(true);
260/// ```
261pub struct Symbol {
262    /// Monotonic id assigned when the symbol is created.
263    pub id: SymId,
264    /// Interned key for the symbol name, used for fast lookup and comparison.
265    /// Interned names allow O(1) equality checks.
266    pub name: InternedStr,
267    /// Packed unit_index (low 32 bits) and crate_index (high 32 bits).
268    /// - unit_index: which compile unit (file) this symbol is defined in
269    /// - crate_index: which crate/package this symbol belongs to
270    ///
271    /// Both use INDEX_NONE (u32::MAX) to indicate "not set".
272    unit_crate_index: AtomicU64,
273    /// Owning HIR node that introduces the symbol (e.g. function def, struct def).
274    /// Immutable once set; represents the primary definition location.
275    pub owner: RwLock<HirId>,
276    /// Additional defining locations for this symbol.
277    /// For example, a struct can have multiple impl blocks in different files.
278    /// owner + defining together represent all locations where this symbol is defined.
279    pub defining: RwLock<Vec<HirId>>,
280    /// The scope that this symbol belongs to.
281    /// Used to quickly find the scope during binding and type resolution.
282    pub scope: AtomicUsize, // 0 = None, n = Some(ScopeId(n-1))
283    /// The parent scope of this symbol (for scope hierarchy).
284    /// Enables upward traversal of the scope chain.
285    pub parent_scope: RwLock<Option<ScopeId>>,
286    /// The kind of symbol this represents (function, struct, variable, etc.).
287    /// Initially Unknown, updated as the symbol is processed.
288    pub kind: AtomicU8,
289    /// Optional backing type for this symbol (e.g. variable type, alias target).
290    /// Set during type analysis if applicable.
291    pub type_of: AtomicUsize, // 0 = None, n = Some(SymId(n-1))
292    /// Optional block id associated with this symbol (for graph building).
293    /// Links the symbol to its corresponding block in the code graph.
294    pub block_id: AtomicU32, // 0 = None, n = Some(BlockId(n-1))
295    /// Whether the symbol is globally visible/exported.
296    /// Used to distinguish public symbols from private ones.
297    pub is_global: AtomicBool,
298    /// Previous version/definition of this symbol (for shadowing and multi-definition tracking).
299    /// Used to chain multiple definitions of the same symbol in different scopes or contexts.
300    /// Example: inner definition shadows outer definition in nested scope.
301    /// Forms a linked list of definitions traversable via following `previous` pointers.
302    pub previous: RwLock<Option<SymId>>,
303    /// For compound types (tuple, array, struct, enum), tracks the types of nested components.
304    /// For tuple types: element types in order.
305    /// For struct/enum: field types in declaration order.
306    /// For array types: single element type.
307    pub nested_types: RwLock<Vec<SymId>>,
308    /// For field symbols, tracks which symbol owns this field (parent struct, enum, or object).
309    /// Set to the symbol that contains/defines this field.
310    /// Examples: enum variant's FieldOf is the enum; struct field's FieldOf is the struct;
311    /// tuple field (by index) FieldOf is the tuple/value being accessed.
312    pub field_of: AtomicUsize, // 0 = None, n = Some(SymId(n-1))
313    /// For classes/functions, tracks decorator function symbols applied to this symbol.
314    /// Used primarily in TypeScript/JavaScript for @decorator syntax.
315    pub decorators: RwLock<Vec<SymId>>,
316}
317
318impl Clone for Symbol {
319    fn clone(&self) -> Self {
320        Self {
321            id: self.id,
322            owner: RwLock::new(*self.owner.read()),
323            name: self.name,
324            unit_crate_index: AtomicU64::new(self.unit_crate_index.load(Ordering::Relaxed)),
325            defining: RwLock::new(self.defining.read().clone()),
326            scope: AtomicUsize::new(self.scope.load(Ordering::Relaxed)),
327            parent_scope: RwLock::new(*self.parent_scope.read()),
328            kind: AtomicU8::new(self.kind.load(Ordering::Relaxed)),
329            type_of: AtomicUsize::new(self.type_of.load(Ordering::Relaxed)),
330            block_id: AtomicU32::new(self.block_id.load(Ordering::Relaxed)),
331            is_global: AtomicBool::new(self.is_global.load(Ordering::Relaxed)),
332            previous: RwLock::new(*self.previous.read()),
333            nested_types: RwLock::new(self.nested_types.read().clone()),
334            field_of: AtomicUsize::new(self.field_of.load(Ordering::Relaxed)),
335            decorators: RwLock::new(self.decorators.read().clone()),
336        }
337    }
338}
339
340impl fmt::Debug for Symbol {
341    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
342        write!(f, "{}", self.format(None))
343    }
344}
345
346impl Symbol {
347    /// Pack unit_index and crate_index into a single u64.
348    /// Layout: [crate_index: u32 (high)][unit_index: u32 (low)]
349    #[inline]
350    const fn pack_indices(unit_index: u32, crate_index: u32) -> u64 {
351        ((crate_index as u64) << 32) | (unit_index as u64)
352    }
353
354    /// Unpack unit_index from the combined u64.
355    #[inline]
356    const fn unpack_unit_index(packed: u64) -> u32 {
357        packed as u32
358    }
359
360    /// Unpack crate_index from the combined u64.
361    #[inline]
362    const fn unpack_crate_index(packed: u64) -> u32 {
363        (packed >> 32) as u32
364    }
365
366    /// Creates a new symbol with the given HIR node owner and interned name.
367    pub fn new(owner: HirId, name_key: InternedStr) -> Self {
368        let id = NEXT_SYMBOL_ID.fetch_add(1, Ordering::Relaxed);
369        let sym_id = SymId(id);
370
371        Self {
372            id: sym_id,
373            owner: RwLock::new(owner),
374            name: name_key,
375            unit_crate_index: AtomicU64::new(Self::pack_indices(INDEX_NONE, INDEX_NONE)),
376            defining: RwLock::new(Vec::new()),
377            scope: AtomicUsize::new(0),
378            parent_scope: RwLock::new(None),
379            kind: AtomicU8::new(SymKind::Unknown as u8),
380            type_of: AtomicUsize::new(0),
381            block_id: AtomicU32::new(0),
382            is_global: AtomicBool::new(false),
383            previous: RwLock::new(None),
384            nested_types: RwLock::new(Vec::new()),
385            field_of: AtomicUsize::new(0),
386            decorators: RwLock::new(Vec::new()),
387        }
388    }
389
390    /// Gets the owner HIR node of this symbol.
391    #[inline]
392    pub fn owner(&self) -> HirId {
393        *self.owner.read()
394    }
395
396    #[inline]
397    pub fn id(&self) -> SymId {
398        self.id
399    }
400
401    /// Sets the owner HIR node of this symbol.
402    #[inline]
403    pub fn set_owner(&self, owner: HirId) {
404        *self.owner.write() = owner;
405    }
406
407    /// Formats the symbol with basic information
408    pub fn format(&self, interner: Option<&crate::interner::InternPool>) -> String {
409        let kind = format!("{:?}", self.kind());
410        if let Some(interner) = interner {
411            if let Some(name) = interner.resolve_owned(self.name) {
412                format!("[{}:{}] {}", self.id.0, kind, name)
413            } else {
414                format!("[{}:{}]?", self.id.0, kind)
415            }
416        } else {
417            format!("[{}:{}]", self.id.0, kind)
418        }
419    }
420
421    /// Gets the scope ID this symbol belongs to.
422    #[inline]
423    pub fn opt_scope(&self) -> Option<ScopeId> {
424        let v = self.scope.load(Ordering::Relaxed);
425        if v == 0 { None } else { Some(ScopeId(v - 1)) }
426    }
427
428    #[inline]
429    pub fn scope(&self) -> ScopeId {
430        self.opt_scope().unwrap()
431    }
432
433    /// Sets the scope ID this symbol belongs to.
434    #[inline]
435    pub fn set_scope(&self, scope_id: ScopeId) {
436        self.scope.store(scope_id.0 + 1, Ordering::Relaxed);
437    }
438
439    /// Gets the parent scope ID in the scope hierarchy.
440    #[inline]
441    pub fn parent_scope(&self) -> Option<ScopeId> {
442        *self.parent_scope.read()
443    }
444
445    /// Sets the parent scope ID in the scope hierarchy.
446    #[inline]
447    pub fn set_parent_scope(&self, scope_id: ScopeId) {
448        *self.parent_scope.write() = Some(scope_id);
449    }
450
451    /// Gets the symbol kind (function, struct, variable, etc.).
452    #[inline]
453    pub fn kind(&self) -> SymKind {
454        // SAFETY: SymKind has repr(u8) implied by enum values 0-23
455        unsafe { std::mem::transmute(self.kind.load(Ordering::Relaxed)) }
456    }
457
458    /// Sets the symbol kind after analysis.
459    #[inline]
460    pub fn set_kind(&self, kind: SymKind) {
461        self.kind.store(kind as u8, Ordering::Relaxed);
462    }
463
464    /// Gets the type of this symbol (if it has one).
465    /// For variables, this is their declared type.
466    /// For type aliases, this is the target type.
467    #[inline]
468    pub fn type_of(&self) -> Option<SymId> {
469        let v = self.type_of.load(Ordering::Relaxed);
470        if v == 0 { None } else { Some(SymId(v - 1)) }
471    }
472
473    /// Sets the type of this symbol.
474    #[inline]
475    pub fn set_type_of(&self, ty: SymId) {
476        tracing::trace!("setting type of symbol {} to symbol {}", self.id, ty,);
477        self.type_of.store(ty.0 + 1, Ordering::Relaxed);
478    }
479
480    /// Gets the compile unit index this symbol is defined in.
481    #[inline]
482    pub fn unit_index(&self) -> Option<usize> {
483        let packed = self.unit_crate_index.load(Ordering::Relaxed);
484        match Self::unpack_unit_index(packed) {
485            INDEX_NONE => None,
486            v => Some(v as usize),
487        }
488    }
489
490    /// Sets the compile unit index, but only if not already set.
491    /// Prevents overwriting the original definition location.
492    #[inline]
493    pub fn set_unit_index(&self, unit_idx: usize) {
494        debug_assert!(unit_idx <= u32::MAX as usize, "unit_index exceeds u32::MAX");
495        let unit_idx = unit_idx as u32;
496
497        loop {
498            let current = self.unit_crate_index.load(Ordering::Relaxed);
499            let current_unit = Self::unpack_unit_index(current);
500
501            // Only set if not already set
502            if current_unit != INDEX_NONE {
503                return;
504            }
505
506            let crate_idx = Self::unpack_crate_index(current);
507            let new_packed = Self::pack_indices(unit_idx, crate_idx);
508
509            if self
510                .unit_crate_index
511                .compare_exchange(current, new_packed, Ordering::Relaxed, Ordering::Relaxed)
512                .is_ok()
513            {
514                return;
515            }
516        }
517    }
518
519    /// Gets the crate/package index this symbol belongs to.
520    #[inline]
521    pub fn crate_index(&self) -> Option<usize> {
522        let packed = self.unit_crate_index.load(Ordering::Relaxed);
523        match Self::unpack_crate_index(packed) {
524            INDEX_NONE => None,
525            v => Some(v as usize),
526        }
527    }
528
529    /// Sets the crate index, but only if not already set.
530    #[inline]
531    pub fn set_crate_index(&self, crate_idx: usize) {
532        debug_assert!(
533            crate_idx <= u32::MAX as usize,
534            "crate_index exceeds u32::MAX"
535        );
536        let crate_idx = crate_idx as u32;
537
538        loop {
539            let current = self.unit_crate_index.load(Ordering::Relaxed);
540            let current_crate = Self::unpack_crate_index(current);
541
542            // Only set if not already set
543            if current_crate != INDEX_NONE {
544                return;
545            }
546
547            let unit_idx = Self::unpack_unit_index(current);
548            let new_packed = Self::pack_indices(unit_idx, crate_idx);
549
550            if self
551                .unit_crate_index
552                .compare_exchange(current, new_packed, Ordering::Relaxed, Ordering::Relaxed)
553                .is_ok()
554            {
555                return;
556            }
557        }
558    }
559
560    /// Checks if this symbol is globally visible/exported.
561    #[inline]
562    pub fn is_global(&self) -> bool {
563        self.is_global.load(Ordering::Relaxed)
564    }
565
566    /// Sets the global visibility flag.
567    #[inline]
568    pub fn set_is_global(&self, value: bool) {
569        self.is_global.store(value, Ordering::Relaxed);
570    }
571
572    /// Adds a HIR node as an additional definition location.
573    /// Prevents duplicate entries.
574    pub fn add_defining(&self, id: HirId) {
575        let mut defs = self.defining.write();
576        if !defs.contains(&id) {
577            defs.push(id);
578        }
579    }
580
581    /// Gets all HIR nodes that define this symbol.
582    pub fn defining_hir_nodes(&self) -> Vec<HirId> {
583        self.defining.read().clone()
584    }
585
586    /// Gets the block ID associated with this symbol.
587    #[inline]
588    pub fn block_id(&self) -> Option<BlockId> {
589        let v = self.block_id.load(Ordering::Relaxed);
590        if v == 0 { None } else { Some(BlockId(v - 1)) }
591    }
592
593    /// Sets the block ID associated with this symbol.
594    #[inline]
595    pub fn set_block_id(&self, block_id: BlockId) {
596        self.block_id.store(block_id.0 + 1, Ordering::Relaxed);
597    }
598
599    /// Gets the previous definition of this symbol (for shadowing).
600    /// Symbols with the same name in nested scopes form a chain via this field.
601    #[inline]
602    pub fn previous(&self) -> Option<SymId> {
603        *self.previous.read()
604    }
605
606    /// Sets the previous definition of this symbol.
607    /// Used to build shadowing chains when a symbol name is reused in a nested scope.
608    #[inline]
609    pub fn set_previous(&self, sym_id: SymId) {
610        *self.previous.write() = Some(sym_id);
611    }
612
613    /// Gets the nested types for compound types (tuples, arrays, structs, enums).
614    /// Returns None if no nested types have been set, Some(vec) otherwise.
615    #[inline]
616    pub fn nested_types(&self) -> Option<Vec<SymId>> {
617        let types = self.nested_types.read();
618        if types.is_empty() {
619            None
620        } else {
621            Some(types.clone())
622        }
623    }
624
625    /// Adds a type to the nested types list for compound types.
626    /// For tuples/arrays, this is in element order. For structs/enums, in field order.
627    #[inline]
628    pub fn add_nested_type(&self, ty: SymId) {
629        self.nested_types.write().push(ty);
630    }
631
632    /// Replaces all nested types with a new list.
633    #[inline]
634    pub fn set_nested_types(&self, types: Vec<SymId>) {
635        *self.nested_types.write() = types;
636    }
637
638    /// Gets which symbol owns this field (parent struct, enum, or object being accessed).
639    #[inline]
640    pub fn field_of(&self) -> Option<SymId> {
641        let v = self.field_of.load(Ordering::Relaxed);
642        if v == 0 { None } else { Some(SymId(v - 1)) }
643    }
644
645    /// Sets which symbol owns this field.
646    #[inline]
647    pub fn set_field_of(&self, owner: SymId) {
648        self.field_of.store(owner.0 + 1, Ordering::Relaxed);
649    }
650
651    /// Gets the decorators applied to this symbol (for TypeScript/JavaScript @decorator syntax).
652    #[inline]
653    pub fn decorators(&self) -> Option<Vec<SymId>> {
654        let decorators = self.decorators.read();
655        if decorators.is_empty() {
656            None
657        } else {
658            Some(decorators.clone())
659        }
660    }
661
662    /// Adds a decorator to this symbol.
663    #[inline]
664    pub fn add_decorator(&self, decorator: SymId) {
665        self.decorators.write().push(decorator);
666    }
667}