Skip to main content

mago_codex/
symbol.rs

1use mago_atom::AtomSet;
2use mago_atom::atom;
3use serde::Deserialize;
4use serde::Serialize;
5
6use mago_atom::Atom;
7use mago_atom::AtomMap;
8
9/// A pair of `Atom`s representing a symbol and its member.
10///
11/// This is used to uniquely identify a symbol and its member within the codebase,
12/// where the first `Atom` is the symbol's fully qualified class name (FQCN)
13/// and the second `Atom` is the member's name (e.g., method, property, constant),
14/// or an empty string if the symbol itself is being referenced (e.g., a class or function
15/// without a specific member).
16pub type SymbolIdentifier = (Atom, Atom);
17
18/// Represents the different kinds of top-level class-like structures in PHP.
19#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord)]
20pub enum SymbolKind {
21    Class,
22    Enum,
23    Trait,
24    Interface,
25}
26
27impl SymbolKind {
28    /// Checks if this symbol kind is `Class`.
29    #[inline]
30    #[must_use]
31    pub const fn is_class(&self) -> bool {
32        matches!(self, SymbolKind::Class)
33    }
34
35    /// Checks if this symbol kind is `Enum`.
36    #[inline]
37    #[must_use]
38    pub const fn is_enum(&self) -> bool {
39        matches!(self, SymbolKind::Enum)
40    }
41
42    /// Checks if this symbol kind is `Trait`.
43    #[inline]
44    #[must_use]
45    pub const fn is_trait(&self) -> bool {
46        matches!(self, SymbolKind::Trait)
47    }
48
49    /// Checks if this symbol kind is `Interface`.
50    #[inline]
51    #[must_use]
52    pub const fn is_interface(&self) -> bool {
53        matches!(self, SymbolKind::Interface)
54    }
55
56    /// Returns the string representation of the symbol kind.
57    #[inline]
58    #[must_use]
59    pub const fn as_str(&self) -> &'static str {
60        match self {
61            SymbolKind::Class => "class",
62            SymbolKind::Enum => "enum",
63            SymbolKind::Trait => "trait",
64            SymbolKind::Interface => "interface",
65        }
66    }
67}
68
69/// Stores a map of all known class-like symbol names (FQCNs) to their corresponding `SymbolKind`.
70/// Provides basic methods for adding symbols and querying.
71#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
72pub struct Symbols {
73    all: AtomMap<SymbolKind>,
74    namespaces: AtomSet,
75}
76
77impl Symbols {
78    /// Creates a new, empty `Symbols` map.
79    #[inline]
80    #[must_use]
81    pub fn new() -> Symbols {
82        Symbols { all: AtomMap::default(), namespaces: AtomSet::default() }
83    }
84
85    /// Adds or updates a symbol name identified as a `Class`.
86    #[inline]
87    pub fn add_class_name(&mut self, name: Atom) {
88        self.namespaces.extend(get_symbol_namespaces(name));
89        self.all.insert(name, SymbolKind::Class);
90    }
91
92    /// Adds or updates a symbol name identified as an `Interface`.
93    #[inline]
94    pub fn add_interface_name(&mut self, name: Atom) {
95        self.namespaces.extend(get_symbol_namespaces(name));
96        self.all.insert(name, SymbolKind::Interface);
97    }
98
99    /// Adds or updates a symbol name identified as a `Trait`.
100    #[inline]
101    pub fn add_trait_name(&mut self, name: Atom) {
102        self.namespaces.extend(get_symbol_namespaces(name));
103        self.all.insert(name, SymbolKind::Trait);
104    }
105
106    /// Adds or updates a symbol name identified as an `Enum`.
107    #[inline]
108    pub fn add_enum_name(&mut self, name: Atom) {
109        self.namespaces.extend(get_symbol_namespaces(name));
110        self.all.insert(name, SymbolKind::Enum);
111    }
112
113    /// Retrieves the `SymbolKind` for a given symbol name, if known.
114    ///
115    /// # Arguments
116    ///
117    /// * `name`: The `Atom` (likely FQCN) of the symbol to look up.
118    ///
119    /// # Returns
120    ///
121    /// `Some(SymbolKind)` if the symbol exists in the map, `None` otherwise.
122    #[inline]
123    #[must_use]
124    pub fn get_kind(&self, name: &Atom) -> Option<SymbolKind> {
125        self.all.get(name).copied() // Use copied() since SymbolKind is Copy
126    }
127
128    /// Checks if a symbol with the given name is known.
129    ///
130    /// # Arguments
131    ///
132    /// * `name`: The `Atom` (likely FQCN) of the symbol to check.
133    ///
134    /// # Returns
135    ///
136    /// `true` if the symbol exists in the map, `false` otherwise.
137    #[inline]
138    #[must_use]
139    pub fn contains(&self, name: &Atom) -> bool {
140        self.all.contains_key(name)
141    }
142
143    /// Check if any symbol within the table is part of the given namespace.
144    ///
145    /// # Arguments
146    ///
147    /// * `namespace`: The `Atom` of the namespace to check for.
148    ///
149    /// # Returns
150    ///
151    /// `true` if the namespace is present, `false` otherwise.
152    pub fn contains_namespace(&self, namespace: &Atom) -> bool {
153        self.namespaces.contains(namespace)
154    }
155
156    /// Checks if a symbol with the given name is a `Class`.
157    ///
158    /// # Arguments
159    ///
160    /// * `name`: The `Atom` (likely FQCN) of the symbol to check.
161    ///
162    /// # Returns
163    ///
164    /// `true` if the symbol is a `Class`, `false` otherwise.
165    #[inline]
166    #[must_use]
167    pub fn contains_class(&self, name: &Atom) -> bool {
168        matches!(self.get_kind(name), Some(SymbolKind::Class))
169    }
170
171    /// Checks if a symbol with the given name is an `Interface`.
172    ///
173    /// # Arguments
174    ///
175    /// * `name`: The `Atom` (likely FQCN) of the symbol to check.
176    ///
177    /// # Returns
178    ///
179    /// `true` if the symbol is an `Interface`, `false` otherwise.
180    #[inline]
181    #[must_use]
182    pub fn contains_interface(&self, name: &Atom) -> bool {
183        matches!(self.get_kind(name), Some(SymbolKind::Interface))
184    }
185
186    /// Checks if a symbol with the given name is a `Trait`.
187    ///
188    /// # Arguments
189    ///
190    /// * `name`: The `Atom` (likely FQCN) of the symbol to check.
191    ///
192    /// # Returns
193    ///
194    /// `true` if the symbol is a `Trait`, `false` otherwise.
195    #[inline]
196    #[must_use]
197    pub fn contains_trait(&self, name: &Atom) -> bool {
198        matches!(self.get_kind(name), Some(SymbolKind::Trait))
199    }
200
201    /// Checks if a symbol with the given name is an `Enum`.
202    ///
203    /// # Arguments
204    ///
205    /// * `name`: The `Atom` (likely FQCN) of the symbol to check.
206    ///
207    /// # Returns
208    ///
209    /// `true` if the symbol is an `Enum`, `false` otherwise.
210    #[inline]
211    #[must_use]
212    pub fn contains_enum(&self, name: &Atom) -> bool {
213        matches!(self.get_kind(name), Some(SymbolKind::Enum))
214    }
215
216    /// Returns a reference to the underlying map of all symbols.
217    #[inline]
218    #[must_use]
219    pub fn get_all(&self) -> &AtomMap<SymbolKind> {
220        &self.all
221    }
222
223    /// Extends the current `Symbols` map with another one.
224    #[inline]
225    pub fn extend(&mut self, other: Symbols) {
226        self.namespaces.extend(other.namespaces);
227        for (entry, kind) in other.all {
228            self.all.entry(entry).or_insert(kind);
229        }
230    }
231}
232
233/// Provides a default, empty `Symbols` map.
234impl Default for Symbols {
235    #[inline]
236    fn default() -> Self {
237        Self::new()
238    }
239}
240/// Returns an iterator that yields all parent namespaces of a given symbol.
241///
242/// For example, if the symbol is `Foo\Bar\Baz\Qux`, the iterator yields:
243/// 1. `Foo`
244/// 2. `Foo\Bar`
245/// 3. `Foo\Bar\Baz`
246pub(super) fn get_symbol_namespaces(symbol_name: Atom) -> impl Iterator<Item = Atom> {
247    let s = symbol_name.as_str();
248
249    s.as_bytes().iter().enumerate().filter_map(move |(i, &byte)| if byte == b'\\' { Some(atom(&s[..i])) } else { None })
250}