Skip to main content

mir_codebase/
definitions.rs

1use std::sync::Arc;
2
3/// Insertion-ordered member map keyed by lowercased member name.
4/// FxHash instead of SipHash: member lookup is one of the hottest analyzer
5/// operations and the keys are short trusted identifiers.
6pub type MemberMap<V> = indexmap::IndexMap<std::sync::Arc<str>, V, rustc_hash::FxBuildHasher>;
7use mir_types::{Location, Name, Type};
8use rustc_hash::FxHashMap;
9use serde::{Deserialize, Serialize};
10
11// ---------------------------------------------------------------------------
12// Interned common types for deduplication
13// ---------------------------------------------------------------------------
14
15/// Interned Type types for common parameter/property types.
16/// Deduplicates allocations when thousands of parameters share types like `string`, `int`, etc.
17mod interned_types {
18    use super::*;
19    use std::sync::OnceLock;
20
21    fn intern_string() -> Arc<Type> {
22        Arc::new(Type::string())
23    }
24
25    fn intern_int() -> Arc<Type> {
26        Arc::new(Type::int())
27    }
28
29    fn intern_float() -> Arc<Type> {
30        Arc::new(Type::float())
31    }
32
33    fn intern_bool() -> Arc<Type> {
34        Arc::new(Type::bool())
35    }
36
37    fn intern_mixed() -> Arc<Type> {
38        Arc::new(Type::mixed())
39    }
40
41    fn intern_null() -> Arc<Type> {
42        Arc::new(Type::null())
43    }
44
45    fn intern_void() -> Arc<Type> {
46        Arc::new(Type::void())
47    }
48
49    static STRING: OnceLock<Arc<Type>> = OnceLock::new();
50    static INT: OnceLock<Arc<Type>> = OnceLock::new();
51    static FLOAT: OnceLock<Arc<Type>> = OnceLock::new();
52    static BOOL: OnceLock<Arc<Type>> = OnceLock::new();
53    static MIXED: OnceLock<Arc<Type>> = OnceLock::new();
54    static NULL: OnceLock<Arc<Type>> = OnceLock::new();
55    static VOID: OnceLock<Arc<Type>> = OnceLock::new();
56
57    pub fn string() -> Arc<Type> {
58        STRING.get_or_init(intern_string).clone()
59    }
60
61    pub fn int() -> Arc<Type> {
62        INT.get_or_init(intern_int).clone()
63    }
64
65    pub fn float() -> Arc<Type> {
66        FLOAT.get_or_init(intern_float).clone()
67    }
68
69    pub fn bool() -> Arc<Type> {
70        BOOL.get_or_init(intern_bool).clone()
71    }
72
73    pub fn mixed() -> Arc<Type> {
74        MIXED.get_or_init(intern_mixed).clone()
75    }
76
77    pub fn null() -> Arc<Type> {
78        NULL.get_or_init(intern_null).clone()
79    }
80
81    pub fn void() -> Arc<Type> {
82        VOID.get_or_init(intern_void).clone()
83    }
84
85    /// Global content-keyed `Arc<Type>` interner. Any structurally-identical
86    /// Type is shared as a single Arc across the session.
87    ///
88    /// Why: PHP codebases re-declare a small set of type shapes thousands of
89    /// times — `string|null` return types, `int` params, `array<string, mixed>`
90    /// property types. Without interning, each declaration allocates its own
91    /// `Arc<Type>` plus the inline `SmallVec<[Atomic; 2]>` and any boxed
92    /// `Atomic` payloads. With interning, only the first occurrence allocates.
93    ///
94    /// Trade-off: every `intern_or_wrap` call hashes + does one DashMap lookup.
95    /// Hashing a `Type` is cheap (SmallVec, small atomics) — measured cost is
96    /// well below the alloc-savings benefit on real workloads.
97    type InternTable = dashmap::DashMap<Type, Arc<Type>, rustc_hash::FxBuildHasher>;
98
99    static GLOBAL_UNION_INTERN: std::sync::OnceLock<InternTable> = std::sync::OnceLock::new();
100
101    fn global_intern_table() -> &'static InternTable {
102        GLOBAL_UNION_INTERN.get_or_init(|| dashmap::DashMap::with_hasher(Default::default()))
103    }
104
105    /// Try to intern a Type if it matches a common type, otherwise wrap in Arc.
106    pub fn intern_or_wrap(union: Type) -> Arc<Type> {
107        // Fast path 1: single-atomic scalar — covered by `OnceLock` constants.
108        // Avoids any DashMap traffic for the most common case. Excludes
109        // `falsy_stripped`/`possibly_absent_offset` types (e.g.
110        // `preg_replace_callback`'s narrowed `string`, or a generic
111        // array-offset read's value type) — the canonical singleton never
112        // carries either flag, so returning it here would silently drop it.
113        if union.types.len() == 1
114            && !union.possibly_undefined
115            && !union.from_docblock
116            && !union.falsy_stripped
117            && !union.possibly_absent_offset
118        {
119            match &union.types[0] {
120                mir_types::Atomic::TString => return string(),
121                mir_types::Atomic::TInt => return int(),
122                mir_types::Atomic::TFloat => return float(),
123                mir_types::Atomic::TBool => return bool(),
124                mir_types::Atomic::TMixed => return mixed(),
125                mir_types::Atomic::TNull => return null(),
126                mir_types::Atomic::TVoid => return void(),
127                _ => {}
128            }
129        }
130        // Fast path 2: empty Type — also a common case (e.g. unresolved
131        // return type). Don't pollute the intern table with these.
132        if union.types.is_empty() {
133            return Arc::new(union);
134        }
135        // Global path: dedup against any previously-seen identical Type.
136        let table = global_intern_table();
137        if let Some(existing) = table.get(&union) {
138            return Arc::clone(existing.value());
139        }
140        let arc = Arc::new(union.clone());
141        // `insert` semantics: if a parallel thread beat us, its Arc wins.
142        // The lookup-before-insert race is benign — both Arcs are content-
143        // equal — but we still want to share the canonical one going forward.
144        match table.entry(union) {
145            dashmap::mapref::entry::Entry::Occupied(o) => Arc::clone(o.get()),
146            dashmap::mapref::entry::Entry::Vacant(v) => {
147                v.insert(Arc::clone(&arc));
148                arc
149            }
150        }
151    }
152}
153
154// ---------------------------------------------------------------------------
155// Shared primitives
156// ---------------------------------------------------------------------------
157
158#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
159pub enum Visibility {
160    Public,
161    Protected,
162    Private,
163}
164
165impl Visibility {
166    pub fn is_at_least(&self, required: Visibility) -> bool {
167        *self <= required
168    }
169}
170
171impl std::fmt::Display for Visibility {
172    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
173        match self {
174            Visibility::Public => write!(f, "public"),
175            Visibility::Protected => write!(f, "protected"),
176            Visibility::Private => write!(f, "private"),
177        }
178    }
179}
180
181fn serialize_template_bound<S>(value: &Option<Arc<Type>>, serializer: S) -> Result<S::Ok, S::Error>
182where
183    S: serde::Serializer,
184{
185    value.as_deref().serialize(serializer)
186}
187
188fn deserialize_template_bound<'de, D>(deserializer: D) -> Result<Option<Arc<Type>>, D::Error>
189where
190    D: serde::Deserializer<'de>,
191{
192    Option::<Type>::deserialize(deserializer).map(|opt| opt.map(interned_types::intern_or_wrap))
193}
194
195#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
196pub struct TemplateParam {
197    pub name: Name,
198    /// Declared upper bound, e.g. `@template T of Traversable`.
199    /// Stored as `Option<Arc<Type>>` so common bounds (e.g. `object`, `mixed`)
200    /// are deduplicated across all template params via the global intern table.
201    #[serde(
202        serialize_with = "serialize_template_bound",
203        deserialize_with = "deserialize_template_bound"
204    )]
205    pub bound: Option<Arc<Type>>,
206    /// Default type used when nothing binds this template param, e.g.
207    /// `@template T = string`. Falls back to `mixed` when absent, same as
208    /// before this field existed.
209    #[serde(
210        default,
211        serialize_with = "serialize_template_bound",
212        deserialize_with = "deserialize_template_bound"
213    )]
214    pub default: Option<Arc<Type>>,
215    /// The entity (class or function FQN) that declared this template param.
216    pub defining_entity: Name,
217    pub variance: mir_types::Variance,
218}
219
220#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
221pub struct DeclaredParam {
222    pub name: Name,
223    /// Parameter type. Stored as `Option<Arc<Type>>` to enable deduplication of
224    /// common types across parameters. Many parameters share types like `string`,
225    /// `int`, `bool`, etc., so interning via Arc saves allocations.
226    #[serde(
227        deserialize_with = "deserialize_param_type",
228        serialize_with = "serialize_param_type"
229    )]
230    pub ty: Option<Arc<Type>>,
231    /// Out-type declared via `@param-out` / `@psalm-param-out`. When set, this
232    /// type is written back to the caller's argument variable after the call
233    /// instead of (or in addition to) the declared in-type.
234    #[serde(
235        default,
236        deserialize_with = "deserialize_param_type",
237        serialize_with = "serialize_param_type"
238    )]
239    pub out_ty: Option<Arc<Type>>,
240    /// `@param-closure-this Type`: the `$this` type of a closure literal passed for this parameter.
241    #[serde(
242        default,
243        deserialize_with = "deserialize_param_type",
244        serialize_with = "serialize_param_type"
245    )]
246    pub closure_this: Option<Arc<Type>>,
247    /// Raw `@param` type spelling when `ty` came from a docblock rather than a
248    /// native hint. Preserved so later analysis can resolve ambiguous
249    /// pseudo-type aliases (e.g. `integer`) against same-named classes once the
250    /// full symbol table is available.
251    #[serde(default)]
252    pub doc_type_raw: Option<Arc<str>>,
253    /// Declaring file for `doc_type_raw`. Needed because inherited signatures
254    /// may be consumed from a different file/namespace than the original
255    /// docblock declaration.
256    #[serde(default)]
257    pub doc_type_file: Option<Arc<str>>,
258    /// Whether this parameter has a default value. Analysis uses it only to mark
259    /// parameters optional; the value itself is kept as text in `default_text`.
260    pub has_default: bool,
261    /// Source text of the default value expression (`10`, `null`, `self::X`).
262    /// `None` when there is no default or its source text is unavailable.
263    #[serde(default)]
264    pub default_text: Option<Arc<str>>,
265    pub is_variadic: bool,
266    pub is_byref: bool,
267    pub is_optional: bool,
268}
269
270impl std::hash::Hash for DeclaredParam {
271    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
272        self.name.hash(state);
273        self.has_default.hash(state);
274        self.default_text.hash(state);
275        self.is_variadic.hash(state);
276        self.is_byref.hash(state);
277        self.is_optional.hash(state);
278        self.doc_type_raw.hash(state);
279        self.doc_type_file.hash(state);
280        // Hash the type value (not the Arc pointer) so that two FnParams with
281        // equal types (PartialEq) always produce the same hash, even when they
282        // are backed by different Arc allocations.
283        self.ty.as_deref().hash(state);
284        self.out_ty.as_deref().hash(state);
285        self.closure_this.as_deref().hash(state);
286    }
287}
288
289// Serde helpers to transparently convert between Option<Type> and Option<Arc<Type>>
290fn deserialize_param_type<'de, D>(deserializer: D) -> Result<Option<Arc<Type>>, D::Error>
291where
292    D: serde::Deserializer<'de>,
293{
294    Option::<Type>::deserialize(deserializer).map(|opt| opt.map(interned_types::intern_or_wrap))
295}
296
297fn serialize_param_type<S>(value: &Option<Arc<Type>>, serializer: S) -> Result<S::Ok, S::Error>
298where
299    S: serde::Serializer,
300{
301    let opt = value.as_ref().map(|arc| (**arc).clone());
302    opt.serialize(serializer)
303}
304
305fn deserialize_return_type<'de, D>(deserializer: D) -> Result<Option<Arc<Type>>, D::Error>
306where
307    D: serde::Deserializer<'de>,
308{
309    Option::<Type>::deserialize(deserializer).map(|opt| opt.map(interned_types::intern_or_wrap))
310}
311
312fn serialize_return_type<S>(value: &Option<Arc<Type>>, serializer: S) -> Result<S::Ok, S::Error>
313where
314    S: serde::Serializer,
315{
316    let opt = value.as_ref().map(|arc| (**arc).clone());
317    opt.serialize(serializer)
318}
319
320fn deserialize_params<'de, D>(deserializer: D) -> Result<Arc<[DeclaredParam]>, D::Error>
321where
322    D: serde::Deserializer<'de>,
323{
324    Vec::<DeclaredParam>::deserialize(deserializer).map(|v| Arc::from(v.into_boxed_slice()))
325}
326
327fn default_imports() -> Arc<FxHashMap<Name, Name>> {
328    Arc::new(FxHashMap::default())
329}
330
331/// Deserialize imports map. Supports both new (Name-keyed) and legacy
332/// (String-keyed) on-disk formats — older `cache.bin` files have plain
333/// `HashMap<String, String>`. Either way, we intern at load time so the
334/// in-memory representation is always `Arc<FxHashMap<Name, Name>>`.
335fn deserialize_imports<'de, D>(deserializer: D) -> Result<Arc<FxHashMap<Name, Name>>, D::Error>
336where
337    D: serde::Deserializer<'de>,
338{
339    let raw = FxHashMap::<String, String>::deserialize(deserializer)?;
340    let mut out: FxHashMap<Name, Name> =
341        FxHashMap::with_capacity_and_hasher(raw.len(), Default::default());
342    for (k, v) in raw {
343        out.insert(Name::new(&k), Name::new(&v));
344    }
345    Ok(Arc::new(out))
346}
347
348/// Serialize imports as the legacy `HashMap<String, String>` shape so disk
349/// caches written by this version remain compatible with readers that haven't
350/// been recompiled yet (and vice-versa).
351fn serialize_imports<S>(
352    value: &Arc<FxHashMap<Name, Name>>,
353    serializer: S,
354) -> Result<S::Ok, S::Error>
355where
356    S: serde::Serializer,
357{
358    use serde::ser::SerializeMap;
359    let mut map = serializer.serialize_map(Some(value.len()))?;
360    for (k, v) in value.iter() {
361        map.serialize_entry(k.as_str(), v.as_str())?;
362    }
363    map.end()
364}
365
366fn serialize_params<S>(value: &Arc<[DeclaredParam]>, serializer: S) -> Result<S::Ok, S::Error>
367where
368    S: serde::Serializer,
369{
370    value.as_ref().serialize(serializer)
371}
372
373/// Helper to wrap `Option<Type>` in interned `Arc<Type>`.
374pub fn wrap_param_type(ty: Option<Type>) -> Option<Arc<Type>> {
375    ty.map(interned_types::intern_or_wrap)
376}
377
378/// Helper to wrap return type `Option<Type>` in interned `Arc<Type>`.
379pub fn wrap_return_type(ty: Option<Type>) -> Option<Arc<Type>> {
380    ty.map(interned_types::intern_or_wrap)
381}
382
383/// Helper to wrap a `PropertyDef` type field (`ty`/`inferred_ty`/`default`) in
384/// an interned `Arc<Type>`, deduplicating common property types via the global
385/// pool. See [`PropertyDef`].
386pub fn wrap_property_type(ty: Option<Type>) -> Option<Arc<Type>> {
387    ty.map(interned_types::intern_or_wrap)
388}
389
390/// Helper to wrap a `TemplateParam.bound` in an interned `Arc<Type>`.
391pub fn wrap_template_bound(ty: Option<Type>) -> Option<Arc<Type>> {
392    ty.map(interned_types::intern_or_wrap)
393}
394
395/// Wrap a variable type in an interned `Arc<Type>`. Use instead of
396/// `Arc::new(ty)` at `FlowState::set_var` and parameter-init sites so that
397/// common scalars (string, int, bool, null, mixed) share a static Arc rather
398/// than allocating a fresh one per assignment.
399pub fn wrap_var_type(ty: Type) -> Arc<Type> {
400    interned_types::intern_or_wrap(ty)
401}
402
403// ---------------------------------------------------------------------------
404// Assertion — `@psalm-assert`, `@psalm-assert-if-true`, etc.
405// ---------------------------------------------------------------------------
406
407#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
408pub enum AssertionKind {
409    Assert,
410    AssertIfTrue,
411    AssertIfFalse,
412}
413
414#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
415pub struct Assertion {
416    pub kind: AssertionKind,
417    pub param: Arc<str>,
418    pub ty: Type,
419    /// True for the `!Type` negated form (`@psalm-assert !null $x`): the
420    /// parameter is asserted to NOT be `ty`, rather than to BE it.
421    #[serde(default)]
422    pub negated: bool,
423    /// Set for an assertion targeting a specific (possibly nested) array key
424    /// path of `param` rather than the whole parameter (`@psalm-assert-if-true
425    /// string $arr['a']['b']` — `param` is `"arr"`, `param_key` is
426    /// `[String("a"), String("b")]`). Empty means "targets the whole parameter".
427    #[serde(default)]
428    pub param_key: Vec<mir_types::atomic::ArrayKey>,
429}
430
431// ---------------------------------------------------------------------------
432// MethodDef
433// ---------------------------------------------------------------------------
434
435/// Prose following the type and name of `@param`, `@return` and `@throws`
436/// tags. Tags without prose are omitted.
437#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
438pub struct TagDescriptions {
439    /// `(parameter name without $, description)`.
440    pub params: Vec<(Arc<str>, Arc<str>)>,
441    pub returns: Option<Arc<str>>,
442    /// `(resolved exception FQCN, description)`.
443    pub throws: Vec<(Arc<str>, Arc<str>)>,
444}
445
446/// `@taint-escape`, `@taint-unescape` and `@psalm-flow` on a function or method.
447#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
448pub struct TaintAnnotations {
449    /// Sink kinds the return value no longer carries.
450    pub escape: Vec<Arc<str>>,
451    /// Sink kinds the return value regains from tainted arguments.
452    pub unescape: Vec<Arc<str>>,
453    /// Parameters whose taint flows into the return value.
454    pub flow_params: Vec<Arc<str>>,
455}
456
457#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
458pub struct MethodDef {
459    pub name: Arc<str>,
460    pub fqcn: Arc<str>,
461    #[serde(
462        deserialize_with = "deserialize_params",
463        serialize_with = "serialize_params"
464    )]
465    pub params: Arc<[DeclaredParam]>,
466    /// Type from annotation (`@return` / native type hint). `None` means unannotated.
467    /// Stored as `Option<Arc<Type>>` to enable deduplication of common return types
468    /// (e.g., `void`, `string`, `mixed`, `bool`) across thousands of methods.
469    #[serde(
470        deserialize_with = "deserialize_return_type",
471        serialize_with = "serialize_return_type"
472    )]
473    pub return_type: Option<Arc<Type>>,
474    /// Type inferred from body analysis. Stored as `Option<Arc<Type>>` (8 B) rather
475    /// than inline `Option<Type>` (176 B, no niche) — inference is now demand-driven
476    /// via salsa (`inferred_*_return_type_demand`), so this field is a rarely/never
477    /// populated fallback; shrinking it saves ~168 B on every MethodDef.
478    #[serde(
479        deserialize_with = "deserialize_return_type",
480        serialize_with = "serialize_return_type"
481    )]
482    pub inferred_return_type: Option<Arc<Type>>,
483    /// Native return hint kept beside a `@return` that names a template, to bound it when unbound.
484    #[serde(default)]
485    pub native_return: Option<Box<Type>>,
486    pub visibility: Visibility,
487    pub is_static: bool,
488    pub is_abstract: bool,
489    pub is_final: bool,
490    pub is_constructor: bool,
491    pub template_params: Vec<TemplateParam>,
492    pub assertions: Vec<Assertion>,
493    pub throws: Vec<Arc<str>>,
494    pub deprecated: Option<Arc<str>>,
495    pub is_internal: bool,
496    pub is_pure: bool,
497    /// An explicit `@impure`/`@psalm-impure`/`@phpstan-impure` declaration.
498    #[serde(default)]
499    pub is_impure: bool,
500    /// `@no-named-arguments` — callers must not use named argument syntax.
501    #[serde(default)]
502    pub no_named_arguments: bool,
503    /// True when the method has the `#[Override]` PHP attribute.
504    #[serde(default)]
505    pub is_override: bool,
506    pub location: Option<Location>,
507    /// Plain-text description from the docblock (text before `@tag` lines).
508    /// Used for hover info.
509    #[serde(default)]
510    pub docstring: Option<Arc<str>>,
511    /// Prose attached to `@param`, `@return` and `@throws` tags. `None` when
512    /// no tag carries a description.
513    #[serde(default)]
514    pub tag_descriptions: Option<Arc<TagDescriptions>>,
515    /// True for methods added via `@method` docblock annotations. Virtual
516    /// methods must not be required as concrete interface implementations.
517    #[serde(default)]
518    pub is_virtual: bool,
519    /// Parameters declared as taint sinks via `@taint-sink <kind> $param`.
520    /// Each entry is `(param_name_without_dollar, sink_kind_string)`.
521    #[serde(default)]
522    pub taint_sink_params: Vec<(Arc<str>, Arc<str>)>,
523    /// `@taint-source` — this method's return value is treated as tainted
524    /// (attacker-controlled) at every call site, mirroring `@taint-sink`'s
525    /// mechanism but marking the source side instead.
526    #[serde(default)]
527    pub is_taint_source: bool,
528    #[serde(default)]
529    pub taint: Option<Arc<TaintAnnotations>>,
530    /// `@if-this-is Type` — the resolved constraint a receiver's type must
531    /// satisfy for this method to be callable. `None` when absent.
532    #[serde(default)]
533    pub if_this_is: Option<Arc<Type>>,
534    /// `@psalm-self-out Type` / `@phpstan-self-out Type` — the receiver's type
535    /// after this call returns (e.g. a fluent builder that narrows `$this` as
536    /// it's configured). `None` when absent.
537    #[serde(default)]
538    pub self_out: Option<Arc<Type>>,
539    /// True when the method has `@inheritDoc` / `{@inheritDoc}` in its docblock.
540    /// The analyzer inherits the parent's return type, param types, throws, and
541    /// template params when this method has none of its own.
542    #[serde(default)]
543    pub is_inherit_doc: bool,
544    /// `@psalm-mutation-free` / `@phpstan-mutation-free` — this method must not
545    /// assign to `$this` properties (same constraint as `@psalm-immutable` on the
546    /// class, but scoped to this single method).
547    #[serde(default)]
548    pub is_mutation_free: bool,
549    /// `@psalm-external-mutation-free` — this method must not mutate any objects
550    /// passed as arguments, but is allowed to modify `$this`.
551    #[serde(default)]
552    pub is_external_mutation_free: bool,
553    /// The body provably never writes a property, so a call leaves the
554    /// receiver's property narrowing intact. Derived from the body, unlike
555    /// `is_mutation_free`, which comes from the annotation.
556    #[serde(default)]
557    pub inferred_mutation_free: bool,
558    /// Method names referenced via `@dataProvider name` / `#[DataProvider('name')]`
559    /// (PHPUnit) — treated as used by dead-code analysis, since PHPUnit invokes
560    /// them by name through reflection rather than a direct call site.
561    #[serde(default)]
562    pub data_provider_targets: Vec<Arc<str>>,
563}
564
565impl MethodDef {
566    pub fn effective_return_type(&self) -> Option<&Type> {
567        self.return_type
568            .as_deref()
569            .or(self.inferred_return_type.as_deref())
570    }
571}
572
573// ---------------------------------------------------------------------------
574// PropertyDef
575// ---------------------------------------------------------------------------
576
577#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
578pub struct PropertyDef {
579    pub name: Arc<str>,
580    /// Declared/inferred/default types. Stored as `Option<Arc<Type>>` (8 B)
581    /// rather than inline `Option<Type>` (176 B, no niche) and interned via the
582    /// global pool on construction/deserialization — common property types
583    /// (`string`, `int`, a shared class type) dedup to one allocation. Mirrors
584    /// `DeclaredParam::ty`. On-disk format is unchanged (the serde helpers (de)serialize
585    /// the inner `Type` transparently).
586    #[serde(
587        deserialize_with = "deserialize_param_type",
588        serialize_with = "serialize_param_type"
589    )]
590    pub ty: Option<Arc<Type>>,
591    #[serde(
592        deserialize_with = "deserialize_param_type",
593        serialize_with = "serialize_param_type"
594    )]
595    pub inferred_ty: Option<Arc<Type>>,
596    pub visibility: Visibility,
597    /// Asymmetric write visibility (`private(set)`); `None` follows `visibility`.
598    pub set_visibility: Option<Visibility>,
599    pub is_static: bool,
600    pub is_readonly: bool,
601    #[serde(
602        deserialize_with = "deserialize_param_type",
603        serialize_with = "serialize_param_type"
604    )]
605    pub default: Option<Arc<Type>>,
606    pub location: Option<Location>,
607    /// `@deprecated` docblock annotation, if present.
608    #[serde(default)]
609    pub deprecated: Option<Arc<str>>,
610    /// True when the property declares a PHP native type hint (`public int $x`).
611    /// A property typed only via a `@var` docblock (or untyped entirely) is
612    /// `false`: PHP gives such a property an implicit `null` default, so it is
613    /// never "uninitialized" (no MissingConstructor) and accepts `null` on
614    /// assignment regardless of the advisory docblock type.
615    #[serde(default)]
616    pub has_native_type: bool,
617    /// True when this entry was synthesised from a `@property` / `@property-read` /
618    /// `@property-write` docblock tag rather than a real PHP property declaration.
619    /// Such entries describe magic properties accessible via `__get`/`__set` and
620    /// do **not** participate in PHP's inheritance visibility rules.
621    #[serde(default)]
622    pub from_docblock: bool,
623    /// True when the property declares a `get` hook with no accompanying `set`
624    /// hook. Such a property is virtual: it has no backing storage, computes
625    /// its value on every access, and can never be assigned to at all — so it
626    /// is never "left uninitialized" by the constructor. A property that also
627    /// declares (or only declares) a `set` hook is backed and can still be
628    /// left uninitialized, so it does not count here.
629    #[serde(default)]
630    pub get_only_hook: bool,
631    /// Hooks declared without a body (interface or `abstract` property): bit 0 `get`, bit 1 `set`.
632    #[serde(default)]
633    pub abstract_hooks: u8,
634    /// For a virtual property (hooks, none touching `$this->name`), its declared hooks as in
635    /// `abstract_hooks`; 0 for a backed one.
636    #[serde(default)]
637    pub virtual_hooks: u8,
638    /// True when `readonly` comes from a native PHP keyword (`readonly` modifier or
639    /// `readonly class`). False when only a `@readonly` docblock annotation is present.
640    /// Distinguishes PHP-enforced read-only from advisory documentation.
641    #[serde(default)]
642    pub has_native_readonly: bool,
643    /// Declared with `#[Override]` (PHP 8.5 checks properties too).
644    #[serde(default)]
645    pub is_override: bool,
646    /// The PHP native type hint alone, with any `@var` docblock refinement stripped —
647    /// `None` when `has_native_type` is false. `ty` mixes in the docblock type when
648    /// present, which makes it unsuitable for checking PHP's redeclared-property
649    /// type invariance rule: that rule is enforced by the runtime purely on the
650    /// native hint, never on the (unenforced) docblock annotation.
651    #[serde(default)]
652    #[serde(
653        deserialize_with = "deserialize_param_type",
654        serialize_with = "serialize_param_type"
655    )]
656    pub native_ty: Option<Arc<Type>>,
657}
658
659// ---------------------------------------------------------------------------
660// ConstantDef
661// ---------------------------------------------------------------------------
662
663#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
664pub struct ConstantDef {
665    pub name: Arc<str>,
666    pub ty: Type,
667    /// Literal initializer when a `@var` makes `ty` wider than the value.
668    #[serde(default)]
669    pub literal_value: Option<Type>,
670    pub visibility: Option<Visibility>,
671    #[serde(default)]
672    pub is_final: bool,
673    pub location: Option<Location>,
674    /// `@deprecated` docblock annotation, if present.
675    #[serde(default)]
676    pub deprecated: Option<Arc<str>>,
677}
678
679// ---------------------------------------------------------------------------
680// ClassDef
681// ---------------------------------------------------------------------------
682
683#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
684pub struct ClassDef {
685    pub fqcn: Arc<str>,
686    pub short_name: Arc<str>,
687    pub parent: Option<Arc<str>>,
688    pub interfaces: Vec<Arc<str>>,
689    pub traits: Vec<Arc<str>>,
690    pub own_methods: MemberMap<Arc<MethodDef>>,
691    pub own_properties: MemberMap<PropertyDef>,
692    pub own_constants: MemberMap<ConstantDef>,
693    #[serde(default)]
694    pub mixins: Vec<Arc<str>>,
695    /// Source of each `mixins` tag, same order; empty when unknown.
696    #[serde(default)]
697    pub mixin_locations: Vec<Location>,
698    pub template_params: Vec<TemplateParam>,
699    /// Type arguments from `@extends ParentClass<T1, T2>` — maps parent's template params to concrete types.
700    pub extends_type_args: Vec<Type>,
701    /// Type arguments from `@implements Interface<T1, T2>`.
702    #[serde(default)]
703    pub implements_type_args: Vec<(Arc<str>, Vec<Type>)>,
704    /// Type arguments from `@use TraitName<T1, T2>`, keyed by the used
705    /// trait's FQCN — a class's `use` clause (unlike `@extends`) may name
706    /// several traits at once.
707    #[serde(default)]
708    pub trait_use_type_args: Vec<(Arc<str>, Vec<Type>)>,
709    pub is_abstract: bool,
710    pub is_final: bool,
711    pub is_readonly: bool,
712    pub deprecated: Option<Arc<str>>,
713    pub is_internal: bool,
714    /// Set when the class carries `@psalm-immutable` — non-constructor methods must not
715    /// assign to `$this` properties.
716    #[serde(default)]
717    pub is_immutable: bool,
718    /// Attribute target flags if this class has `#[Attribute]` annotation.
719    /// `None` = not an attribute class. The value is a bitmask of PHP's
720    /// `Attribute::TARGET_*` constants (e.g. `Attribute::TARGET_CLASS = 1`).
721    #[serde(default)]
722    pub attribute_flags: Option<i64>,
723    /// Declared with `#[AllowDynamicProperties]`; subclasses inherit it.
724    #[serde(default)]
725    pub allows_dynamic_properties: bool,
726    pub location: Option<Location>,
727    /// Per-`use` statement locations for each used trait: `(fqcn, location)` in
728    /// declaration order, parallel to `traits`.  Absent from older serialized
729    /// slices; defaults to empty.
730    #[serde(default)]
731    pub trait_use_locations: Vec<(Arc<str>, Location)>,
732    /// Type aliases declared on this class via `@psalm-type` / `@phpstan-type`.
733    #[serde(default)]
734    pub type_aliases: FxHashMap<Arc<str>, Type>,
735    /// Raw import-type declarations (`(local_name, original_name, from_class)`) — resolved during finalization.
736    #[serde(default)]
737    pub pending_import_types: Vec<(Arc<str>, Arc<str>, Arc<str>)>,
738    /// Trait precedence exclusions from `insteadof` declarations in this class's `use` blocks.
739    /// Maps method_name_lowercase → list of trait FQCNs whose version of the method is excluded.
740    /// E.g. `use A, B { B::hello insteadof A; }` stores `"hello" → ["A"]`.
741    #[serde(default)]
742    pub trait_insteadof: MemberMap<Vec<Arc<str>>>,
743    /// Trait method aliases from `as` declarations in this class's `use` blocks.
744    /// Maps new_name_lowercase → (optional_trait_fqcn, original_method_name_lowercase, visibility_override, alias_cased).
745    /// `alias_cased` is the alias name preserving the original PHP casing (for error messages / case checks).
746    /// Visibility is `None` when the `as` clause only renames without changing visibility.
747    /// E.g. `use Base { __construct as __constructBase; }` stores
748    ///   `"__constructbase" → (None, "__construct", None, "__constructBase")`.
749    /// E.g. `use T { foo as private traitFoo; }` stores
750    ///   `"traitfoo" → (None, "foo", Some(Private), "traitFoo")`.
751    #[serde(default)]
752    #[allow(clippy::type_complexity)]
753    pub trait_aliases:
754        FxHashMap<Arc<str>, (Option<Arc<str>>, Arc<str>, Option<Visibility>, Arc<str>)>,
755    /// Source span of each `trait_aliases` adaptation, keyed the same way.
756    #[serde(default)]
757    pub trait_alias_locations: FxHashMap<Arc<str>, Location>,
758}
759
760impl ClassDef {
761    pub fn get_method(&self, name: &str) -> Option<&MethodDef> {
762        // PHP method names are case-insensitive; caller should pass lowercase name.
763        // Only searches own_methods — inherited method resolution is done by
764        // `db::lookup_method_in_chain`.
765        self.own_methods.get(name).map(Arc::as_ref).or_else(|| {
766            self.own_methods
767                .iter()
768                .find(|(k, _)| k.as_ref().eq_ignore_ascii_case(name))
769                .map(|(_, v)| v.as_ref())
770        })
771    }
772
773    pub fn get_property(&self, name: &str) -> Option<&PropertyDef> {
774        self.own_properties.get(name)
775    }
776}
777
778// ---------------------------------------------------------------------------
779// InterfaceDef
780// ---------------------------------------------------------------------------
781
782#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
783pub struct InterfaceDef {
784    pub fqcn: Arc<str>,
785    pub short_name: Arc<str>,
786    pub extends: Vec<Arc<str>>,
787    pub own_methods: MemberMap<Arc<MethodDef>>,
788    pub own_constants: MemberMap<ConstantDef>,
789    pub template_params: Vec<TemplateParam>,
790    pub location: Option<Location>,
791    /// `@deprecated` docblock annotation, if present.
792    #[serde(default)]
793    pub deprecated: Option<Arc<str>>,
794    /// Properties declared via `@property*` docblock annotations on the interface.
795    #[serde(default)]
796    pub own_properties: MemberMap<PropertyDef>,
797    /// `@seal-properties` / `@psalm-seal-properties` — disallows undeclared property access.
798    #[serde(default)]
799    pub seal_properties: bool,
800    /// Type arguments from `@extends BaseIface<T1, T2>` docblock lines, keyed by the
801    /// extended interface's FQCN — an interface's native `extends` list (unlike a
802    /// class's single parent) may name several base interfaces at once.
803    #[serde(default)]
804    pub extends_type_args: Vec<(Arc<str>, Vec<Type>)>,
805    /// Type aliases declared on this interface via `@psalm-type` / `@phpstan-type`.
806    #[serde(default)]
807    pub type_aliases: FxHashMap<Arc<str>, Type>,
808}
809
810// ---------------------------------------------------------------------------
811// TraitDef
812// ---------------------------------------------------------------------------
813
814#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
815pub struct TraitDef {
816    pub fqcn: Arc<str>,
817    pub short_name: Arc<str>,
818    pub own_methods: MemberMap<Arc<MethodDef>>,
819    pub own_properties: MemberMap<PropertyDef>,
820    pub own_constants: MemberMap<ConstantDef>,
821    pub template_params: Vec<TemplateParam>,
822    /// Traits used by this trait (`use OtherTrait;` inside a trait body).
823    pub traits: Vec<Arc<str>>,
824    pub location: Option<Location>,
825    /// Per-`use` statement locations for each used trait: `(fqcn, location)` in
826    /// declaration order, parallel to `traits`. Mirrors `ClassDef`/`EnumDef`'s
827    /// field of the same name. Absent from older serialized slices; defaults
828    /// to empty.
829    #[serde(default)]
830    pub trait_use_locations: Vec<(Arc<str>, Location)>,
831    /// Type arguments from `@use OtherTrait<T1, T2>` (a trait may itself
832    /// `use` a generic trait).
833    #[serde(default)]
834    pub trait_use_type_args: Vec<(Arc<str>, Vec<Type>)>,
835    /// Set when the trait carries `@psalm-immutable`.
836    #[serde(default)]
837    pub is_immutable: bool,
838    /// `@psalm-require-extends` / `@phpstan-require-extends` — FQCNs that using classes must extend.
839    #[serde(default)]
840    pub require_extends: Vec<Arc<str>>,
841    /// `@psalm-require-implements` / `@phpstan-require-implements` — FQCNs that using classes must implement.
842    #[serde(default)]
843    pub require_implements: Vec<Arc<str>>,
844    /// `@deprecated` docblock annotation, if present.
845    #[serde(default)]
846    pub deprecated: Option<Arc<str>>,
847    /// Type aliases declared on this trait via `@psalm-type` / `@phpstan-type`.
848    #[serde(default)]
849    pub type_aliases: FxHashMap<Arc<str>, Type>,
850}
851
852// ---------------------------------------------------------------------------
853// EnumDef
854// ---------------------------------------------------------------------------
855
856#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
857pub struct EnumCaseDef {
858    pub name: Arc<str>,
859    pub value: Option<Type>,
860    pub location: Option<Location>,
861    /// `@deprecated` docblock annotation, if present.
862    #[serde(default)]
863    pub deprecated: Option<Arc<str>>,
864}
865
866#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
867pub struct EnumDef {
868    pub fqcn: Arc<str>,
869    pub short_name: Arc<str>,
870    pub scalar_type: Option<Type>,
871    pub interfaces: Vec<Arc<str>>,
872    /// Type arguments from `@implements Interface<T1, T2>`.
873    #[serde(default)]
874    pub implements_type_args: Vec<(Arc<str>, Vec<Type>)>,
875    pub cases: MemberMap<EnumCaseDef>,
876    pub own_methods: MemberMap<Arc<MethodDef>>,
877    pub own_constants: MemberMap<ConstantDef>,
878    /// `use SomeTrait;` declarations. PHP enums may use traits (for methods),
879    /// just never carry instance properties from them.
880    #[serde(default)]
881    pub traits: Vec<Arc<str>>,
882    #[serde(default)]
883    pub trait_use_locations: Vec<(Arc<str>, Location)>,
884    /// Type arguments from `@use SomeTrait<T1, T2>`.
885    #[serde(default)]
886    pub trait_use_type_args: Vec<(Arc<str>, Vec<Type>)>,
887    pub location: Option<Location>,
888    /// `@deprecated` docblock annotation (or `#[Deprecated]` attribute), if present.
889    #[serde(default)]
890    pub deprecated: Option<Arc<str>>,
891    /// Type aliases declared on this enum via `@psalm-type` / `@phpstan-type`.
892    #[serde(default)]
893    pub type_aliases: FxHashMap<Arc<str>, Type>,
894    /// Properties declared via `@property*` docblock annotations on the enum.
895    #[serde(default)]
896    pub own_properties: MemberMap<PropertyDef>,
897}
898
899// ---------------------------------------------------------------------------
900// FunctionDef
901// ---------------------------------------------------------------------------
902
903#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
904pub struct FunctionDef {
905    pub fqn: Arc<str>,
906    pub short_name: Arc<str>,
907    #[serde(
908        deserialize_with = "deserialize_params",
909        serialize_with = "serialize_params"
910    )]
911    pub params: Arc<[DeclaredParam]>,
912    /// Type from annotation (`@return` / native type hint). `None` means unannotated.
913    /// Stored as `Option<Arc<Type>>` to enable deduplication of common return types.
914    #[serde(
915        deserialize_with = "deserialize_return_type",
916        serialize_with = "serialize_return_type"
917    )]
918    pub return_type: Option<Arc<Type>>,
919    /// See `MethodDef::inferred_return_type` — `Option<Arc<Type>>` (8 B) for the
920    /// same demand-driven-inference reason.
921    #[serde(
922        deserialize_with = "deserialize_return_type",
923        serialize_with = "serialize_return_type"
924    )]
925    pub inferred_return_type: Option<Arc<Type>>,
926    pub template_params: Vec<TemplateParam>,
927    pub assertions: Vec<Assertion>,
928    pub throws: Vec<Arc<str>>,
929    pub deprecated: Option<Arc<str>>,
930    pub is_pure: bool,
931    /// `@psalm-mutation-free` / `@phpstan-mutation-free` on a free function.
932    /// A free function has no `$this`, so this is behaviorally equivalent to
933    /// `is_external_mutation_free` here (both forbid mutating a parameter);
934    /// see `MethodDef::is_mutation_free` for the method-level (has-`$this`)
935    /// distinction this mirrors.
936    #[serde(default)]
937    pub is_mutation_free: bool,
938    /// `@psalm-external-mutation-free` on a free function — must not mutate
939    /// any object passed as an argument.
940    #[serde(default)]
941    pub is_external_mutation_free: bool,
942    /// `@no-named-arguments` — callers must not use named argument syntax.
943    #[serde(default)]
944    pub no_named_arguments: bool,
945    pub location: Option<Location>,
946    /// Plain-text description from the docblock (text before `@tag` lines).
947    /// Used for hover info.
948    #[serde(default)]
949    pub docstring: Option<Arc<str>>,
950    /// Prose attached to `@param`, `@return` and `@throws` tags. `None` when
951    /// no tag carries a description.
952    #[serde(default)]
953    pub tag_descriptions: Option<Arc<TagDescriptions>>,
954    /// Parameters declared as taint sinks via `@taint-sink <kind> $param`.
955    /// Each entry is `(param_name_without_dollar, sink_kind_string)`.
956    #[serde(default)]
957    pub taint_sink_params: Vec<(Arc<str>, Arc<str>)>,
958    /// `@taint-source` — this function's return value is treated as tainted
959    /// (attacker-controlled) at every call site, mirroring `@taint-sink`'s
960    /// mechanism but marking the source side instead.
961    #[serde(default)]
962    pub is_taint_source: bool,
963    #[serde(default)]
964    pub taint: Option<Arc<TaintAnnotations>>,
965    /// Type aliases declared on this function via `@psalm-type` / `@phpstan-type`.
966    #[serde(default)]
967    pub type_aliases: FxHashMap<Arc<str>, Type>,
968}
969
970impl FunctionDef {
971    pub fn effective_return_type(&self) -> Option<&Type> {
972        self.return_type
973            .as_deref()
974            .or(self.inferred_return_type.as_deref())
975    }
976}
977
978// ---------------------------------------------------------------------------
979// StubSlice — serializable bundle of definitions from one extension's stubs
980// ---------------------------------------------------------------------------
981
982/// A `namespace` declaration's byte range with the names it resolves against.
983#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
984pub struct NamespaceBlock {
985    pub start: u32,
986    pub end: u32,
987    pub namespace: Option<Arc<str>>,
988    #[serde(
989        deserialize_with = "deserialize_imports",
990        serialize_with = "serialize_imports"
991    )]
992    pub class_imports: Arc<FxHashMap<Name, Name>>,
993    #[serde(
994        deserialize_with = "deserialize_imports",
995        serialize_with = "serialize_imports"
996    )]
997    pub function_imports: Arc<FxHashMap<Name, Name>>,
998    #[serde(
999        deserialize_with = "deserialize_imports",
1000        serialize_with = "serialize_imports"
1001    )]
1002    pub const_imports: Arc<FxHashMap<Name, Name>>,
1003}
1004
1005/// A snapshot of all PHP definitions contributed by a single stub file set.
1006///
1007/// Produced by `mir-stubs-gen` at code-generation time and deserialized at
1008/// runtime to ingest definitions into the salsa db via
1009/// `MirDatabase::ingest_stub_slice`.
1010#[derive(Debug, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)]
1011pub struct StubSlice {
1012    pub classes: Vec<Arc<ClassDef>>,
1013    pub interfaces: Vec<Arc<InterfaceDef>>,
1014    pub traits: Vec<Arc<TraitDef>>,
1015    pub enums: Vec<Arc<EnumDef>>,
1016    pub functions: Vec<Arc<FunctionDef>>,
1017    #[serde(default)]
1018    pub constants: Vec<(Arc<str>, Type)>,
1019    /// Source file this slice was collected from. `None` for bundled stub slices
1020    /// that were pre-computed and are not tied to a specific on-disk file.
1021    #[serde(default)]
1022    pub file: Option<Arc<str>>,
1023    /// Types of `@var`-annotated global variables collected from this file.
1024    /// Populated by `DefinitionCollector`; ingested into the salsa db's
1025    /// `global_vars` table by `ingest_stub_slice` when `file` is `Some`.
1026    #[serde(default)]
1027    pub global_vars: Vec<(Arc<str>, Type)>,
1028    /// The first namespace declared in this file (e.g. `"App\\Service"`).
1029    /// Populated by `DefinitionCollector`; ingested into the salsa db's
1030    /// `file_namespaces` table by `ingest_stub_slice` when `file` is `Some`.
1031    #[serde(default)]
1032    pub namespace: Option<Arc<str>>,
1033    /// `use` class-like aliases: alias → FQCN. Each `use` kind has its own map, as in PHP.
1034    #[serde(
1035        deserialize_with = "deserialize_imports",
1036        serialize_with = "serialize_imports"
1037    )]
1038    #[serde(default = "default_imports")]
1039    pub class_imports: Arc<FxHashMap<Name, Name>>,
1040    /// `use function` aliases: alias → function FQN.
1041    #[serde(
1042        deserialize_with = "deserialize_imports",
1043        serialize_with = "serialize_imports"
1044    )]
1045    #[serde(default = "default_imports")]
1046    pub function_imports: Arc<FxHashMap<Name, Name>>,
1047    /// `use const` aliases: alias → constant FQN.
1048    #[serde(
1049        deserialize_with = "deserialize_imports",
1050        serialize_with = "serialize_imports"
1051    )]
1052    #[serde(default = "default_imports")]
1053    pub const_imports: Arc<FxHashMap<Name, Name>>,
1054    /// Per-block namespace and imports; populated only when the file declares
1055    /// two or more `namespace` blocks, otherwise empty.
1056    #[serde(default)]
1057    pub namespace_blocks: Arc<[NamespaceBlock]>,
1058    /// Set to `true` after `deduplicate_params_in_slice` has run on this slice.
1059    /// `ingest_stub_slice` skips the clone+re-dedup when this flag is set.
1060    #[serde(skip)]
1061    pub is_deduped: bool,
1062}
1063
1064// ---------------------------------------------------------------------------
1065// Param list deduplication
1066// ---------------------------------------------------------------------------
1067
1068use std::sync::Mutex;
1069
1070type ParamCache = Mutex<FxHashMap<Vec<DeclaredParam>, Arc<[DeclaredParam]>>>;
1071
1072/// Global cache of canonical Arc<[DeclaredParam]> instances for deduplication.
1073/// Shared across all StubSlices to deduplicate vendor code with millions of
1074/// methods that often have identical parameter lists.
1075static PARAM_DEDUP_CACHE: std::sync::OnceLock<ParamCache> = std::sync::OnceLock::new();
1076
1077/// Deduplicate parameter lists across all methods and functions in a StubSlice.
1078/// Many PHP framework methods share identical parameter lists (e.g., thousands
1079/// of `(string $arg, array $opts)` signatures). This function groups identical
1080/// param lists globally (across all slices processed so far) and replaces them
1081/// with Arc<[DeclaredParam]> pointers to shared allocations.
1082///
1083/// Expected memory savings: 100–150 MiB on cold start (vendor collection).
1084pub fn deduplicate_params_in_slice(slice: &mut StubSlice) {
1085    let cache: &ParamCache = PARAM_DEDUP_CACHE.get_or_init(|| Mutex::new(FxHashMap::default()));
1086    let mut canonical_params = cache.lock().unwrap();
1087
1088    let mut deduplicate = |params: &mut Arc<[DeclaredParam]>| {
1089        if let Some(existing) = canonical_params.get(params.as_ref()) {
1090            *params = existing.clone();
1091        } else {
1092            canonical_params.insert(params.as_ref().to_vec(), params.clone());
1093        }
1094    };
1095
1096    // Deduplicate method params in all classes
1097    for cls in &mut slice.classes {
1098        for method in Arc::make_mut(cls).own_methods.values_mut() {
1099            deduplicate(&mut Arc::make_mut(method).params);
1100        }
1101    }
1102
1103    // Deduplicate method params in all interfaces
1104    for iface in &mut slice.interfaces {
1105        for method in Arc::make_mut(iface).own_methods.values_mut() {
1106            deduplicate(&mut Arc::make_mut(method).params);
1107        }
1108    }
1109
1110    // Deduplicate method params in all traits
1111    for tr in &mut slice.traits {
1112        for method in Arc::make_mut(tr).own_methods.values_mut() {
1113            deduplicate(&mut Arc::make_mut(method).params);
1114        }
1115    }
1116
1117    // Deduplicate method params in all enums
1118    for en in &mut slice.enums {
1119        for method in Arc::make_mut(en).own_methods.values_mut() {
1120            deduplicate(&mut Arc::make_mut(method).params);
1121        }
1122    }
1123
1124    // Deduplicate function params
1125    for func in &mut slice.functions {
1126        deduplicate(&mut Arc::make_mut(func).params);
1127    }
1128    slice.is_deduped = true;
1129}