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.and_then(|a| a.as_str()),
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_ref().and_then(|a| a.as_str()),
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::Const
215 if ["true", "false", "null"]
216 .iter()
217 .any(|c| name.eq_ignore_ascii_case(c)) =>
218 {
219 ResolvedName::Fqn(name.to_owned())
221 }
222 NameContext::Function | NameContext::Const => {
223 let imported = if ctx == NameContext::Function {
224 self.functions.get(&name.to_ascii_lowercase())
225 } else {
226 self.consts.get(name)
227 };
228 match (imported, &self.namespace) {
229 (Some(target), _) => ResolvedName::Fqn(target.clone()),
230 (None, Some(ns)) => ResolvedName::Fallback {
231 namespaced: format!("{ns}\\{name}"),
232 global: name.to_owned(),
233 },
234 (None, None) => ResolvedName::Fqn(name.to_owned()),
235 }
236 }
237 }
238 }
239
240 fn prefix_namespace(&self, name: &str) -> String {
241 match &self.namespace {
242 Some(ns) => format!("{ns}\\{name}"),
243 None => name.to_owned(),
244 }
245 }
246}
247
248#[cfg(test)]
249mod tests {
250 use super::*;
251
252 fn fqn(s: &str) -> ResolvedName {
253 ResolvedName::Fqn(s.into())
254 }
255
256 fn resolver() -> NameResolver {
257 let mut r = NameResolver::new();
258 r.enter_namespace(Some("App"));
259 r.add_import(UseKind::Normal, "Lib\\Foo", None);
260 r.add_import(UseKind::Normal, "\\Lib\\Bar", Some("Baz"));
261 r.add_import(UseKind::Function, "Lib\\helper", None);
262 r.add_import(UseKind::Const, "Lib\\LIMIT", None);
263 r
264 }
265
266 #[test]
267 fn class_names() {
268 let r = resolver();
269 let c = NameContext::Class;
270 assert_eq!(
271 r.resolve(c, NameKind::Unqualified, &["foo"]),
272 fqn("Lib\\Foo")
273 );
274 assert_eq!(
275 r.resolve(c, NameKind::Unqualified, &["Baz"]),
276 fqn("Lib\\Bar")
277 );
278 assert_eq!(
279 r.resolve(c, NameKind::Unqualified, &["Other"]),
280 fqn("App\\Other")
281 );
282 assert_eq!(
283 r.resolve(c, NameKind::Qualified, &["Foo", "X"]),
284 fqn("Lib\\Foo\\X")
285 );
286 assert_eq!(
287 r.resolve(c, NameKind::Qualified, &["Sub", "X"]),
288 fqn("App\\Sub\\X")
289 );
290 assert_eq!(r.resolve(c, NameKind::FullyQualified, &["Foo"]), fqn("Foo"));
291 assert_eq!(r.resolve(c, NameKind::Relative, &["Foo"]), fqn("App\\Foo"));
292 assert_eq!(
293 r.resolve(c, NameKind::Unqualified, &["Self"]),
294 ResolvedName::Special(SpecialClass::Self_)
295 );
296 }
297
298 #[test]
299 fn function_and_const_names() {
300 let r = resolver();
301 assert_eq!(
302 r.resolve(NameContext::Function, NameKind::Unqualified, &["HELPER"]),
303 fqn("Lib\\helper")
304 );
305 assert_eq!(
306 r.resolve(NameContext::Function, NameKind::Unqualified, &["strlen"]),
307 ResolvedName::Fallback {
308 namespaced: "App\\strlen".into(),
309 global: "strlen".into()
310 }
311 );
312 assert_eq!(
313 r.resolve(NameContext::Const, NameKind::Unqualified, &["LIMIT"]),
314 fqn("Lib\\LIMIT")
315 );
316 assert_eq!(
317 r.resolve(NameContext::Const, NameKind::Unqualified, &["NULL"]),
318 fqn("NULL")
319 );
320 assert_eq!(
321 r.resolve(NameContext::Const, NameKind::Unqualified, &["true"]),
322 fqn("true")
323 );
324 assert_eq!(
325 r.resolve(NameContext::Const, NameKind::Qualified, &["Sub", "null"]),
326 fqn("App\\Sub\\null")
327 );
328 assert!(matches!(
330 r.resolve(NameContext::Const, NameKind::Unqualified, &["limit"]),
331 ResolvedName::Fallback { .. }
332 ));
333 }
334
335 #[test]
336 fn global_namespace_and_reset() {
337 let mut r = resolver();
338 r.enter_namespace(None);
339 assert_eq!(
340 r.resolve(NameContext::Function, NameKind::Unqualified, &["strlen"]),
341 fqn("strlen")
342 );
343 assert_eq!(
344 r.resolve(NameContext::Class, NameKind::Unqualified, &["Foo"]),
345 fqn("Foo")
346 );
347 }
348
349 #[test]
350 fn error_name() {
351 let r = NameResolver::new();
352 assert_eq!(
353 r.resolve::<&str>(NameContext::Class, NameKind::Error, &[]),
354 ResolvedName::Error
355 );
356 }
357}