mago_codex/metadata/
mod.rs1use std::collections::hash_map::Entry;
2
3use ahash::HashMap;
4use ahash::HashSet;
5use serde::Deserialize;
6use serde::Serialize;
7
8use mago_atom::Atom;
9use mago_atom::AtomMap;
10use mago_atom::AtomSet;
11use mago_reporting::IssueCollection;
12
13use crate::get_closure;
14use crate::get_function;
15use crate::get_method;
16use crate::identifier::function_like::FunctionLikeIdentifier;
17use crate::identifier::method::MethodIdentifier;
18use crate::metadata::class_like::ClassLikeMetadata;
19use crate::metadata::constant::ConstantMetadata;
20use crate::metadata::function_like::FunctionLikeMetadata;
21use crate::metadata::property::PropertyMetadata;
22use crate::metadata::ttype::TypeMetadata;
23use crate::symbol::SymbolKind;
24use crate::symbol::Symbols;
25use crate::ttype::atomic::TAtomic;
26use crate::ttype::union::TUnion;
27
28pub mod attribute;
29pub mod class_like;
30pub mod class_like_constant;
31pub mod constant;
32pub mod enum_case;
33pub mod flags;
34pub mod function_like;
35pub mod parameter;
36pub mod property;
37pub mod ttype;
38
39#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
45pub struct CodebaseMetadata {
46 pub infer_types_from_usage: bool,
48 pub aliases: AtomMap<TypeMetadata>,
50 pub class_likes: AtomMap<ClassLikeMetadata>,
52 pub function_likes: HashMap<(Atom, Atom), FunctionLikeMetadata>,
55 pub symbols: Symbols,
57 pub constants: AtomMap<ConstantMetadata>,
59 pub all_class_like_descendants: AtomMap<AtomSet>,
61 pub direct_classlike_descendants: AtomMap<AtomSet>,
63 pub safe_symbols: AtomSet,
65 pub safe_symbol_members: HashSet<(Atom, Atom)>,
67}
68
69impl CodebaseMetadata {
70 #[inline]
72 pub fn new() -> Self {
73 Self::default()
74 }
75
76 #[inline]
79 pub fn is_inheritable(&self, fq_class_name: &Atom) -> bool {
80 match self.symbols.get_kind(fq_class_name) {
81 Some(SymbolKind::Class) => {
82 self.class_likes.get(fq_class_name).is_some_and(|meta| !meta.flags.is_final())
84 }
85 Some(SymbolKind::Enum) => {
86 false
88 }
89 Some(SymbolKind::Interface) | Some(SymbolKind::Trait) | None => {
90 true
92 }
93 }
94 }
95
96 #[inline]
97 pub fn class_or_trait_can_use_trait(&self, child_class: &Atom, parent_trait: &Atom) -> bool {
98 if let Some(metadata) = self.class_likes.get(child_class) {
99 if metadata.used_traits.contains(parent_trait) {
100 return true;
101 }
102
103 return metadata.used_traits.contains(parent_trait);
104 }
105 false
106 }
107
108 #[inline]
111 pub fn get_classconst_literal_value(&self, fq_class_name: &Atom, const_name: &Atom) -> Option<&TAtomic> {
112 self.class_likes
113 .get(fq_class_name)
114 .and_then(|class_metadata| class_metadata.constants.get(const_name))
115 .and_then(|constant_metadata| constant_metadata.inferred_type.as_ref())
116 }
117
118 #[inline]
121 pub fn property_exists(&self, classlike_name: &Atom, property_name: &Atom) -> bool {
122 self.class_likes
123 .get(classlike_name)
124 .is_some_and(|metadata| metadata.appearing_property_ids.contains_key(property_name))
125 }
126
127 #[inline]
130 pub fn method_exists(&self, classlike_name: &Atom, method_name: &Atom) -> bool {
131 self.class_likes.get(classlike_name).is_some_and(|metadata| metadata.methods.contains(method_name))
132 }
133
134 #[inline]
137 pub fn appearing_method_exists(&self, classlike_name: &Atom, method_name: &Atom) -> bool {
138 self.class_likes.get(classlike_name).is_some_and(|metadata| metadata.has_appearing_method(method_name))
139 }
140
141 #[inline]
143 pub fn declaring_method_exists(&self, classlike_name: &Atom, method_name: &Atom) -> bool {
144 self.class_likes.get(classlike_name).and_then(|metadata| metadata.declaring_method_ids.get(method_name))
145 == Some(classlike_name) }
147
148 #[inline]
151 pub fn get_declaring_class_for_property(&self, fq_class_name: &Atom, property_name: &Atom) -> Option<&Atom> {
152 self.class_likes.get(fq_class_name).and_then(|metadata| metadata.declaring_property_ids.get(property_name))
153 }
154
155 #[inline]
159 pub fn get_property_metadata(&self, fq_class_name: &Atom, property_name: &Atom) -> Option<&PropertyMetadata> {
160 let appearing_class_fqcn =
162 self.class_likes.get(fq_class_name).and_then(|meta| meta.appearing_property_ids.get(property_name)); appearing_class_fqcn
166 .and_then(|fqcn| self.class_likes.get(fqcn))
167 .and_then(|meta| meta.properties.get(property_name))
168 }
169
170 #[inline]
174 pub fn get_property_type(&self, fq_class_name: &Atom, property_name: &Atom) -> Option<&TUnion> {
175 let declaring_class_fqcn = self.get_declaring_class_for_property(fq_class_name, property_name)?;
177 let property_metadata = self.class_likes.get(declaring_class_fqcn)?.properties.get(property_name)?;
179
180 property_metadata.type_metadata.as_ref().map(|tm| &tm.type_union)
182 }
183
184 #[inline]
187 pub fn get_appearing_method_id(&self, method_id: &MethodIdentifier) -> MethodIdentifier {
188 self.class_likes
189 .get(method_id.get_class_name())
190 .and_then(|metadata| metadata.appearing_method_ids.get(method_id.get_method_name()))
191 .map_or(*method_id, |appearing_fqcn| MethodIdentifier::new(*appearing_fqcn, *method_id.get_method_name()))
192 }
193
194 #[inline]
196 pub fn get_function_like(&self, identifier: &FunctionLikeIdentifier) -> Option<&FunctionLikeMetadata> {
197 match identifier {
198 FunctionLikeIdentifier::Function(fq_function_name) => get_function(self, fq_function_name),
199 FunctionLikeIdentifier::Method(fq_classlike_name, method_name) => {
200 get_method(self, fq_classlike_name, method_name)
201 }
202 FunctionLikeIdentifier::Closure(file_id, position) => get_closure(self, file_id, position),
203 }
204 }
205
206 #[inline]
209 pub fn extend(&mut self, other: CodebaseMetadata) {
210 for (k, v) in other.aliases {
211 self.aliases.entry(k).or_insert(v);
212 }
213
214 for (k, v) in other.class_likes {
216 let metadata_to_keep = match self.class_likes.entry(k) {
217 Entry::Occupied(entry) => {
218 let existing_metadata = entry.remove();
219
220 if v.flags.is_user_defined() {
221 v
222 } else if existing_metadata.flags.is_user_defined() {
223 existing_metadata
224 } else if v.flags.is_built_in() {
225 v
226 } else if existing_metadata.flags.is_built_in() {
227 existing_metadata
228 } else {
229 v
230 }
231 }
232 Entry::Vacant(_) => v,
233 };
234 self.class_likes.insert(k, metadata_to_keep);
235 }
236
237 for (k, v) in other.function_likes {
238 let metadata_to_keep = match self.function_likes.entry(k) {
239 Entry::Occupied(entry) => {
240 let existing_metadata = entry.remove();
241
242 if v.flags.is_user_defined() {
243 v
244 } else if existing_metadata.flags.is_user_defined() {
245 existing_metadata
246 } else if v.flags.is_built_in() {
247 v
248 } else if existing_metadata.flags.is_built_in() {
249 existing_metadata
250 } else {
251 v
252 }
253 }
254 Entry::Vacant(_) => v,
255 };
256 self.function_likes.insert(k, metadata_to_keep);
257 }
258
259 for (k, v) in other.constants {
260 let metadata_to_keep = match self.constants.entry(k) {
261 Entry::Occupied(entry) => {
262 let existing_metadata = entry.remove();
263
264 if v.flags.is_user_defined() {
265 v
266 } else if existing_metadata.flags.is_user_defined() {
267 existing_metadata
268 } else if v.flags.is_built_in() {
269 v
270 } else if existing_metadata.flags.is_built_in() {
271 existing_metadata
272 } else {
273 v
274 }
275 }
276 Entry::Vacant(_) => v,
277 };
278 self.constants.insert(k, metadata_to_keep);
279 }
280
281 self.symbols.extend(other.symbols);
282
283 for (k, v) in other.all_class_like_descendants {
284 self.all_class_like_descendants.entry(k).or_default().extend(v);
285 }
286
287 for (k, v) in other.direct_classlike_descendants {
288 self.direct_classlike_descendants.entry(k).or_default().extend(v);
289 }
290
291 self.safe_symbols.extend(other.safe_symbols);
292 self.safe_symbol_members.extend(other.safe_symbol_members);
293 self.infer_types_from_usage |= other.infer_types_from_usage;
294 }
295
296 pub fn take_issues(&mut self, user_defined: bool) -> IssueCollection {
297 let mut issues = IssueCollection::new();
298
299 for metadata in self.class_likes.values_mut() {
300 if user_defined && !metadata.flags.is_user_defined() {
301 continue;
302 }
303
304 issues.extend(metadata.take_issues());
305 }
306
307 for metadata in self.function_likes.values_mut() {
308 if user_defined && !metadata.flags.is_user_defined() {
309 continue;
310 }
311
312 issues.extend(metadata.take_issues());
313 }
314
315 for metadata in self.constants.values_mut() {
316 if user_defined && !metadata.flags.is_user_defined() {
317 continue;
318 }
319
320 issues.extend(metadata.take_issues());
321 }
322
323 issues
324 }
325}
326
327impl Default for CodebaseMetadata {
329 #[inline]
330 fn default() -> Self {
331 Self {
332 class_likes: AtomMap::default(),
333 aliases: AtomMap::default(),
334 function_likes: HashMap::default(),
335 symbols: Symbols::new(),
336 infer_types_from_usage: false,
337 constants: AtomMap::default(),
338 all_class_like_descendants: AtomMap::default(),
339 direct_classlike_descendants: AtomMap::default(),
340 safe_symbols: AtomSet::default(),
341 safe_symbol_members: HashSet::default(),
342 }
343 }
344}