Skip to main content

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