Skip to main content

mago_codex/metadata/
class_like.rs

1use foldhash::fast::RandomState;
2use indexmap::IndexMap;
3use serde::Deserialize;
4use serde::Serialize;
5
6use mago_atom::Atom;
7use mago_atom::AtomMap;
8use mago_atom::AtomSet;
9use mago_reporting::Issue;
10use mago_span::Span;
11
12use crate::flags::attribute::AttributeFlags;
13use crate::identifier::method::MethodIdentifier;
14use crate::metadata::attribute::AttributeMetadata;
15use crate::metadata::class_like_constant::ClassLikeConstantMetadata;
16use crate::metadata::enum_case::EnumCaseMetadata;
17use crate::metadata::flags::MetadataFlags;
18use crate::metadata::property::PropertyMetadata;
19use crate::metadata::ttype::TypeMetadata;
20use crate::symbol::SymbolKind;
21use crate::ttype::atomic::TAtomic;
22use crate::ttype::template::GenericTemplate;
23use crate::ttype::template::variance::Variance;
24use crate::ttype::union::TUnion;
25use crate::visibility::Visibility;
26
27/// Type alias for template types stored in metadata.
28/// Maps template parameter names to their defining entity and constraint type.
29pub type TemplateTypes = IndexMap<Atom, GenericTemplate, RandomState>;
30
31/// Contains comprehensive metadata for a PHP class-like structure (class, interface, trait, enum).
32///
33/// Aggregates information about inheritance, traits, generics, methods, properties, constants,
34/// attributes, docblock tags, analysis flags, and more.
35#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
36#[non_exhaustive]
37pub struct ClassLikeMetadata {
38    pub name: Atom,
39    pub original_name: Atom,
40    pub span: Span,
41    pub direct_parent_interfaces: AtomSet,
42    pub all_parent_interfaces: AtomSet,
43    pub direct_parent_class: Option<Atom>,
44    pub require_extends: AtomSet,
45    pub require_implements: AtomSet,
46    pub all_parent_classes: AtomSet,
47    pub used_traits: AtomSet,
48    pub trait_alias_map: AtomMap<Atom>,
49    pub trait_visibility_map: AtomMap<Visibility>,
50    pub trait_final_map: AtomSet,
51    pub child_class_likes: Option<AtomSet>,
52    pub name_span: Option<Span>,
53    pub kind: SymbolKind,
54    pub template_types: TemplateTypes,
55    pub template_readonly: AtomSet,
56    pub template_variance: Vec<Variance>,
57    pub template_extended_offsets: AtomMap<Vec<TUnion>>,
58    pub template_extended_parameters: AtomMap<IndexMap<Atom, TUnion, RandomState>>,
59    pub template_type_extends_count: AtomMap<usize>,
60    pub template_type_implements_count: AtomMap<usize>,
61    pub template_type_uses_count: AtomMap<usize>,
62    pub methods: AtomSet,
63    pub pseudo_methods: AtomSet,
64    pub static_pseudo_methods: AtomSet,
65    pub declaring_method_ids: AtomMap<MethodIdentifier>,
66    pub appearing_method_ids: AtomMap<MethodIdentifier>,
67    pub inheritable_method_ids: AtomMap<MethodIdentifier>,
68    pub overridden_method_ids: AtomMap<IndexMap<Atom, MethodIdentifier, RandomState>>,
69    pub properties: AtomMap<PropertyMetadata>,
70    pub appearing_property_ids: AtomMap<Atom>,
71    pub declaring_property_ids: AtomMap<Atom>,
72    pub inheritable_property_ids: AtomMap<Atom>,
73    pub overridden_property_ids: AtomMap<AtomSet>,
74    pub initialized_properties: AtomSet,
75    pub constants: AtomMap<ClassLikeConstantMetadata>,
76    pub trait_constant_ids: AtomMap<Atom>,
77    pub enum_cases: AtomMap<EnumCaseMetadata>,
78    pub invalid_dependencies: AtomSet,
79    pub attributes: Vec<AttributeMetadata>,
80    pub enum_type: Option<TAtomic>,
81    pub has_sealed_methods: Option<bool>,
82    pub has_sealed_properties: Option<bool>,
83    pub permitted_inheritors: Option<AtomSet>,
84    pub issues: Vec<Issue>,
85    pub attribute_flags: Option<AttributeFlags>,
86    pub flags: MetadataFlags,
87    pub type_aliases: AtomMap<TypeMetadata>,
88    /// Imported type aliases in the form of (`from_fqcn`, `type_name`, span)
89    pub imported_type_aliases: AtomMap<(Atom, Atom, Span)>,
90    /// Mixin types from @mixin annotations - these types' methods/properties
91    /// can be accessed via magic methods (__call, __get, __set, __callStatic)
92    pub mixins: Vec<TUnion>,
93}
94
95impl ClassLikeMetadata {
96    #[must_use]
97    pub fn new(
98        name: Atom,
99        original_name: Atom,
100        span: Span,
101        name_span: Option<Span>,
102        flags: MetadataFlags,
103    ) -> ClassLikeMetadata {
104        ClassLikeMetadata {
105            constants: AtomMap::default(),
106            trait_constant_ids: AtomMap::default(),
107            enum_cases: AtomMap::default(),
108            flags,
109            kind: SymbolKind::Class,
110            direct_parent_interfaces: AtomSet::default(),
111            all_parent_classes: AtomSet::default(),
112            appearing_method_ids: AtomMap::default(),
113            attributes: Vec::new(),
114            all_parent_interfaces: AtomSet::default(),
115            declaring_method_ids: AtomMap::default(),
116            appearing_property_ids: AtomMap::default(),
117            declaring_property_ids: AtomMap::default(),
118            direct_parent_class: None,
119            require_extends: AtomSet::default(),
120            require_implements: AtomSet::default(),
121            inheritable_method_ids: AtomMap::default(),
122            enum_type: None,
123            inheritable_property_ids: AtomMap::default(),
124            initialized_properties: AtomSet::default(),
125            invalid_dependencies: AtomSet::default(),
126            span,
127            name_span,
128            methods: AtomSet::default(),
129            pseudo_methods: AtomSet::default(),
130            static_pseudo_methods: AtomSet::default(),
131            overridden_method_ids: AtomMap::default(),
132            overridden_property_ids: AtomMap::default(),
133            properties: AtomMap::default(),
134            template_variance: Vec::new(),
135            template_type_extends_count: AtomMap::default(),
136            template_extended_parameters: AtomMap::default(),
137            template_extended_offsets: AtomMap::default(),
138            template_type_implements_count: AtomMap::default(),
139            template_type_uses_count: AtomMap::default(),
140            template_types: TemplateTypes::default(),
141            used_traits: AtomSet::default(),
142            trait_alias_map: AtomMap::default(),
143            trait_visibility_map: AtomMap::default(),
144            trait_final_map: AtomSet::default(),
145            name,
146            original_name,
147            child_class_likes: None,
148            template_readonly: AtomSet::default(),
149            has_sealed_methods: None,
150            has_sealed_properties: None,
151            permitted_inheritors: None,
152            issues: vec![],
153            attribute_flags: None,
154            type_aliases: AtomMap::default(),
155            imported_type_aliases: AtomMap::default(),
156            mixins: Vec::default(),
157        }
158    }
159
160    /// Returns a reference to the map of trait method aliases.
161    #[inline]
162    #[must_use]
163    pub fn get_trait_alias_map(&self) -> &AtomMap<Atom> {
164        &self.trait_alias_map
165    }
166
167    /// Returns a vector of the generic type parameter names.
168    #[inline]
169    #[must_use]
170    pub fn get_template_type_names(&self) -> Vec<Atom> {
171        self.template_types.keys().copied().collect()
172    }
173
174    /// Returns type parameters for a specific generic parameter name.
175    #[inline]
176    #[must_use]
177    pub fn get_template_type(&self, name: Atom) -> Option<&GenericTemplate> {
178        self.template_types.get(&name)
179    }
180
181    /// Returns type parameters for a specific generic parameter name with its index.
182    #[inline]
183    #[must_use]
184    pub fn get_template_type_with_index(&self, name: Atom) -> Option<(usize, &GenericTemplate)> {
185        self.template_types.get_full(&name).map(|(index, _, types)| (index, types))
186    }
187
188    #[must_use]
189    pub fn get_template_for_index(&self, index: usize) -> Option<(Atom, &GenericTemplate)> {
190        self.template_types.get_index(index).map(|(name, types)| (*name, types))
191    }
192
193    #[must_use]
194    pub fn get_template_name_for_index(&self, index: usize) -> Option<Atom> {
195        self.template_types.get_index(index).map(|(name, _)| *name)
196    }
197
198    #[must_use]
199    pub fn get_template_index_for_name(&self, name: Atom) -> Option<usize> {
200        self.template_types.get_index_of(&name)
201    }
202
203    /// Checks if a specific parent is either a parent class or interface.
204    #[inline]
205    #[must_use]
206    pub fn has_parent(&self, parent: Atom) -> bool {
207        self.all_parent_classes.contains(&parent) || self.all_parent_interfaces.contains(&parent)
208    }
209
210    /// Checks if a specific parent has template extended parameters.
211    #[inline]
212    #[must_use]
213    pub fn has_template_extended_parameter(&self, parent: Atom) -> bool {
214        self.template_extended_parameters.contains_key(&parent)
215    }
216
217    /// Checks if a specific method appears in this class-like.
218    #[inline]
219    #[must_use]
220    pub fn has_appearing_method(&self, method: Atom) -> bool {
221        self.appearing_method_ids.contains_key(&method)
222    }
223
224    /// Returns a vector of property names.
225    #[inline]
226    #[must_use]
227    pub fn get_property_names(&self) -> AtomSet {
228        self.properties.keys().copied().collect()
229    }
230
231    /// Checks if a specific property appears in this class-like.
232    #[inline]
233    #[must_use]
234    pub fn has_appearing_property(&self, name: Atom) -> bool {
235        self.appearing_property_ids.contains_key(&name)
236    }
237
238    /// Checks if a specific property is declared in this class-like.
239    #[inline]
240    #[must_use]
241    pub fn has_declaring_property(&self, name: Atom) -> bool {
242        self.declaring_property_ids.contains_key(&name)
243    }
244
245    /// Takes ownership of the issues found for this class-like structure.
246    #[inline]
247    pub fn take_issues(&mut self) -> Vec<Issue> {
248        std::mem::take(&mut self.issues)
249    }
250
251    /// Adds a single direct parent interface.
252    #[inline]
253    pub fn add_direct_parent_interface(&mut self, interface: Atom) {
254        self.direct_parent_interfaces.insert(interface);
255        self.all_parent_interfaces.insert(interface);
256    }
257
258    /// Adds a single interface to the list of all parent interfaces. Use with caution, normally derived.
259    #[inline]
260    pub fn add_all_parent_interface(&mut self, interface: Atom) {
261        self.all_parent_interfaces.insert(interface);
262    }
263
264    /// Adds multiple interfaces to the list of all parent interfaces. Use with caution.
265    #[inline]
266    pub fn add_all_parent_interfaces(&mut self, interfaces: impl IntoIterator<Item = Atom>) {
267        self.all_parent_interfaces.extend(interfaces);
268    }
269
270    /// Adds multiple ancestor classes. Use with caution.
271    #[inline]
272    pub fn add_all_parent_classes(&mut self, classes: impl IntoIterator<Item = Atom>) {
273        self.all_parent_classes.extend(classes);
274    }
275
276    /// Adds a single used trait. Returns `true` if the trait was not already present.
277    #[inline]
278    pub fn add_used_trait(&mut self, trait_name: Atom) -> bool {
279        self.used_traits.insert(trait_name)
280    }
281
282    /// Adds multiple used traits.
283    #[inline]
284    pub fn add_used_traits(&mut self, traits: impl IntoIterator<Item = Atom>) {
285        self.used_traits.extend(traits);
286    }
287
288    /// Adds or updates a single trait alias. Returns the previous original name if one existed for the alias.
289    #[inline]
290    pub fn add_trait_alias(&mut self, method: Atom, alias: Atom) -> Option<Atom> {
291        self.trait_alias_map.insert(method, alias)
292    }
293
294    /// Adds or updates a single trait visibility override. Returns the previous visibility if one existed.
295    #[inline]
296    pub fn add_trait_visibility(&mut self, method: Atom, visibility: Visibility) -> Option<Visibility> {
297        self.trait_visibility_map.insert(method, visibility)
298    }
299
300    /// Adds a single template type definition.
301    #[inline]
302    pub fn add_template_type(&mut self, name: Atom, constraint: GenericTemplate) {
303        self.template_types.insert(name, constraint);
304    }
305
306    /// Set the variance for the template parameters
307    #[inline]
308    pub fn set_template_variance(&mut self, template_variance: Vec<Variance>) {
309        self.template_variance = template_variance;
310    }
311
312    /// Adds or replaces the offset types for a specific template parameter name.
313    #[inline]
314    pub fn add_template_extended_offset(&mut self, name: Atom, types: Vec<TUnion>) -> Option<Vec<TUnion>> {
315        self.template_extended_offsets.insert(name, types)
316    }
317
318    /// Adds or replaces the resolved parameters for a specific parent FQCN.
319    #[inline]
320    pub fn extend_template_extended_parameters(
321        &mut self,
322        template_extended_parameters: AtomMap<IndexMap<Atom, TUnion, RandomState>>,
323    ) {
324        self.template_extended_parameters.extend(template_extended_parameters);
325    }
326
327    /// Adds or replaces a single resolved parameter for the parent FQCN.
328    #[inline]
329    pub fn add_template_extended_parameter(
330        &mut self,
331        parent_fqcn: Atom,
332        parameter_name: Atom,
333        parameter_type: TUnion,
334    ) -> Option<TUnion> {
335        self.template_extended_parameters.entry(parent_fqcn).or_default().insert(parameter_name, parameter_type)
336    }
337
338    /// Adds or updates the declaring method identifier for a method name.
339    #[inline]
340    pub fn add_declaring_method_id(
341        &mut self,
342        method: Atom,
343        declaring_method_id: MethodIdentifier,
344    ) -> Option<MethodIdentifier> {
345        self.add_appearing_method_id(method, declaring_method_id);
346        self.declaring_method_ids.insert(method, declaring_method_id)
347    }
348
349    /// Adds or updates the appearing method identifier for a method name.
350    #[inline]
351    pub fn add_appearing_method_id(
352        &mut self,
353        method: Atom,
354        appearing_method_id: MethodIdentifier,
355    ) -> Option<MethodIdentifier> {
356        self.appearing_method_ids.insert(method, appearing_method_id)
357    }
358
359    /// Adds a parent method identifier to the map for an overridden method. Initializes map if needed. Returns the previous value if one existed.
360    #[inline]
361    pub fn add_overridden_method_parent(
362        &mut self,
363        method: Atom,
364        parent_method_id: MethodIdentifier,
365    ) -> Option<MethodIdentifier> {
366        self.overridden_method_ids
367            .entry(method)
368            .or_default()
369            .insert(parent_method_id.get_class_name(), parent_method_id)
370    }
371
372    /// Adds or updates a property's metadata. Returns the previous metadata if the property existed.
373    #[inline]
374    pub fn add_property(&mut self, name: Atom, property_metadata: PropertyMetadata) -> Option<PropertyMetadata> {
375        let class_name = self.name;
376
377        self.add_declaring_property_id(name, class_name);
378        if property_metadata.flags.has_default() {
379            self.initialized_properties.insert(name);
380        }
381
382        if !property_metadata.is_final() {
383            self.inheritable_property_ids.insert(name, class_name);
384        }
385
386        self.properties.insert(name, property_metadata)
387    }
388
389    /// Adds or updates a property's metadata using just the property metadata. Returns the previous metadata if the property existed.
390    #[inline]
391    pub fn add_property_metadata(&mut self, property_metadata: PropertyMetadata) -> Option<PropertyMetadata> {
392        let name = property_metadata.get_name().0;
393
394        self.add_property(name, property_metadata)
395    }
396
397    /// Adds or updates the declaring class FQCN for a property name.
398    #[inline]
399    pub fn add_declaring_property_id(&mut self, prop: Atom, declaring_fqcn: Atom) -> Option<Atom> {
400        self.appearing_property_ids.insert(prop, declaring_fqcn);
401        self.declaring_property_ids.insert(prop, declaring_fqcn)
402    }
403
404    #[must_use]
405    pub fn get_missing_required_interface<'a>(&self, other: &'a ClassLikeMetadata) -> Option<&'a Atom> {
406        for required_interface in &other.require_implements {
407            if self.all_parent_interfaces.contains(required_interface) {
408                continue;
409            }
410
411            if (self.flags.is_abstract() || self.kind.is_trait())
412                && self.require_implements.contains(required_interface)
413            {
414                continue; // Abstract classes and traits can require interfaces they implement
415            }
416
417            return Some(required_interface);
418        }
419
420        None
421    }
422
423    #[must_use]
424    pub fn get_missing_required_extends<'a>(&self, other: &'a ClassLikeMetadata) -> Option<&'a Atom> {
425        for required_extend in &other.require_extends {
426            if self.all_parent_classes.contains(required_extend) {
427                continue;
428            }
429
430            if self.kind.is_interface() && self.all_parent_interfaces.contains(required_extend) {
431                continue;
432            }
433
434            if (self.flags.is_abstract() || self.kind.is_trait()) && self.require_extends.contains(required_extend) {
435                continue; // Abstract classes and traits can require classes they extend
436            }
437
438            return Some(required_extend);
439        }
440
441        None
442    }
443
444    #[must_use]
445    pub fn is_permitted_to_inherit(&self, other: &ClassLikeMetadata) -> bool {
446        if self.kind.is_trait() || self.flags.is_abstract() {
447            return true; // Traits and abstract classes can always inherit
448        }
449
450        let Some(permitted_inheritors) = &other.permitted_inheritors else {
451            return true; // No restrictions, inheriting is allowed
452        };
453
454        if permitted_inheritors.contains(&self.name) {
455            return true; // This class-like is explicitly permitted to inherit
456        }
457
458        self.all_parent_interfaces.iter().any(|parent_interface| permitted_inheritors.contains(parent_interface))
459            || self.all_parent_classes.iter().any(|parent_class| permitted_inheritors.contains(parent_class))
460            || self.used_traits.iter().any(|used_trait| permitted_inheritors.contains(used_trait))
461    }
462
463    #[inline]
464    pub fn mark_as_populated(&mut self) {
465        self.flags |= MetadataFlags::POPULATED;
466        self.shrink_to_fit();
467    }
468
469    #[inline]
470    pub fn shrink_to_fit(&mut self) {
471        self.properties.shrink_to_fit();
472        self.initialized_properties.shrink_to_fit();
473        self.appearing_property_ids.shrink_to_fit();
474        self.declaring_property_ids.shrink_to_fit();
475        self.inheritable_property_ids.shrink_to_fit();
476        self.overridden_property_ids.shrink_to_fit();
477        self.appearing_method_ids.shrink_to_fit();
478        self.declaring_method_ids.shrink_to_fit();
479        self.inheritable_method_ids.shrink_to_fit();
480        self.overridden_method_ids.shrink_to_fit();
481        self.attributes.shrink_to_fit();
482        self.constants.shrink_to_fit();
483        self.enum_cases.shrink_to_fit();
484        self.type_aliases.shrink_to_fit();
485    }
486}