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