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 flags;
33pub mod function_like;
34pub mod parameter;
35pub mod property;
36pub mod ttype;
37
38#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
44pub struct CodebaseMetadata {
45 pub infer_types_from_usage: bool,
47 pub aliases: HashMap<StringIdentifier, TypeMetadata>,
49 pub class_likes: HashMap<StringIdentifier, ClassLikeMetadata>,
51 pub function_likes: HashMap<(StringIdentifier, StringIdentifier), FunctionLikeMetadata>,
54 pub symbols: Symbols,
56 pub constants: HashMap<StringIdentifier, ConstantMetadata>,
58 pub all_class_like_descendants: HashMap<StringIdentifier, HashSet<StringIdentifier>>,
60 pub direct_classlike_descendants: HashMap<StringIdentifier, HashSet<StringIdentifier>>,
62 pub safe_symbols: HashSet<StringIdentifier>,
64 pub safe_symbol_members: HashSet<(StringIdentifier, StringIdentifier)>,
66}
67
68impl CodebaseMetadata {
69 #[inline]
71 pub fn new() -> Self {
72 Self::default()
73 }
74
75 #[inline]
78 pub fn is_inheritable(&self, fq_class_name: &StringIdentifier) -> bool {
79 match self.symbols.get_kind(fq_class_name) {
80 Some(SymbolKind::Class) => {
81 self.class_likes.get(fq_class_name).is_some_and(|meta| !meta.flags.is_final())
83 }
84 Some(SymbolKind::Enum) => {
85 false
87 }
88 Some(SymbolKind::Interface) | Some(SymbolKind::Trait) | None => {
89 true
91 }
92 }
93 }
94
95 #[inline]
96 pub fn class_or_trait_can_use_trait(
97 &self,
98 child_class: &StringIdentifier,
99 parent_trait: &StringIdentifier,
100 ) -> bool {
101 if let Some(metadata) = self.class_likes.get(child_class) {
102 if metadata.used_traits.contains(parent_trait) {
103 return true;
104 }
105
106 return metadata.used_traits.contains(parent_trait);
107 }
108 false
109 }
110
111 #[inline]
114 pub fn get_classconst_literal_value(
115 &self,
116 fq_class_name: &StringIdentifier,
117 const_name: &StringIdentifier,
118 ) -> Option<&TAtomic> {
119 self.class_likes
120 .get(fq_class_name)
121 .and_then(|class_metadata| class_metadata.constants.get(const_name))
122 .and_then(|constant_metadata| constant_metadata.inferred_type.as_ref())
123 }
124
125 #[inline]
128 pub fn property_exists(&self, classlike_name: &StringIdentifier, property_name: &StringIdentifier) -> bool {
129 self.class_likes
130 .get(classlike_name)
131 .is_some_and(|metadata| metadata.appearing_property_ids.contains_key(property_name))
132 }
133
134 #[inline]
137 pub fn method_exists(&self, classlike_name: &StringIdentifier, method_name: &StringIdentifier) -> bool {
138 self.class_likes.get(classlike_name).is_some_and(|metadata| metadata.has_method(method_name))
139 }
140
141 #[inline]
144 pub fn appearing_method_exists(&self, classlike_name: &StringIdentifier, method_name: &StringIdentifier) -> bool {
145 self.class_likes.get(classlike_name).is_some_and(|metadata| metadata.has_appearing_method(method_name))
146 }
147
148 #[inline]
150 pub fn declaring_method_exists(&self, classlike_name: &StringIdentifier, method_name: &StringIdentifier) -> bool {
151 self.class_likes.get(classlike_name).and_then(|metadata| metadata.get_declaring_method_ids().get(method_name))
152 == Some(classlike_name) }
154
155 #[inline]
158 pub fn get_declaring_class_for_property(
159 &self,
160 fq_class_name: &StringIdentifier,
161 property_name: &StringIdentifier,
162 ) -> Option<&StringIdentifier> {
163 self.class_likes.get(fq_class_name).and_then(|metadata| metadata.declaring_property_ids.get(property_name))
164 }
165
166 #[inline]
170 pub fn get_property_metadata(
171 &self,
172 fq_class_name: &StringIdentifier,
173 property_name: &StringIdentifier,
174 ) -> Option<&PropertyMetadata> {
175 let appearing_class_fqcn =
177 self.class_likes.get(fq_class_name).and_then(|meta| meta.appearing_property_ids.get(property_name)); appearing_class_fqcn
181 .and_then(|fqcn| self.class_likes.get(fqcn))
182 .and_then(|meta| meta.properties.get(property_name))
183 }
184
185 #[inline]
189 pub fn get_property_type(
190 &self,
191 fq_class_name: &StringIdentifier,
192 property_name: &StringIdentifier,
193 ) -> Option<&TUnion> {
194 let declaring_class_fqcn = self.get_declaring_class_for_property(fq_class_name, property_name)?;
196 let property_metadata = self.class_likes.get(declaring_class_fqcn)?.properties.get(property_name)?;
198
199 property_metadata.type_metadata.as_ref().map(|tm| &tm.type_union)
201 }
202
203 #[inline]
206 pub fn get_appearing_method_id(&self, method_id: &MethodIdentifier) -> MethodIdentifier {
207 self.class_likes
208 .get(method_id.get_class_name()) .and_then(|metadata| metadata.get_appearing_method_ids().get(method_id.get_method_name()))
210 .map_or(*method_id, |appearing_fqcn| MethodIdentifier::new(*appearing_fqcn, *method_id.get_method_name()))
211 }
212
213 #[inline]
215 pub fn get_function_like(
216 &self,
217 identifier: &FunctionLikeIdentifier,
218 interner: &ThreadedInterner,
219 ) -> Option<&FunctionLikeMetadata> {
220 match identifier {
221 FunctionLikeIdentifier::Function(fq_function_name) => get_function(self, interner, fq_function_name),
222 FunctionLikeIdentifier::Method(fq_classlike_name, method_name) => {
223 get_method(self, interner, fq_classlike_name, method_name)
224 }
225 FunctionLikeIdentifier::Closure(position) => get_closure(self, interner, position),
226 }
227 }
228
229 #[inline]
232 pub fn extend(&mut self, other: CodebaseMetadata) {
233 for (k, v) in other.aliases {
234 self.aliases.entry(k).or_insert(v);
235 }
236
237 for (k, v) in other.class_likes {
239 let metadata_to_keep = match self.class_likes.entry(k) {
240 Entry::Occupied(entry) => {
241 let existing_metadata = entry.remove();
242
243 if v.flags.is_user_defined() {
244 v
245 } else if existing_metadata.flags.is_user_defined() {
246 existing_metadata
247 } else if v.flags.is_built_in() {
248 v
249 } else if existing_metadata.flags.is_built_in() {
250 existing_metadata
251 } else {
252 v
253 }
254 }
255 Entry::Vacant(_) => v,
256 };
257 self.class_likes.insert(k, metadata_to_keep);
258 }
259
260 for (k, v) in other.function_likes {
261 let metadata_to_keep = match self.function_likes.entry(k) {
262 Entry::Occupied(entry) => {
263 let existing_metadata = entry.remove();
264
265 if v.flags.is_user_defined() {
266 v
267 } else if existing_metadata.flags.is_user_defined() {
268 existing_metadata
269 } else if v.flags.is_built_in() {
270 v
271 } else if existing_metadata.flags.is_built_in() {
272 existing_metadata
273 } else {
274 v
275 }
276 }
277 Entry::Vacant(_) => v,
278 };
279 self.function_likes.insert(k, metadata_to_keep);
280 }
281
282 for (k, v) in other.constants {
283 let metadata_to_keep = match self.constants.entry(k) {
284 Entry::Occupied(entry) => {
285 let existing_metadata = entry.remove();
286
287 if v.flags.is_user_defined() {
288 v
289 } else if existing_metadata.flags.is_user_defined() {
290 existing_metadata
291 } else if v.flags.is_built_in() {
292 v
293 } else if existing_metadata.flags.is_built_in() {
294 existing_metadata
295 } else {
296 v
297 }
298 }
299 Entry::Vacant(_) => v,
300 };
301 self.constants.insert(k, metadata_to_keep);
302 }
303
304 self.symbols.extend(other.symbols);
305
306 for (k, v) in other.all_class_like_descendants {
307 self.all_class_like_descendants.entry(k).or_default().extend(v);
308 }
309
310 for (k, v) in other.direct_classlike_descendants {
311 self.direct_classlike_descendants.entry(k).or_default().extend(v);
312 }
313
314 self.safe_symbols.extend(other.safe_symbols);
315 self.safe_symbol_members.extend(other.safe_symbol_members);
316 self.infer_types_from_usage |= other.infer_types_from_usage;
317 }
318
319 pub fn take_issues(&mut self, user_defined: bool) -> IssueCollection {
320 let mut issues = IssueCollection::new();
321
322 for metadata in self.class_likes.values_mut() {
323 if user_defined && !metadata.flags.is_user_defined() {
324 continue;
325 }
326
327 issues.extend(metadata.take_issues());
328 }
329
330 for metadata in self.function_likes.values_mut() {
331 if user_defined && !metadata.flags.is_user_defined() {
332 continue;
333 }
334
335 issues.extend(metadata.take_issues());
336 }
337
338 for metadata in self.constants.values_mut() {
339 if user_defined && !metadata.flags.is_user_defined() {
340 continue;
341 }
342
343 issues.extend(metadata.take_issues());
344 }
345
346 issues
347 }
348}
349
350impl Default for CodebaseMetadata {
352 #[inline]
353 fn default() -> Self {
354 Self {
355 class_likes: HashMap::default(),
356 aliases: HashMap::default(),
357 function_likes: HashMap::default(),
358 symbols: Symbols::new(),
359 infer_types_from_usage: false,
360 constants: HashMap::default(),
361 all_class_like_descendants: HashMap::default(),
362 direct_classlike_descendants: HashMap::default(),
363 safe_symbols: HashSet::default(),
364 safe_symbol_members: HashSet::default(),
365 }
366 }
367}