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