mago_codex/metadata/
class_like.rs

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