mir_analyzer/symbol.rs
1//! Per-expression resolved symbol data, retained from body analysis.
2//!
3//! Body analysis resolves a type for every expression; this module keeps
4//! that state so downstream tools can build position indexes for hover,
5//! go-to-definition, and completions.
6
7use std::sync::Arc;
8
9use mir_types::Type;
10use php_ast::Span;
11
12/// Type of a class-name token: `class-string<Fqcn>`.
13pub(crate) fn class_token_type(fqcn: &str) -> Type {
14 Type::single(mir_types::Atomic::TClassString(Some(fqcn.into())))
15}
16
17/// Compact navigation payload for cursor-based name resolution.
18#[derive(Debug, Clone)]
19pub struct NavigationFact {
20 pub span: Span,
21 pub expr_span: Option<Span>,
22 pub name: crate::Name,
23}
24
25/// Compact typed navigation payload for cursor-based symbol resolution.
26#[derive(Debug, Clone)]
27pub struct ResolvedNavigationFact {
28 pub span: Span,
29 pub expr_span: Option<Span>,
30 pub kind: ReferenceKind,
31 pub resolved_type: Type,
32}
33
34impl ResolvedNavigationFact {
35 pub fn into_resolved_symbol(self, file: Arc<str>) -> ResolvedSymbol {
36 ResolvedSymbol {
37 file,
38 span: self.span,
39 expr_span: self.expr_span,
40 kind: self.kind,
41 resolved_type: self.resolved_type,
42 }
43 }
44}
45
46/// A single resolved symbol observed during body analysis.
47#[derive(Debug, Clone)]
48pub struct ResolvedSymbol {
49 /// Absolute path of the file this symbol was found in.
50 pub file: Arc<str>,
51 /// Byte-offset span covering only the identifier token (method name,
52 /// function name, variable sigil+name, etc.). Used for precise
53 /// go-to-definition and reference highlighting.
54 pub span: Span,
55 /// Byte-offset span of the full call expression, e.g. the entire
56 /// `$obj->method(args)` node. Set only for call-like symbols (method
57 /// calls, static calls, function calls). `symbol_at` uses this as a
58 /// fallback so that a cursor sitting in the whitespace between two
59 /// chained method calls still resolves to the innermost enclosing call.
60 pub expr_span: Option<Span>,
61 /// What kind of symbol this is.
62 pub kind: ReferenceKind,
63 /// The resolved type at this location.
64 pub resolved_type: Type,
65}
66
67impl ResolvedSymbol {
68 /// Return the reference-index lookup key, or `None` for kinds that are not
69 /// tracked there (e.g. variables). Delegates to [`ReferenceKind::to_name`].
70 pub fn codebase_key(&self) -> Option<String> {
71 self.kind.to_name().map(|name| name.codebase_key())
72 }
73
74 /// Convert to a typed [`crate::Name`] for use with
75 /// [`crate::AnalysisSession::definition_of`] or
76 /// [`crate::AnalysisSession::indexed_references_to`]. Delegates to [`ReferenceKind::to_name`].
77 pub fn to_symbol(&self) -> Option<crate::Name> {
78 self.kind.to_name()
79 }
80}
81
82/// One declaration emitted by [`crate::AnalysisSession::document_symbols`].
83/// Tool-agnostic shape for outline / breadcrumb features; consumers map this
84/// onto whatever protocol-specific symbol type they need.
85///
86/// Forms a tree: classes/interfaces/traits/enums have `children` populated
87/// with their methods, properties, and constants. Top-level functions and
88/// constants have empty `children`.
89#[derive(Debug, Clone)]
90pub struct DocumentSymbol {
91 /// FQCN for classes/interfaces/traits/enums; FQN for functions /
92 /// constants; short name for members nested inside a class.
93 pub name: Arc<str>,
94 /// Coarse kind suitable for icon / severity selection in outlines.
95 pub kind: DeclarationKind,
96 /// Source location of the declaration (file + 1-based lines, 0-based
97 /// columns). `None` only for synthetic stub-only definitions that don't
98 /// have a recorded source span.
99 pub location: Option<mir_types::Location>,
100 /// For container symbols (Class, Interface, Trait, Enum), the nested
101 /// methods, properties, and constants declared on this type. Empty for
102 /// leaf kinds (Function, Constant, etc.).
103 pub children: Vec<DocumentSymbol>,
104}
105
106/// Coarse declaration kind used by [`DocumentSymbol`]. Maps onto outline-style
107/// symbol kinds in any consumer protocol.
108#[derive(Debug, Clone, Copy, PartialEq, Eq)]
109pub enum DeclarationKind {
110 Class,
111 Interface,
112 Trait,
113 Enum,
114 Function,
115 Method,
116 Property,
117 Constant,
118 EnumCase,
119}
120
121/// The kind of symbol reference that was resolved.
122#[derive(Debug, Clone)]
123pub enum ReferenceKind {
124 /// A variable reference (`$foo`).
125 Variable(Arc<str>),
126 /// An instance method call (`$obj->method()`).
127 MethodCall { class: Arc<str>, method: Arc<str> },
128 /// A static method call (`Foo::method()`).
129 StaticCall { class: Arc<str>, method: Arc<str> },
130 /// A property access (`$obj->prop`).
131 PropertyAccess { class: Arc<str>, property: Arc<str> },
132 /// A free function call (`foo()`).
133 FunctionCall(Arc<str>),
134 /// A class reference (`new Foo`, `instanceof Foo`, type hints).
135 ClassReference(Arc<str>),
136 /// A class constant access (`Config::VERSION`, `self::CONST`, `parent::CONST`).
137 ConstantAccess { class: Arc<str>, constant: Arc<str> },
138 /// A global constant reference (`FOO`, `\FOO`).
139 GlobalConstant(Arc<str>),
140 /// A symbol occurrence inside a `use` import statement (`use Foo\Bar;`,
141 /// `use function foo\bar;`, `use const FOO\BAR;`) — the import's own name
142 /// token, not a usage site. Kept distinct from the wrapped kind so a
143 /// consumer walking reference-index postings (e.g. a future index-based
144 /// rename) can tell an import line apart from a genuine usage: renaming
145 /// must update the import too, but a plain find-references / dead-code
146 /// "is this ever used" check must not count the bare import as one (there
147 /// is deliberately no `UnusedImport` check).
148 UseImport(Box<ReferenceKind>),
149 /// The type of a member-access receiver (`$obj` in `$obj->prop`, `Foo` in
150 /// `Foo::method()`), recorded at the gap between the receiver expression
151 /// and the member token — the `->`/`?->`/`::` operator and any
152 /// intervening whitespace — rather than at the receiver's own span. This
153 /// is what lets a cursor sitting right after the operator (the position a
154 /// member-completion request fires from) resolve a type, since that
155 /// offset falls outside both the receiver expression's span and the
156 /// member's span. Not a codebase-level symbol — `to_name` returns `None`.
157 Receiver,
158}
159
160impl ReferenceKind {
161 /// Map to a typed [`crate::Name`], or `None` for kinds that don't correspond
162 /// to a codebase-level symbol (currently only `Variable`).
163 pub fn to_name(&self) -> Option<crate::Name> {
164 match self {
165 ReferenceKind::MethodCall { class, method }
166 | ReferenceKind::StaticCall { class, method } => {
167 Some(crate::Name::method(class.clone(), method.as_ref()))
168 }
169 ReferenceKind::PropertyAccess { class, property } => {
170 Some(crate::Name::property(class.clone(), property.clone()))
171 }
172 ReferenceKind::FunctionCall(fqn) => Some(crate::Name::function(fqn.clone())),
173 ReferenceKind::ClassReference(fqcn) => Some(crate::Name::class(fqcn.clone())),
174 ReferenceKind::ConstantAccess { class, constant } => {
175 Some(crate::Name::class_constant(class.clone(), constant.clone()))
176 }
177 ReferenceKind::GlobalConstant(fqn) => Some(crate::Name::global_constant(fqn.clone())),
178 ReferenceKind::Variable(_) => None,
179 ReferenceKind::UseImport(inner) => inner.to_name(),
180 ReferenceKind::Receiver => None,
181 }
182 }
183}