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