Skip to main content

mago_codex/metadata/
class_like.rs

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