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