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