Skip to main content

mir_analyzer/
symbol.rs

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