Skip to main content

mir_analyzer/db/
nodes.rs

1use std::sync::Arc;
2
3use mir_codebase::storage::{Assertion, FnParam, Location, TemplateParam, Visibility};
4use mir_codebase::StubSlice;
5use mir_issues::Issue;
6use mir_types::Union;
7
8// SourceFile input (S1)
9
10/// Source file registered as a Salsa input.
11/// Setting `text` on an existing `SourceFile` is the single write that drives
12/// all downstream query invalidation.
13#[salsa::input]
14pub struct SourceFile {
15    pub path: Arc<str>,
16    pub text: Arc<str>,
17}
18
19// FileDefinitions (S1)
20
21/// Result of the `collect_file_definitions` tracked query.
22#[derive(Clone, Debug)]
23pub struct FileDefinitions {
24    pub slice: Arc<StubSlice>,
25    pub issues: Arc<Vec<Issue>>,
26}
27
28impl PartialEq for FileDefinitions {
29    fn eq(&self, other: &Self) -> bool {
30        Arc::ptr_eq(&self.slice, &other.slice) && Arc::ptr_eq(&self.issues, &other.issues)
31    }
32}
33
34// SAFETY: FileDefinitions contains Arc pointers and Vec, which are Move-safe.
35// The pointer passed to maybe_update is provided by Salsa and points to
36// properly aligned and initialized memory. We have exclusive write access
37// through the mutable pointer (Salsa guarantees this). The in-place update
38// is safe because we own both the old and new values.
39unsafe impl salsa::Update for FileDefinitions {
40    unsafe fn maybe_update(old_pointer: *mut Self, new_value: Self) -> bool {
41        unsafe { *old_pointer = new_value };
42        true
43    }
44}
45
46// ClassNode input (S2)
47
48/// `(interface_fqcn, type_args)` pairs from `@implements Iface<T1, T2>`
49/// docblocks.  Stored on `ClassNode` for classes only.
50pub type ImplementsTypeArgs = Arc<[(Arc<str>, Arc<[Union]>)]>;
51
52/// Salsa input representing a single class or interface in the inheritance
53/// graph.  Fields are kept minimal — only what `class_ancestors` needs.
54///
55/// Invariant: every FQCN in the codebase that is known to the Salsa DB has
56/// exactly one `ClassNode` handle, stored in `MirDb::class_nodes`.  When a
57/// class is removed (file deleted or re-indexed), its node is marked
58/// `active = false` rather than dropped, so dependent `class_ancestors` queries
59/// can still observe the change and re-run.
60#[salsa::input]
61pub struct ClassNode {
62    pub fqcn: Arc<str>,
63    /// `false` when the class has been removed from the codebase.  Dependent
64    /// queries observe this change and re-run, returning empty ancestors.
65    pub active: bool,
66    pub is_interface: bool,
67    /// `true` for trait nodes.  Traits don't currently participate in the
68    /// `class_ancestors` query (it returns empty for traits), but registering
69    /// them as `ClassNode`s lets callers answer `type_exists`-style questions
70    /// through the db.
71    pub is_trait: bool,
72    /// `true` for enum nodes.  See note on `is_trait`.
73    pub is_enum: bool,
74    /// `true` if the class is declared `abstract`.  Always `false` for
75    /// interfaces, traits, and enums.
76    pub is_abstract: bool,
77    /// Direct parent class (classes only; `None` for interfaces).
78    pub parent: Option<Arc<str>>,
79    /// Directly implemented interfaces (classes only).
80    pub interfaces: Arc<[Arc<str>]>,
81    /// Used traits (classes only).  Traits are added to the ancestor list but
82    /// their own ancestors are not recursed into, matching PHP semantics.
83    pub traits: Arc<[Arc<str>]>,
84    /// Directly extended interfaces (interfaces only).
85    pub extends: Arc<[Arc<str>]>,
86    /// Declared `@template` parameters from the class/interface/trait
87    /// docblock.  Empty for classes without templates.
88    pub template_params: Arc<[TemplateParam]>,
89    /// `@psalm-require-extends` / `@phpstan-require-extends` — FQCNs that
90    /// using classes must extend.  Populated for trait nodes only; empty for
91    /// classes/interfaces/enums.
92    pub require_extends: Arc<[Arc<str>]>,
93    /// `@psalm-require-implements` / `@phpstan-require-implements` — FQCNs
94    /// that using classes must implement.  Populated for trait nodes only;
95    /// empty for classes/interfaces/enums.
96    pub require_implements: Arc<[Arc<str>]>,
97    /// `true` if this is a *backed* enum (declared with a scalar type).
98    /// Always `false` for non-enum nodes and pure (unbacked) enums.  Used by
99    /// `extends_or_implements_via_db` to answer the implicit `BackedEnum`
100    /// interface check.
101    pub is_backed_enum: bool,
102    /// `@mixin` / `@psalm-mixin` FQCNs declared on the class docblock.
103    /// Used by `lookup_method_in_chain` for delegated magic-method lookup.
104    /// Empty for interfaces, traits, and enums (mixin is a class-only
105    /// docblock concept).
106    pub mixins: Arc<[Arc<str>]>,
107    /// `@deprecated` message from the class docblock, if any.  Mirrors
108    /// `ClassStorage::deprecated`.  Empty / `None` for interfaces, traits,
109    /// and enums (S5-PR42 only mirrors the class-level field — those storages
110    /// don't carry a deprecated message).
111    pub deprecated: Option<Arc<str>>,
112    /// For backed-enum nodes: the declared scalar type (`int`/`string`).
113    /// Mirrors `EnumStorage::scalar_type`.  `None` for non-enum nodes and
114    /// for unbacked (pure) enums.  Used by the `Enum->value` property read
115    /// in `expr.rs` to return the backed scalar type instead of `mixed`.
116    pub enum_scalar_type: Option<Union>,
117    /// `true` if the class is declared `final`.  Always `false` for
118    /// interfaces, traits, and enums (PHP enums are implicitly final but the
119    /// codebase doesn't currently track that on `EnumStorage`).
120    pub is_final: bool,
121    /// `true` if the class is declared `readonly`.  Always `false` for
122    /// non-class kinds.
123    pub is_readonly: bool,
124    /// Source location of the class declaration.  Mirrors
125    /// `ClassStorage::location` (and `InterfaceStorage::location`,
126    /// `TraitStorage::location`, `EnumStorage::location`).  Used by
127    /// `ClassAnalyzer` to attribute issues to the right span.
128    pub location: Option<Location>,
129    /// Type arguments from `@extends Parent<T1, T2>` — populated for
130    /// classes only.  Mirrors `ClassStorage::extends_type_args`.
131    pub extends_type_args: Arc<[Union]>,
132    /// Type arguments from `@implements Iface<T1, T2>` — populated for
133    /// classes only.  Mirrors `ClassStorage::implements_type_args`.
134    pub implements_type_args: ImplementsTypeArgs,
135}
136
137// FunctionNode input (S5-PR2)
138
139/// Salsa input representing a single global function.
140///
141/// `inferred_return_type` is the Pass-2-derived return type, populated
142/// per-function by the priming sweep.  It is committed to Salsa serially
143/// after the parallel sweep returns (so worker db clones have dropped
144/// and `Storage::cancel_others` sees strong-count==1).  The buffer-and-
145/// commit pattern lives in [`InferredReturnTypes`] and
146/// [`MirDb::commit_inferred_return_types`].
147///
148/// Invariant: every FQN known to the Salsa DB has exactly one `FunctionNode`
149/// handle in `MirDb::function_nodes`.  Removed functions are marked
150/// `active = false` rather than dropped.
151#[salsa::input]
152pub struct FunctionNode {
153    pub fqn: Arc<str>,
154    pub short_name: Arc<str>,
155    pub active: bool,
156    pub params: Arc<[FnParam]>,
157    pub return_type: Option<Arc<Union>>,
158    pub inferred_return_type: Option<Arc<Union>>,
159    pub template_params: Arc<[TemplateParam]>,
160    pub assertions: Arc<[Assertion]>,
161    pub throws: Arc<[Arc<str>]>,
162    pub deprecated: Option<Arc<str>>,
163    pub is_pure: bool,
164    /// Source location of the declaration.  `None` for functions registered
165    /// without a known origin (e.g. some legacy test fixtures).
166    pub location: Option<Location>,
167}
168
169// MethodNode input (S5-PR3)
170
171/// Salsa input representing a single method or interface/trait method.
172///
173/// `inferred_return_type` is the Pass-2-derived return type, populated per
174/// method by the priming sweep.  Committed to Salsa serially after the
175/// parallel sweep returns; see [`FunctionNode`] for the buffer-and-commit
176/// pattern that resolves the historical "S3 deadlock".
177///
178/// The node is keyed by `(fqcn, method_name_lower)` where `fqcn` is the
179/// FQCN of the **owning** class/interface/trait and `method_name_lower` is
180/// the PHP-normalised (lowercased) method name.  Nodes for classes that are
181/// removed from the codebase are marked `active = false` via
182/// `deactivate_class_methods` rather than being dropped.
183#[salsa::input]
184pub struct MethodNode {
185    pub fqcn: Arc<str>,
186    pub name: Arc<str>,
187    pub active: bool,
188    pub params: Arc<[FnParam]>,
189    pub return_type: Option<Arc<Union>>,
190    pub inferred_return_type: Option<Arc<Union>>,
191    pub template_params: Arc<[TemplateParam]>,
192    pub assertions: Arc<[Assertion]>,
193    pub throws: Arc<[Arc<str>]>,
194    pub deprecated: Option<Arc<str>>,
195    pub is_internal: bool,
196    pub visibility: Visibility,
197    pub is_static: bool,
198    pub is_abstract: bool,
199    pub is_final: bool,
200    pub is_constructor: bool,
201    pub is_pure: bool,
202    /// Source location of the declaration.  `None` for synthesized methods
203    /// (e.g. enum implicit `cases`/`from`/`tryFrom`).
204    pub location: Option<Location>,
205}
206
207// PropertyNode input (S5-PR4)
208
209/// Salsa input representing a single class/trait property.
210///
211/// `inferred_ty` is intentionally absent — it stays in `PropertyStorage` until
212/// a future S3-style tracked query promotes it.
213///
214/// Keyed by `(owner fqcn, prop_name)` — property names are case-sensitive.
215#[salsa::input]
216pub struct PropertyNode {
217    pub fqcn: Arc<str>,
218    pub name: Arc<str>,
219    pub active: bool,
220    pub ty: Option<Union>,
221    pub visibility: Visibility,
222    pub is_static: bool,
223    pub is_readonly: bool,
224    pub location: Option<Location>,
225}
226
227// ClassConstantNode input (S5-PR4)
228
229/// Salsa input representing a single class/interface/enum constant.
230///
231/// Keyed by `(owner fqcn, const_name)` — constant names are case-sensitive.
232#[salsa::input]
233pub struct ClassConstantNode {
234    pub fqcn: Arc<str>,
235    pub name: Arc<str>,
236    pub active: bool,
237    pub ty: Union,
238    pub visibility: Option<Visibility>,
239    pub is_final: bool,
240    /// Source location of the declaration.  Mirrors `ConstantStorage::location`
241    /// for class/interface/trait constants, and `EnumCaseStorage::location` for
242    /// enum cases.  `None` for nodes registered without a source span.
243    pub location: Option<Location>,
244}
245
246// GlobalConstantNode input (S5-PR47)
247
248/// Salsa input representing a global PHP constant (e.g. `PHP_EOL`).
249/// Mirrors `Codebase::constants`.
250#[salsa::input]
251pub struct GlobalConstantNode {
252    pub fqn: Arc<str>,
253    pub active: bool,
254    pub ty: Union,
255}
256
257// Ancestors return type (S2)
258
259/// The computed ancestor list for a class or interface.
260///
261/// Uses content equality so Salsa's cycle-convergence check can detect
262/// fixpoints correctly (two empty lists from different iterations are equal).
263#[derive(Clone, Debug, Default)]
264pub struct Ancestors(pub Vec<Arc<str>>);
265
266impl PartialEq for Ancestors {
267    fn eq(&self, other: &Self) -> bool {
268        self.0.len() == other.0.len()
269            && self
270                .0
271                .iter()
272                .zip(&other.0)
273                .all(|(a, b)| a.as_ref() == b.as_ref())
274    }
275}
276
277// SAFETY: Ancestors contains Arc pointers, which are Move-safe.
278// The pointer passed to maybe_update is provided by Salsa and points to
279// properly aligned and initialized memory. We dereference it to check equality
280// and conditionally update. Salsa guarantees exclusive write access through
281// the mutable pointer. The comparison is safe because we're comparing valid
282// initialized values.
283unsafe impl salsa::Update for Ancestors {
284    unsafe fn maybe_update(old_ptr: *mut Self, new_val: Self) -> bool {
285        let old = unsafe { &mut *old_ptr };
286        if *old == new_val {
287            return false;
288        }
289        *old = new_val;
290        true
291    }
292}