Skip to main content

mago_codex/metadata/
class_like.rs

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