mago_codex/symbol.rs
1use mago_word::WordSet;
2use mago_word::word;
3use serde::Deserialize;
4use serde::Serialize;
5
6use mago_word::Word;
7use mago_word::WordMap;
8
9/// A pair of `Word`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 `Word` is the symbol's fully qualified class name (FQCN)
13/// and the second `Word` 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 = (Word, Word);
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, Eq)]
72pub struct Symbols {
73 all: WordMap<SymbolKind>,
74 namespaces: WordSet,
75}
76
77impl Symbols {
78 /// Creates a new, empty `Symbols` map.
79 #[inline]
80 #[must_use]
81 pub fn new() -> Symbols {
82 Symbols { all: WordMap::default(), namespaces: WordSet::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: Word) {
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: Word) {
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: Word) {
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: Word) {
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 `Word` (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: Word) -> 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 `Word` (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: Word) -> 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 `Word` of the namespace to check for.
148 ///
149 /// # Returns
150 ///
151 /// `true` if the namespace is present, `false` otherwise.
152 #[must_use]
153 pub fn contains_namespace(&self, namespace: Word) -> bool {
154 self.namespaces.contains(&namespace)
155 }
156
157 /// Checks if a symbol with the given name is a `Class`.
158 ///
159 /// # Arguments
160 ///
161 /// * `name`: The `Word` (likely FQCN) of the symbol to check.
162 ///
163 /// # Returns
164 ///
165 /// `true` if the symbol is a `Class`, `false` otherwise.
166 #[inline]
167 #[must_use]
168 pub fn contains_class(&self, name: Word) -> bool {
169 matches!(self.get_kind(name), Some(SymbolKind::Class))
170 }
171
172 /// Checks if a symbol with the given name is an `Interface`.
173 ///
174 /// # Arguments
175 ///
176 /// * `name`: The `Word` (likely FQCN) of the symbol to check.
177 ///
178 /// # Returns
179 ///
180 /// `true` if the symbol is an `Interface`, `false` otherwise.
181 #[inline]
182 #[must_use]
183 pub fn contains_interface(&self, name: Word) -> bool {
184 matches!(self.get_kind(name), Some(SymbolKind::Interface))
185 }
186
187 /// Checks if a symbol with the given name is a `Trait`.
188 ///
189 /// # Arguments
190 ///
191 /// * `name`: The `Word` (likely FQCN) of the symbol to check.
192 ///
193 /// # Returns
194 ///
195 /// `true` if the symbol is a `Trait`, `false` otherwise.
196 #[inline]
197 #[must_use]
198 pub fn contains_trait(&self, name: Word) -> bool {
199 matches!(self.get_kind(name), Some(SymbolKind::Trait))
200 }
201
202 /// Checks if a symbol with the given name is an `Enum`.
203 ///
204 /// # Arguments
205 ///
206 /// * `name`: The `Word` (likely FQCN) of the symbol to check.
207 ///
208 /// # Returns
209 ///
210 /// `true` if the symbol is an `Enum`, `false` otherwise.
211 #[inline]
212 #[must_use]
213 pub fn contains_enum(&self, name: Word) -> bool {
214 matches!(self.get_kind(name), Some(SymbolKind::Enum))
215 }
216
217 /// Returns a reference to the underlying map of all symbols.
218 #[inline]
219 #[must_use]
220 pub fn get_all(&self) -> &WordMap<SymbolKind> {
221 &self.all
222 }
223
224 /// Extends the current `Symbols` map with another one.
225 #[inline]
226 pub fn extend(&mut self, other: Symbols) {
227 self.namespaces.extend(other.namespaces);
228 for (entry, kind) in other.all {
229 self.all.entry(entry).or_insert(kind);
230 }
231 }
232
233 /// Extends the current `Symbols` map from a reference without consuming the source.
234 #[inline]
235 pub fn extend_ref(&mut self, other: &Symbols) {
236 self.namespaces.extend(other.namespaces.iter().copied());
237
238 for (entry, kind) in &other.all {
239 self.all.entry(*entry).or_insert(*kind);
240 }
241 }
242
243 /// Removes a symbol by its FQCN.
244 ///
245 /// Note: does not remove namespaces (they may be shared by other symbols).
246 #[inline]
247 pub fn remove(&mut self, name: Word) {
248 self.all.remove(&name);
249 }
250}
251
252/// Provides a default, empty `Symbols` map.
253impl Default for Symbols {
254 #[inline]
255 fn default() -> Self {
256 Self::new()
257 }
258}
259/// Returns an iterator that yields all parent namespaces of a given symbol.
260///
261/// For example, if the symbol is `Foo\Bar\Baz\Qux`, the iterator yields:
262/// 1. `Foo`
263/// 2. `Foo\Bar`
264/// 3. `Foo\Bar\Baz`
265pub(super) fn get_symbol_namespaces(symbol_name: Word) -> impl Iterator<Item = Word> {
266 let bytes: Vec<u8> = symbol_name.as_bytes().to_vec();
267
268 (0..bytes.len()).filter_map(move |i| if bytes[i] == b'\\' { Some(word(&bytes[..i])) } else { None })
269}