1use std::collections::HashMap;
4
5use crate::ast::{NameKind, NamespaceDecl, Stmt, StmtKind, UseKind};
6use crate::owned;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10#[non_exhaustive]
11pub enum NameContext {
12 Class,
14 Function,
16 Const,
18}
19
20#[derive(Debug, Clone, PartialEq, Eq)]
22#[non_exhaustive]
23pub enum ResolvedName {
24 Fqn(String),
26 Fallback {
29 namespaced: String,
31 global: String,
33 },
34 Special(SpecialClass),
36 Error,
38}
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
42pub enum SpecialClass {
43 Self_,
45 Static,
47 Parent,
49}
50
51impl SpecialClass {
52 pub fn as_str(self) -> &'static str {
54 match self {
55 SpecialClass::Self_ => "self",
56 SpecialClass::Static => "static",
57 SpecialClass::Parent => "parent",
58 }
59 }
60}
61
62#[derive(Debug, Clone, Default)]
71pub struct NameResolver {
72 namespace: Option<String>,
73 classes: HashMap<String, String>,
74 functions: HashMap<String, String>,
75 consts: HashMap<String, String>,
76}
77
78impl NameResolver {
79 pub fn new() -> Self {
81 Self::default()
82 }
83
84 pub fn namespace(&self) -> Option<&str> {
86 self.namespace.as_deref()
87 }
88
89 pub fn enter_namespace(&mut self, namespace: Option<&str>) {
91 self.namespace = namespace.filter(|n| !n.is_empty()).map(str::to_owned);
92 self.classes.clear();
93 self.functions.clear();
94 self.consts.clear();
95 }
96
97 pub fn add_import(&mut self, kind: UseKind, fqn: &str, alias: Option<&str>) {
100 let fqn = fqn.trim_start_matches('\\');
101 let local = alias.unwrap_or_else(|| fqn.rsplit('\\').next().unwrap_or(fqn));
102 if local.is_empty() {
103 return;
104 }
105 match kind {
106 UseKind::Normal => self
107 .classes
108 .insert(local.to_ascii_lowercase(), fqn.to_owned()),
109 UseKind::Function => self
110 .functions
111 .insert(local.to_ascii_lowercase(), fqn.to_owned()),
112 UseKind::Const => self.consts.insert(local.to_owned(), fqn.to_owned()),
113 };
114 }
115
116 pub fn observe_stmt(&mut self, stmt: &Stmt<'_, '_>) {
118 match &stmt.kind {
119 StmtKind::Namespace(ns) => self.observe_namespace(ns),
120 StmtKind::Use(decl) => {
121 for item in decl.uses.iter() {
122 self.add_import(
123 item.kind.unwrap_or(decl.kind),
124 &item.name.join_parts(),
125 item.alias,
126 );
127 }
128 }
129 _ => {}
130 }
131 }
132
133 fn observe_namespace(&mut self, ns: &NamespaceDecl<'_, '_>) {
134 let name = ns.name.as_ref().map(|n| n.join_parts());
135 self.enter_namespace(name.as_deref());
136 }
137
138 pub fn observe_owned_stmt(&mut self, stmt: &owned::Stmt) {
140 match &stmt.kind {
141 owned::StmtKind::Namespace(ns) => {
142 let name = ns.name.as_ref().map(|n| n.parts.join("\\"));
143 self.enter_namespace(name.as_deref());
144 }
145 owned::StmtKind::Use(decl) => {
146 for item in decl.uses.iter() {
147 self.add_import(
148 item.kind.unwrap_or(decl.kind),
149 &item.name.parts.join("\\"),
150 item.alias.as_deref(),
151 );
152 }
153 }
154 _ => {}
155 }
156 }
157
158 pub fn resolve_name(&self, name: &crate::ast::Name<'_, '_>, ctx: NameContext) -> ResolvedName {
160 self.resolve(ctx, name.kind(), name.parts_slice())
161 }
162
163 pub fn resolve_owned_name(&self, name: &owned::Name, ctx: NameContext) -> ResolvedName {
165 self.resolve(ctx, name.kind, &name.parts)
166 }
167
168 pub fn resolve<S: AsRef<str>>(
170 &self,
171 ctx: NameContext,
172 kind: NameKind,
173 parts: &[S],
174 ) -> ResolvedName {
175 let join = |parts: &[S]| {
176 parts
177 .iter()
178 .map(AsRef::as_ref)
179 .collect::<Vec<_>>()
180 .join("\\")
181 };
182 if parts.is_empty() || kind == NameKind::Error {
183 return ResolvedName::Error;
184 }
185 match kind {
186 NameKind::FullyQualified => ResolvedName::Fqn(join(parts)),
187 NameKind::Relative => ResolvedName::Fqn(self.prefix_namespace(&join(parts))),
188 NameKind::Qualified => {
189 let first = parts[0].as_ref().to_ascii_lowercase();
190 match self.classes.get(&first) {
191 Some(target) => ResolvedName::Fqn(format!("{target}\\{}", join(&parts[1..]))),
192 None => ResolvedName::Fqn(self.prefix_namespace(&join(parts))),
193 }
194 }
195 _ => self.resolve_unqualified(ctx, parts[0].as_ref()),
196 }
197 }
198
199 fn resolve_unqualified(&self, ctx: NameContext, name: &str) -> ResolvedName {
200 match ctx {
201 NameContext::Class => {
202 let lower = name.to_ascii_lowercase();
203 match lower.as_str() {
204 "self" => return ResolvedName::Special(SpecialClass::Self_),
205 "static" => return ResolvedName::Special(SpecialClass::Static),
206 "parent" => return ResolvedName::Special(SpecialClass::Parent),
207 _ => {}
208 }
209 match self.classes.get(&lower) {
210 Some(target) => ResolvedName::Fqn(target.clone()),
211 None => ResolvedName::Fqn(self.prefix_namespace(name)),
212 }
213 }
214 NameContext::Function | NameContext::Const => {
215 let imported = if ctx == NameContext::Function {
216 self.functions.get(&name.to_ascii_lowercase())
217 } else {
218 self.consts.get(name)
219 };
220 match (imported, &self.namespace) {
221 (Some(target), _) => ResolvedName::Fqn(target.clone()),
222 (None, Some(ns)) => ResolvedName::Fallback {
223 namespaced: format!("{ns}\\{name}"),
224 global: name.to_owned(),
225 },
226 (None, None) => ResolvedName::Fqn(name.to_owned()),
227 }
228 }
229 }
230 }
231
232 fn prefix_namespace(&self, name: &str) -> String {
233 match &self.namespace {
234 Some(ns) => format!("{ns}\\{name}"),
235 None => name.to_owned(),
236 }
237 }
238}
239
240#[cfg(test)]
241mod tests {
242 use super::*;
243
244 fn fqn(s: &str) -> ResolvedName {
245 ResolvedName::Fqn(s.into())
246 }
247
248 fn resolver() -> NameResolver {
249 let mut r = NameResolver::new();
250 r.enter_namespace(Some("App"));
251 r.add_import(UseKind::Normal, "Lib\\Foo", None);
252 r.add_import(UseKind::Normal, "\\Lib\\Bar", Some("Baz"));
253 r.add_import(UseKind::Function, "Lib\\helper", None);
254 r.add_import(UseKind::Const, "Lib\\LIMIT", None);
255 r
256 }
257
258 #[test]
259 fn class_names() {
260 let r = resolver();
261 let c = NameContext::Class;
262 assert_eq!(
263 r.resolve(c, NameKind::Unqualified, &["foo"]),
264 fqn("Lib\\Foo")
265 );
266 assert_eq!(
267 r.resolve(c, NameKind::Unqualified, &["Baz"]),
268 fqn("Lib\\Bar")
269 );
270 assert_eq!(
271 r.resolve(c, NameKind::Unqualified, &["Other"]),
272 fqn("App\\Other")
273 );
274 assert_eq!(
275 r.resolve(c, NameKind::Qualified, &["Foo", "X"]),
276 fqn("Lib\\Foo\\X")
277 );
278 assert_eq!(
279 r.resolve(c, NameKind::Qualified, &["Sub", "X"]),
280 fqn("App\\Sub\\X")
281 );
282 assert_eq!(r.resolve(c, NameKind::FullyQualified, &["Foo"]), fqn("Foo"));
283 assert_eq!(r.resolve(c, NameKind::Relative, &["Foo"]), fqn("App\\Foo"));
284 assert_eq!(
285 r.resolve(c, NameKind::Unqualified, &["Self"]),
286 ResolvedName::Special(SpecialClass::Self_)
287 );
288 }
289
290 #[test]
291 fn function_and_const_names() {
292 let r = resolver();
293 assert_eq!(
294 r.resolve(NameContext::Function, NameKind::Unqualified, &["HELPER"]),
295 fqn("Lib\\helper")
296 );
297 assert_eq!(
298 r.resolve(NameContext::Function, NameKind::Unqualified, &["strlen"]),
299 ResolvedName::Fallback {
300 namespaced: "App\\strlen".into(),
301 global: "strlen".into()
302 }
303 );
304 assert_eq!(
305 r.resolve(NameContext::Const, NameKind::Unqualified, &["LIMIT"]),
306 fqn("Lib\\LIMIT")
307 );
308 assert!(matches!(
310 r.resolve(NameContext::Const, NameKind::Unqualified, &["limit"]),
311 ResolvedName::Fallback { .. }
312 ));
313 }
314
315 #[test]
316 fn global_namespace_and_reset() {
317 let mut r = resolver();
318 r.enter_namespace(None);
319 assert_eq!(
320 r.resolve(NameContext::Function, NameKind::Unqualified, &["strlen"]),
321 fqn("strlen")
322 );
323 assert_eq!(
324 r.resolve(NameContext::Class, NameKind::Unqualified, &["Foo"]),
325 fqn("Foo")
326 );
327 }
328
329 #[test]
330 fn error_name() {
331 let r = NameResolver::new();
332 assert_eq!(
333 r.resolve::<&str>(NameContext::Class, NameKind::Error, &[]),
334 ResolvedName::Error
335 );
336 }
337}