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