1use std::collections::hash_map::Entry;
2
3use ahash::HashMap;
4use ahash::HashSet;
5use serde::Deserialize;
6use serde::Serialize;
7
8use mago_interner::StringIdentifier;
9use mago_interner::ThreadedInterner;
10use mago_reporting::IssueCollection;
11
12use crate::get_closure;
13use crate::get_function;
14use crate::get_method;
15use crate::identifier::function_like::FunctionLikeIdentifier;
16use crate::identifier::method::MethodIdentifier;
17use crate::metadata::class_like::ClassLikeMetadata;
18use crate::metadata::constant::ConstantMetadata;
19use crate::metadata::function_like::FunctionLikeMetadata;
20use crate::metadata::property::PropertyMetadata;
21use crate::metadata::ttype::TypeMetadata;
22use crate::symbol::SymbolKind;
23use crate::symbol::Symbols;
24use crate::ttype::atomic::TAtomic;
25use crate::ttype::union::TUnion;
26
27pub mod attribute;
28pub mod class_like;
29pub mod class_like_constant;
30pub mod constant;
31pub mod enum_case;
32pub mod function_like;
33pub mod parameter;
34pub mod property;
35pub mod ttype;
36
37#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
43pub struct CodebaseMetadata {
44 pub infer_types_from_usage: bool,
46 pub aliases: HashMap<StringIdentifier, TypeMetadata>,
48 pub class_likes: HashMap<StringIdentifier, ClassLikeMetadata>,
50 pub function_likes: HashMap<(StringIdentifier, StringIdentifier), FunctionLikeMetadata>,
53 pub symbols: Symbols,
55 pub constants: HashMap<StringIdentifier, ConstantMetadata>,
57 pub all_class_like_descendants: HashMap<StringIdentifier, HashSet<StringIdentifier>>,
59 pub direct_classlike_descendants: HashMap<StringIdentifier, HashSet<StringIdentifier>>,
61 pub safe_symbols: HashSet<StringIdentifier>,
63 pub safe_symbol_members: HashSet<(StringIdentifier, StringIdentifier)>,
65}
66
67impl CodebaseMetadata {
68 #[inline]
70 pub fn new() -> Self {
71 Self::default()
72 }
73
74 #[inline]
77 pub fn is_inheritable(&self, fq_class_name: &StringIdentifier) -> bool {
78 match self.symbols.get_kind(fq_class_name) {
79 Some(SymbolKind::Class) => {
80 self.class_likes.get(fq_class_name).is_some_and(|meta| !meta.is_final)
82 }
83 Some(SymbolKind::Enum) => {
84 false
86 }
87 Some(SymbolKind::Interface) | Some(SymbolKind::Trait) | None => {
88 true
90 }
91 }
92 }
93
94 #[inline]
95 pub fn class_or_trait_can_use_trait(
96 &self,
97 child_class: &StringIdentifier,
98 parent_trait: &StringIdentifier,
99 ) -> bool {
100 if let Some(metadata) = self.class_likes.get(child_class) {
101 if metadata.used_traits.contains(parent_trait) {
102 return true;
103 }
104
105 return metadata.used_traits.contains(parent_trait);
106 }
107 false
108 }
109
110 #[inline]
113 pub fn get_classconst_literal_value(
114 &self,
115 fq_class_name: &StringIdentifier,
116 const_name: &StringIdentifier,
117 ) -> Option<&TAtomic> {
118 self.class_likes
119 .get(fq_class_name)
120 .and_then(|class_metadata| class_metadata.constants.get(const_name))
121 .and_then(|constant_metadata| constant_metadata.inferred_type.as_ref())
122 }
123
124 #[inline]
127 pub fn property_exists(&self, classlike_name: &StringIdentifier, property_name: &StringIdentifier) -> bool {
128 self.class_likes
129 .get(classlike_name)
130 .is_some_and(|metadata| metadata.appearing_property_ids.contains_key(property_name))
131 }
132
133 #[inline]
136 pub fn method_exists(&self, classlike_name: &StringIdentifier, method_name: &StringIdentifier) -> bool {
137 self.class_likes.get(classlike_name).is_some_and(|metadata| metadata.has_method(method_name))
138 }
139
140 #[inline]
143 pub fn appearing_method_exists(&self, classlike_name: &StringIdentifier, method_name: &StringIdentifier) -> bool {
144 self.class_likes.get(classlike_name).is_some_and(|metadata| metadata.has_appearing_method(method_name))
145 }
146
147 #[inline]
149 pub fn declaring_method_exists(&self, classlike_name: &StringIdentifier, method_name: &StringIdentifier) -> bool {
150 self.class_likes.get(classlike_name).and_then(|metadata| metadata.get_declaring_method_ids().get(method_name))
151 == Some(classlike_name) }
153
154 #[inline]
157 pub fn get_declaring_class_for_property(
158 &self,
159 fq_class_name: &StringIdentifier,
160 property_name: &StringIdentifier,
161 ) -> Option<&StringIdentifier> {
162 self.class_likes.get(fq_class_name).and_then(|metadata| metadata.declaring_property_ids.get(property_name))
163 }
164
165 #[inline]
169 pub fn get_property_metadata(
170 &self,
171 fq_class_name: &StringIdentifier,
172 property_name: &StringIdentifier,
173 ) -> Option<&PropertyMetadata> {
174 let appearing_class_fqcn =
176 self.class_likes.get(fq_class_name).and_then(|meta| meta.appearing_property_ids.get(property_name)); appearing_class_fqcn
180 .and_then(|fqcn| self.class_likes.get(fqcn))
181 .and_then(|meta| meta.properties.get(property_name))
182 }
183
184 #[inline]
188 pub fn get_property_type(
189 &self,
190 fq_class_name: &StringIdentifier,
191 property_name: &StringIdentifier,
192 ) -> Option<&TUnion> {
193 let declaring_class_fqcn = self.get_declaring_class_for_property(fq_class_name, property_name)?;
195 let property_metadata = self.class_likes.get(declaring_class_fqcn)?.properties.get(property_name)?;
197
198 property_metadata.type_metadata.as_ref().map(|tm| &tm.type_union)
200 }
201
202 #[inline]
205 pub fn get_appearing_method_id(&self, method_id: &MethodIdentifier) -> MethodIdentifier {
206 self.class_likes
207 .get(method_id.get_class_name()) .and_then(|metadata| metadata.get_appearing_method_ids().get(method_id.get_method_name()))
209 .map_or(*method_id, |appearing_fqcn| MethodIdentifier::new(*appearing_fqcn, *method_id.get_method_name()))
210 }
211
212 #[inline]
214 pub fn get_function_like(
215 &self,
216 identifier: &FunctionLikeIdentifier,
217 interner: &ThreadedInterner,
218 ) -> Option<&FunctionLikeMetadata> {
219 match identifier {
220 FunctionLikeIdentifier::Function(fq_function_name) => get_function(self, interner, fq_function_name),
221 FunctionLikeIdentifier::Method(fq_classlike_name, method_name) => {
222 get_method(self, interner, fq_classlike_name, method_name)
223 }
224 FunctionLikeIdentifier::Closure(position) => get_closure(self, interner, position),
225 }
226 }
227
228 #[inline]
231 pub fn extend(&mut self, other: CodebaseMetadata) {
232 for (k, v) in other.aliases {
233 self.aliases.entry(k).or_insert(v);
234 }
235
236 for (k, v) in other.class_likes {
238 let metadata_to_keep = match self.class_likes.entry(k) {
239 Entry::Occupied(entry) => {
240 let existing_metadata = entry.remove();
241 let existing_category = existing_metadata.span.start.source.category();
242 let new_category = v.span.start.source.category();
243
244 if new_category.is_user_defined() {
245 v
246 } else if existing_category.is_user_defined() {
247 existing_metadata
248 } else if new_category.is_built_in() {
249 v
250 } else if existing_category.is_built_in() {
251 existing_metadata
252 } else {
253 v
254 }
255 }
256 Entry::Vacant(_) => v,
257 };
258 self.class_likes.insert(k, metadata_to_keep);
259 }
260
261 for (k, v) in other.function_likes {
262 let metadata_to_keep = match self.function_likes.entry(k) {
263 Entry::Occupied(entry) => {
264 let existing_metadata = entry.remove();
265 let existing_category = existing_metadata.span.start.source.category();
266 let new_category = v.span.start.source.category();
267
268 if new_category.is_user_defined() {
269 v
270 } else if existing_category.is_user_defined() {
271 existing_metadata
272 } else if new_category.is_built_in() {
273 v
274 } else if existing_category.is_built_in() {
275 existing_metadata
276 } else {
277 v
278 }
279 }
280 Entry::Vacant(_) => v,
281 };
282 self.function_likes.insert(k, metadata_to_keep);
283 }
284
285 for (k, v) in other.constants {
286 let metadata_to_keep = match self.constants.entry(k) {
287 Entry::Occupied(entry) => {
288 let existing_metadata = entry.remove();
289 let existing_category = existing_metadata.span.start.source.category();
290 let new_category = v.span.start.source.category();
291
292 if new_category.is_user_defined() {
293 v
294 } else if existing_category.is_user_defined() {
295 existing_metadata
296 } else if new_category.is_built_in() {
297 v
298 } else if existing_category.is_built_in() {
299 existing_metadata
300 } else {
301 v
302 }
303 }
304 Entry::Vacant(_) => v,
305 };
306 self.constants.insert(k, metadata_to_keep);
307 }
308
309 self.symbols.extend(other.symbols);
310
311 for (k, v) in other.all_class_like_descendants {
312 self.all_class_like_descendants.entry(k).or_default().extend(v);
313 }
314
315 for (k, v) in other.direct_classlike_descendants {
316 self.direct_classlike_descendants.entry(k).or_default().extend(v);
317 }
318
319 self.safe_symbols.extend(other.safe_symbols);
320 self.safe_symbol_members.extend(other.safe_symbol_members);
321 self.infer_types_from_usage |= other.infer_types_from_usage;
322 }
323
324 pub fn take_issues(&mut self, user_defined: bool) -> IssueCollection {
325 let mut issues = IssueCollection::new();
326
327 for metadata in self.class_likes.values_mut() {
328 if user_defined && !metadata.is_user_defined() {
329 continue;
330 }
331
332 issues.extend(metadata.take_issues());
333 }
334
335 for metadata in self.function_likes.values_mut() {
336 if user_defined && !metadata.user_defined {
337 continue;
338 }
339
340 issues.extend(metadata.take_issues());
341 }
342
343 for metadata in self.constants.values_mut() {
344 if user_defined && !metadata.user_defined {
345 continue;
346 }
347
348 issues.extend(metadata.take_issues());
349 }
350
351 issues
352 }
353}
354
355impl Default for CodebaseMetadata {
357 #[inline]
358 fn default() -> Self {
359 Self {
360 class_likes: HashMap::default(),
361 aliases: HashMap::default(),
362 function_likes: HashMap::default(),
363 symbols: Symbols::new(),
364 infer_types_from_usage: false,
365 constants: HashMap::default(),
366 all_class_like_descendants: HashMap::default(),
367 direct_classlike_descendants: HashMap::default(),
368 safe_symbols: HashSet::default(),
369 safe_symbol_members: HashSet::default(),
370 }
371 }
372}