Skip to main content

php_lsp/
resolve.rs

1//! Centralized cursor resolution.
2//!
3//! `goto_definition`, `goto_declaration`, and `hover` all needed to answer the
4//! same question — *"which declaration in this AST is named `word`?"* — and each
5//! had its own near-identical statement walker (`scan_statements`,
6//! `find_any_declaration`, …). [`resolve_declaration`] is the single walker; it returns
7//! a borrowed handle to the matched node ([`Declaration`]) and leaves rendering (range
8//! vs. signature vs. abstract-filtering) to the caller.
9//!
10//! The walker performs *name matching*, not full cursor-context classification:
11//! it matches a declaration whose name equals `word`, exactly as the three
12//! original copies did. Distinguishing "method call vs. class name at this
13//! offset" is a separate, behavior-changing concern and intentionally not done
14//! here.
15//!
16//! Callers narrow the match with an `accept` predicate. Returning `false` means
17//! "skip this candidate and keep looking", mirroring the `_ => {}` fall-through
18//! in the original walkers. This is what lets declaration's two-pass logic
19//! (abstract first, then any) reuse the same traversal.
20
21use php_ast::{
22    ClassConstDecl, ClassDecl, ClassMemberKind, EnumCase, EnumDecl, EnumMemberKind, FunctionDecl,
23    Ident, InterfaceDecl, MethodDecl, NamespaceBody, Param, PropertyDecl, Span, Stmt, StmtKind,
24    TraitDecl,
25};
26
27use crate::util::strip_variable_sigil;
28
29/// Which type-like declaration a member belongs to. Lets callers reproduce
30/// per-container behavior (e.g. definition resolves enum constants differently
31/// from class constants) without re-walking the AST.
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33pub enum Container {
34    Class,
35    Interface,
36    Trait,
37    Enum,
38}
39
40/// A declaration node matched by name. Each variant borrows the matched AST node
41/// (arena-allocated, so it outlives the walk) plus the span(s) callers need to
42/// compute a precise name range.
43pub enum Declaration<'a> {
44    Function {
45        decl: &'a FunctionDecl<'a, 'a>,
46        stmt_span: Span,
47    },
48    Class {
49        decl: &'a ClassDecl<'a, 'a>,
50        /// The class name (always present — anonymous classes never match by name).
51        name: Ident<'a>,
52        stmt_span: Span,
53    },
54    Interface {
55        decl: &'a InterfaceDecl<'a, 'a>,
56        stmt_span: Span,
57    },
58    Trait {
59        decl: &'a TraitDecl<'a, 'a>,
60        stmt_span: Span,
61    },
62    Enum {
63        decl: &'a EnumDecl<'a, 'a>,
64        stmt_span: Span,
65    },
66    Method {
67        method: &'a MethodDecl<'a, 'a>,
68        container: Container,
69        member_span: Span,
70    },
71    ClassConst {
72        konst: &'a ClassConstDecl<'a, 'a>,
73        container: Container,
74        member_span: Span,
75    },
76    Property {
77        property: &'a PropertyDecl<'a, 'a>,
78        container: Container,
79        member_span: Span,
80    },
81    /// A constructor-promoted parameter, which acts as a property declaration.
82    PromotedParam { param: &'a Param<'a, 'a> },
83    EnumCase {
84        case: &'a EnumCase<'a, 'a>,
85        enum_name: Ident<'a>,
86        member_span: Span,
87    },
88}
89
90impl<'a> Declaration<'a> {
91    /// The identifier the cursor matched (without any `$` sigil).
92    pub fn name(&self) -> &'a str {
93        match self {
94            Declaration::Function { decl, .. } => decl.name.or_error(),
95            Declaration::Class { name, .. } => name.or_error(),
96            Declaration::Interface { decl, .. } => decl.name.or_error(),
97            Declaration::Trait { decl, .. } => decl.name.or_error(),
98            Declaration::Enum { decl, .. } => decl.name.or_error(),
99            Declaration::Method { method, .. } => method.name.or_error(),
100            Declaration::ClassConst { konst, .. } => konst.name.or_error(),
101            Declaration::Property { property, .. } => property.name.or_error(),
102            Declaration::PromotedParam { param } => param.name.or_error(),
103            Declaration::EnumCase { case, .. } => case.name.or_error(),
104        }
105    }
106
107    pub fn span(&self) -> Span {
108        match self {
109            Declaration::Function { stmt_span, .. } => *stmt_span,
110            Declaration::Class { stmt_span, .. } => *stmt_span,
111            Declaration::Interface { stmt_span, .. } => *stmt_span,
112            Declaration::Trait { stmt_span, .. } => *stmt_span,
113            Declaration::Enum { stmt_span, .. } => *stmt_span,
114            Declaration::Method { member_span, .. } => *member_span,
115            Declaration::ClassConst { member_span, .. } => *member_span,
116            Declaration::Property { member_span, .. } => *member_span,
117            Declaration::PromotedParam { param } => param.span,
118            Declaration::EnumCase { member_span, .. } => *member_span,
119        }
120    }
121}
122
123/// Find the first declaration named `word` that `accept` approves, scanning
124/// `stmts` in source order and recursing into braced namespaces.
125///
126/// A `$` sigil on `word` is stripped before matching property / promoted-param
127/// names (which are stored without it), matching the original walkers.
128pub fn resolve_declaration<'a>(
129    stmts: &'a [Stmt<'a, 'a>],
130    word: &str,
131    accept: &dyn Fn(&Declaration<'a>) -> bool,
132) -> Option<Declaration<'a>> {
133    let bare = strip_variable_sigil(word);
134    for stmt in stmts {
135        match &stmt.kind {
136            StmtKind::Function(f) if f.name == word => {
137                let d = Declaration::Function {
138                    decl: f,
139                    stmt_span: stmt.span,
140                };
141                if accept(&d) {
142                    return Some(d);
143                }
144            }
145            StmtKind::Class(c) => {
146                // Class name takes priority over members (match-arm order in the
147                // originals); fall through to members when the name is rejected.
148                if let Some(name) = c.name
149                    && name.or_error() == word
150                {
151                    let d = Declaration::Class {
152                        decl: c,
153                        name,
154                        stmt_span: stmt.span,
155                    };
156                    if accept(&d) {
157                        return Some(d);
158                    }
159                }
160                if let Some(d) =
161                    resolve_member(c.body.members.iter(), word, bare, Container::Class, accept)
162                {
163                    return Some(d);
164                }
165            }
166            StmtKind::Interface(i) => {
167                if i.name == word {
168                    let d = Declaration::Interface {
169                        decl: i,
170                        stmt_span: stmt.span,
171                    };
172                    if accept(&d) {
173                        return Some(d);
174                    }
175                }
176                if let Some(d) = resolve_member(
177                    i.body.members.iter(),
178                    word,
179                    bare,
180                    Container::Interface,
181                    accept,
182                ) {
183                    return Some(d);
184                }
185            }
186            StmtKind::Trait(t) => {
187                if t.name == word {
188                    let d = Declaration::Trait {
189                        decl: t,
190                        stmt_span: stmt.span,
191                    };
192                    if accept(&d) {
193                        return Some(d);
194                    }
195                }
196                if let Some(d) =
197                    resolve_member(t.body.members.iter(), word, bare, Container::Trait, accept)
198                {
199                    return Some(d);
200                }
201            }
202            StmtKind::Enum(e) => {
203                if e.name == word {
204                    let d = Declaration::Enum {
205                        decl: e,
206                        stmt_span: stmt.span,
207                    };
208                    if accept(&d) {
209                        return Some(d);
210                    }
211                }
212                for member in e.body.members.iter() {
213                    match &member.kind {
214                        EnumMemberKind::Case(c) if c.name == word => {
215                            let d = Declaration::EnumCase {
216                                case: c,
217                                enum_name: e.name,
218                                member_span: member.span,
219                            };
220                            if accept(&d) {
221                                return Some(d);
222                            }
223                        }
224                        EnumMemberKind::Method(m) if m.name == word => {
225                            let d = Declaration::Method {
226                                method: m,
227                                container: Container::Enum,
228                                member_span: member.span,
229                            };
230                            if accept(&d) {
231                                return Some(d);
232                            }
233                        }
234                        EnumMemberKind::ClassConst(cc) if cc.name == word => {
235                            let d = Declaration::ClassConst {
236                                konst: cc,
237                                container: Container::Enum,
238                                member_span: member.span,
239                            };
240                            if accept(&d) {
241                                return Some(d);
242                            }
243                        }
244                        _ => {}
245                    }
246                }
247            }
248            StmtKind::Namespace(ns) => {
249                if let NamespaceBody::Braced(inner) = &ns.body
250                    && let Some(d) = resolve_declaration(&inner.stmts, word, accept)
251                {
252                    return Some(d);
253                }
254            }
255            _ => {}
256        }
257    }
258    None
259}
260
261/// Scan class/interface/trait body members. Promoted-constructor parameters are
262/// only considered for `Container::Class` (where the originals handled them).
263fn resolve_member<'a>(
264    members: impl Iterator<Item = &'a php_ast::ClassMember<'a, 'a>>,
265    word: &str,
266    bare: &str,
267    container: Container,
268    accept: &dyn Fn(&Declaration<'a>) -> bool,
269) -> Option<Declaration<'a>> {
270    for member in members {
271        match &member.kind {
272            ClassMemberKind::Method(m) => {
273                if m.name == word {
274                    let d = Declaration::Method {
275                        method: m,
276                        container,
277                        member_span: member.span,
278                    };
279                    if accept(&d) {
280                        return Some(d);
281                    }
282                }
283                // Constructor-promoted parameters act as property declarations.
284                if container == Container::Class && m.name == "__construct" {
285                    for p in m.params.iter() {
286                        if p.visibility.is_some() && p.name == bare {
287                            let d = Declaration::PromotedParam { param: p };
288                            if accept(&d) {
289                                return Some(d);
290                            }
291                        }
292                    }
293                }
294            }
295            ClassMemberKind::ClassConst(cc) if cc.name == word => {
296                let d = Declaration::ClassConst {
297                    konst: cc,
298                    container,
299                    member_span: member.span,
300                };
301                if accept(&d) {
302                    return Some(d);
303                }
304            }
305            ClassMemberKind::Property(p) if p.name == bare => {
306                let d = Declaration::Property {
307                    property: p,
308                    container,
309                    member_span: member.span,
310                };
311                if accept(&d) {
312                    return Some(d);
313                }
314            }
315            _ => {}
316        }
317    }
318    None
319}