mago_names/lib.rs
1use std::collections::HashSet;
2
3use ahash::HashMap;
4use serde::Serialize;
5
6use mago_span::HasPosition;
7use mago_span::Position;
8
9pub mod kind;
10pub mod resolver;
11pub mod scope;
12
13mod internal;
14
15/// Stores the results of a name resolution pass over a PHP program.
16///
17/// Maps source code positions (specifically, the starting byte offset of identifiers)
18/// to their resolved fully qualified name (`StringIdentifier`) and a flag indicating
19/// whether the resolution involved an explicit `use` alias or construct.
20#[derive(Debug, Clone, Eq, PartialEq, Serialize, Default)]
21pub struct ResolvedNames<'arena> {
22 /// Internal map storing: position (byte offset) -> (Resolved Name ID, Was Imported Flag)
23 names: HashMap<u32, (&'arena str, bool)>,
24}
25
26impl<'arena> ResolvedNames<'arena> {
27 /// Returns the total number of resolved names stored.
28 #[must_use]
29 pub fn len(&self) -> usize {
30 self.names.len()
31 }
32
33 /// Returns `true` if no resolved names are stored.
34 #[must_use]
35 pub fn is_empty(&self) -> bool {
36 self.names.is_empty()
37 }
38
39 /// Checks if a resolved name exists for the given source `Position`.
40 #[must_use]
41 pub fn contains(&self, position: &Position) -> bool {
42 self.names.contains_key(&position.offset)
43 }
44
45 /// Gets the resolved name identifier for the given source position.
46 ///
47 /// # Panics
48 ///
49 /// Panics if no resolved name is found at the specified `position`.
50 /// Use `contains` first if unsure.
51 pub fn get<T: HasPosition>(&self, position: &T) -> &'arena str {
52 self.names.get(&position.offset()).map(|(name, _)| name).expect("resolved name not found at position")
53 }
54
55 /// Attempts to resolve the name at the given source position.
56 ///
57 /// Returns `Some(&str)` if a resolved name exists at the position,
58 /// or `None` if no name is found.
59 pub fn resolve<T: HasPosition>(&self, position: &T) -> Option<&'arena str> {
60 self.names.get(&position.offset()).map(|(name, _)| *name)
61 }
62
63 /// Checks if the name resolved at the given position originated from an explicit `use` alias or construct.
64 ///
65 /// Returns `false` if the name was resolved relative to the namespace, is a definition,
66 /// or if no name is found at the position.
67 pub fn is_imported<T: HasPosition>(&self, position: &T) -> bool {
68 self.names
69 .get(&position.offset()) // Get Option<(StringIdentifier, bool)>
70 .is_some_and(|(_, imported)| *imported) // Default to false if position not found
71 }
72
73 /// Inserts a resolution result into the map (intended for internal use).
74 ///
75 /// Associates the resolved `name` identifier and its `imported` status with the
76 /// given `position` (byte offset).
77 pub(crate) fn insert_at<T: HasPosition>(&mut self, position: &T, name: &'arena str, imported: bool) {
78 self.names.insert(position.offset(), (name, imported));
79 }
80
81 /// Returns a `HashSet` containing references to all stored resolution results.
82 ///
83 /// Each element in the set is a reference to a tuple: `(&usize, &(StringIdentifier, bool))`,
84 /// representing `(&position, &(resolved_name_id, was_imported_flag))`.
85 #[must_use]
86 pub fn all(&self) -> HashSet<(&u32, &(&'arena str, bool))> {
87 self.names.iter().collect()
88 }
89}