1use std::borrow::Cow;
2
3use mago_atom::Atom;
4use mago_atom::ascii_lowercase_atom;
5
6use crate::get_class_like;
7use crate::get_closure;
8use crate::get_declaring_method;
9use crate::get_function;
10use crate::identifier::function_like::FunctionLikeIdentifier;
11use crate::metadata::CodebaseMetadata;
12use crate::metadata::function_like::FunctionLikeMetadata;
13use crate::ttype::TType;
14use crate::ttype::atomic::TAtomic;
15use crate::ttype::atomic::array::TArray;
16use crate::ttype::atomic::callable::TCallable;
17use crate::ttype::atomic::callable::TCallableSignature;
18use crate::ttype::atomic::callable::parameter::TCallableParameter;
19use crate::ttype::atomic::derived::TDerived;
20use crate::ttype::atomic::derived::key_of::TKeyOf;
21use crate::ttype::atomic::derived::value_of::TValueOf;
22use crate::ttype::atomic::mixed::TMixed;
23use crate::ttype::atomic::object::TObject;
24use crate::ttype::atomic::object::named::TNamedObject;
25use crate::ttype::atomic::reference::TReference;
26use crate::ttype::atomic::reference::TReferenceMemberSelector;
27use crate::ttype::atomic::scalar::TScalar;
28use crate::ttype::atomic::scalar::class_like_string::TClassLikeString;
29use crate::ttype::combiner;
30use crate::ttype::get_mixed;
31use crate::ttype::union::TUnion;
32
33#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
34pub enum StaticClassType {
35 #[default]
36 None,
37 Name(Atom),
38 Object(TObject),
39}
40
41#[derive(Debug)]
42pub struct TypeExpansionOptions {
43 pub self_class: Option<Atom>,
44 pub static_class_type: StaticClassType,
45 pub parent_class: Option<Atom>,
46 pub evaluate_class_constants: bool,
47 pub evaluate_conditional_types: bool,
48 pub function_is_final: bool,
49 pub expand_generic: bool,
50 pub expand_templates: bool,
51}
52
53impl Default for TypeExpansionOptions {
54 fn default() -> Self {
55 Self {
56 self_class: None,
57 static_class_type: StaticClassType::default(),
58 parent_class: None,
59 evaluate_class_constants: true,
60 evaluate_conditional_types: false,
61 function_is_final: false,
62 expand_generic: false,
63 expand_templates: true,
64 }
65 }
66}
67
68pub fn expand_union(codebase: &CodebaseMetadata, return_type: &mut TUnion, options: &TypeExpansionOptions) {
69 if !return_type.is_expandable() {
70 return;
71 }
72
73 let mut types = std::mem::take(&mut return_type.types).into_owned();
74
75 types = combiner::combine(types, codebase, false);
76
77 let mut new_return_type_parts = vec![];
78 let mut skipped_keys = vec![];
79
80 for (i, return_type_part) in types.iter_mut().enumerate() {
81 let mut skip_key = false;
82 expand_atomic(return_type_part, codebase, options, &mut skip_key, &mut new_return_type_parts);
83
84 if skip_key {
85 skipped_keys.push(i);
86 }
87 }
88
89 if !skipped_keys.is_empty() {
90 let mut i = 0;
91 types.retain(|_| {
92 let to_retain = !skipped_keys.contains(&i);
93 i += 1;
94 to_retain
95 });
96
97 new_return_type_parts.append(&mut types);
98
99 if new_return_type_parts.is_empty() {
100 new_return_type_parts.push(TAtomic::Mixed(TMixed::new()));
101 }
102
103 types = if new_return_type_parts.len() > 1 {
104 combiner::combine(new_return_type_parts, codebase, false)
105 } else {
106 new_return_type_parts
107 };
108 }
109
110 return_type.types = Cow::Owned(types);
111}
112
113pub(crate) fn expand_atomic(
114 return_type_part: &mut TAtomic,
115 codebase: &CodebaseMetadata,
116 options: &TypeExpansionOptions,
117 skip_key: &mut bool,
118 new_return_type_parts: &mut Vec<TAtomic>,
119) {
120 match return_type_part {
121 TAtomic::Array(array_type) => match array_type {
122 TArray::Keyed(keyed_data) => {
123 if let Some((key_parameter, value_parameter)) = &mut keyed_data.parameters {
124 expand_union(codebase, key_parameter, options);
125 expand_union(codebase, value_parameter, options);
126 }
127
128 if let Some(known_items) = &mut keyed_data.known_items {
129 for (_, item_type) in known_items.values_mut() {
130 expand_union(codebase, item_type, options);
131 }
132 }
133 }
134 TArray::List(list_data) => {
135 expand_union(codebase, &mut list_data.element_type, options);
136
137 if let Some(known_elements) = &mut list_data.known_elements {
138 for (_, element_type) in known_elements.values_mut() {
139 expand_union(codebase, element_type, options);
140 }
141 }
142 }
143 },
144 TAtomic::Object(TObject::Named(named_object)) => {
145 expand_named_object(named_object, codebase, options);
146 }
147 TAtomic::Callable(TCallable::Signature(signature)) => {
148 if let Some(return_type) = signature.get_return_type_mut() {
149 expand_union(codebase, return_type, options);
150 }
151
152 for param in signature.get_parameters_mut() {
153 if let Some(param_type) = param.get_type_signature_mut() {
154 expand_union(codebase, param_type, options);
155 }
156 }
157 }
158 TAtomic::GenericParameter(parameter) => {
159 expand_union(codebase, parameter.constraint.as_mut(), options);
160 }
161 TAtomic::Scalar(TScalar::ClassLikeString(TClassLikeString::OfType { constraint, .. })) => {
162 let mut atomic_return_type_parts = vec![];
163 expand_atomic(constraint, codebase, options, &mut false, &mut atomic_return_type_parts);
164
165 if !atomic_return_type_parts.is_empty() {
166 *constraint = Box::new(atomic_return_type_parts.remove(0));
167 }
168 }
169 TAtomic::Reference(TReference::Member { class_like_name, member_selector }) => {
170 *skip_key = true;
171
172 match member_selector {
173 TReferenceMemberSelector::Wildcard => {
174 let Some(class_like) = get_class_like(codebase, class_like_name) else {
175 new_return_type_parts.push(TAtomic::Mixed(TMixed::new()));
176
177 return;
178 };
179
180 for constant in class_like.constants.values() {
181 if let Some(inferred_type) = constant.inferred_type.as_ref() {
182 let mut inferred_type = inferred_type.clone();
183
184 let mut skip_inferred_type = false;
185 expand_atomic(
186 &mut inferred_type,
187 codebase,
188 options,
189 &mut skip_inferred_type,
190 new_return_type_parts,
191 );
192
193 if !skip_inferred_type {
194 new_return_type_parts.push(inferred_type);
195 }
196 } else if let Some(type_metadata) = constant.type_metadata.as_ref() {
197 let mut constant_type = type_metadata.type_union.clone();
198
199 expand_union(codebase, &mut constant_type, options);
200
201 new_return_type_parts.extend(constant_type.types.into_owned());
202 } else {
203 new_return_type_parts.push(TAtomic::Mixed(TMixed::new()));
204 }
205 }
206
207 for enum_case_name in class_like.enum_cases.keys() {
208 new_return_type_parts
209 .push(TAtomic::Object(TObject::new_enum_case(class_like.original_name, *enum_case_name)));
210 }
211 }
212 TReferenceMemberSelector::StartsWith(prefix) => {
213 let Some(class_like) = get_class_like(codebase, class_like_name) else {
214 new_return_type_parts.push(TAtomic::Mixed(TMixed::new()));
215
216 return;
217 };
218
219 for (constant_name, constant) in class_like.constants.iter() {
220 if !constant_name.starts_with(prefix.as_str()) {
221 continue;
222 }
223
224 if let Some(inferred_type) = constant.inferred_type.as_ref() {
225 let mut inferred_type = inferred_type.clone();
226
227 let mut skip_inferred_type = false;
228 expand_atomic(
229 &mut inferred_type,
230 codebase,
231 options,
232 &mut skip_inferred_type,
233 new_return_type_parts,
234 );
235
236 if !skip_inferred_type {
237 new_return_type_parts.push(inferred_type);
238 }
239 } else if let Some(type_metadata) = constant.type_metadata.as_ref() {
240 let mut constant_type = type_metadata.type_union.clone();
241
242 expand_union(codebase, &mut constant_type, options);
243
244 new_return_type_parts.extend(constant_type.types.into_owned());
245 } else {
246 new_return_type_parts.push(TAtomic::Mixed(TMixed::new()));
247 }
248 }
249
250 for enum_case_name in class_like.enum_cases.keys() {
251 if !enum_case_name.starts_with(prefix.as_str()) {
252 continue;
253 }
254
255 new_return_type_parts
256 .push(TAtomic::Object(TObject::new_enum_case(class_like.original_name, *enum_case_name)));
257 }
258 }
259 TReferenceMemberSelector::EndsWith(suffix) => {
260 let Some(class_like) = get_class_like(codebase, class_like_name) else {
261 new_return_type_parts.push(TAtomic::Mixed(TMixed::new()));
262
263 return;
264 };
265
266 for (constant_name, constant) in class_like.constants.iter() {
267 if !constant_name.ends_with(suffix.as_str()) {
268 continue;
269 }
270
271 if let Some(inferred_type) = constant.inferred_type.as_ref() {
272 let mut inferred_type = inferred_type.clone();
273
274 let mut skip_inferred_type = false;
275 expand_atomic(
276 &mut inferred_type,
277 codebase,
278 options,
279 &mut skip_inferred_type,
280 new_return_type_parts,
281 );
282
283 if !skip_inferred_type {
284 new_return_type_parts.push(inferred_type);
285 }
286 } else if let Some(type_metadata) = constant.type_metadata.as_ref() {
287 let mut constant_type = type_metadata.type_union.clone();
288
289 expand_union(codebase, &mut constant_type, options);
290
291 new_return_type_parts.extend(constant_type.types.into_owned());
292 } else {
293 new_return_type_parts.push(TAtomic::Mixed(TMixed::new()));
294 }
295 }
296
297 for enum_case_name in class_like.enum_cases.keys() {
298 if !enum_case_name.ends_with(suffix.as_str()) {
299 continue;
300 }
301
302 new_return_type_parts
303 .push(TAtomic::Object(TObject::new_enum_case(class_like.original_name, *enum_case_name)));
304 }
305 }
306 TReferenceMemberSelector::Identifier(member_name) => {
307 let Some(class_like) = get_class_like(codebase, class_like_name) else {
308 new_return_type_parts.push(TAtomic::Mixed(TMixed::new()));
309
310 return;
311 };
312
313 if class_like.enum_cases.contains_key(member_name) {
314 new_return_type_parts
315 .push(TAtomic::Object(TObject::new_enum_case(class_like.original_name, *member_name)));
316 } else if let Some(constant) = class_like.constants.get(member_name) {
317 if let Some(inferred_type) = constant.inferred_type.as_ref() {
318 let mut inferred_type = inferred_type.clone();
319
320 let mut skip_inferred_type = false;
321 expand_atomic(
322 &mut inferred_type,
323 codebase,
324 options,
325 &mut skip_inferred_type,
326 new_return_type_parts,
327 );
328
329 if !skip_inferred_type {
330 new_return_type_parts.push(inferred_type);
331 }
332 } else if let Some(type_metadata) = constant.type_metadata.as_ref() {
333 let mut constant_type = type_metadata.type_union.clone();
334
335 expand_union(codebase, &mut constant_type, options);
336
337 new_return_type_parts.extend(constant_type.types.into_owned());
338 } else {
339 new_return_type_parts.push(TAtomic::Mixed(TMixed::new()));
340 }
341 } else {
342 new_return_type_parts.push(TAtomic::Mixed(TMixed::new()));
343 }
344 }
345 }
346 }
347 TAtomic::Callable(TCallable::Alias(id)) => {
348 if let Some(value) = get_atomic_of_function_like_identifier(id, codebase) {
349 *skip_key = true;
350 new_return_type_parts.push(value);
351 }
352 }
353 TAtomic::Conditional(conditional) => {
354 *skip_key = true;
355
356 let mut then = conditional.then.clone();
357 let mut otherwise = conditional.otherwise.clone();
358
359 expand_union(codebase, &mut then, options);
360 expand_union(codebase, &mut otherwise, options);
361
362 new_return_type_parts.extend(then.types.into_owned());
363 new_return_type_parts.extend(otherwise.types.into_owned());
364 }
365 TAtomic::Derived(derived) => match derived {
366 TDerived::KeyOf(key_of) => {
367 *skip_key = true;
368 new_return_type_parts.extend(expand_key_of(key_of, codebase, options));
369 }
370 TDerived::ValueOf(value_of) => {
371 *skip_key = true;
372 new_return_type_parts.extend(expand_value_of(value_of, codebase, options));
373 }
374 TDerived::PropertiesOf(_) => todo!("expand_properties_of"),
375 },
376 _ => {}
377 }
378}
379
380fn expand_named_object(named_object: &mut TNamedObject, codebase: &CodebaseMetadata, options: &TypeExpansionOptions) {
381 let name_str_lc = ascii_lowercase_atom(named_object.name.as_str());
382
383 if named_object.is_this() || name_str_lc == "static" || name_str_lc == "$this" {
384 match &options.static_class_type {
385 StaticClassType::Object(TObject::Named(static_object)) => {
386 if let Some(static_object_intersections) = &static_object.intersection_types {
387 let intersections = named_object.intersection_types.get_or_insert_with(Vec::new);
388 intersections.extend(static_object_intersections.iter().cloned());
389 }
390
391 if named_object.type_parameters.is_none() {
392 named_object.type_parameters = static_object.type_parameters.clone();
393 }
394
395 named_object.name = static_object.name;
396 named_object.is_this = true;
397 }
398 StaticClassType::Name(static_class_name) => {
399 named_object.name = *static_class_name;
400 named_object.is_this = options.function_is_final;
401 }
402 _ => {}
403 }
404 } else if name_str_lc == "self" {
405 if let Some(self_class_name) = options.self_class {
406 named_object.name = self_class_name;
407 }
408 } else if name_str_lc == "parent"
409 && let Some(self_class_name) = options.self_class
410 && let Some(class_metadata) = get_class_like(codebase, &self_class_name)
411 && let Some(parent_name) = class_metadata.direct_parent_class
412 {
413 named_object.name = parent_name;
414 }
415
416 if named_object.type_parameters.is_none()
417 && let Some(class_like_metadata) = get_class_like(codebase, &named_object.name)
418 && !class_like_metadata.template_types.is_empty()
419 {
420 let default_params: Vec<TUnion> = class_like_metadata
421 .template_types
422 .iter()
423 .map(|(_, template_map)| template_map.iter().map(|(_, t)| t).next().cloned().unwrap_or_else(get_mixed))
424 .collect();
425
426 if !default_params.is_empty() {
427 named_object.type_parameters = Some(default_params);
428 }
429 }
430}
431
432pub fn get_signature_of_function_like_identifier(
433 function_like_identifier: &FunctionLikeIdentifier,
434 codebase: &CodebaseMetadata,
435) -> Option<TCallableSignature> {
436 Some(match function_like_identifier {
437 FunctionLikeIdentifier::Function(name) => {
438 let function_like_metadata = get_function(codebase, name)?;
439
440 get_signature_of_function_like_metadata(
441 function_like_identifier,
442 function_like_metadata,
443 codebase,
444 &TypeExpansionOptions::default(),
445 )
446 }
447 FunctionLikeIdentifier::Closure(file_id, position) => {
448 let function_like_metadata = get_closure(codebase, file_id, position)?;
449
450 get_signature_of_function_like_metadata(
451 function_like_identifier,
452 function_like_metadata,
453 codebase,
454 &TypeExpansionOptions::default(),
455 )
456 }
457 FunctionLikeIdentifier::Method(classlike_name, method_name) => {
458 let function_like_metadata = get_declaring_method(codebase, classlike_name, method_name)?;
459
460 get_signature_of_function_like_metadata(
461 function_like_identifier,
462 function_like_metadata,
463 codebase,
464 &TypeExpansionOptions {
465 self_class: Some(*classlike_name),
466 static_class_type: StaticClassType::Name(*classlike_name),
467 ..Default::default()
468 },
469 )
470 }
471 })
472}
473
474pub fn get_atomic_of_function_like_identifier(
475 function_like_identifier: &FunctionLikeIdentifier,
476 codebase: &CodebaseMetadata,
477) -> Option<TAtomic> {
478 let signature = get_signature_of_function_like_identifier(function_like_identifier, codebase)?;
479
480 Some(TAtomic::Callable(TCallable::Signature(signature)))
481}
482
483pub fn get_signature_of_function_like_metadata(
484 function_like_identifier: &FunctionLikeIdentifier,
485 function_like_metadata: &FunctionLikeMetadata,
486 codebase: &CodebaseMetadata,
487 options: &TypeExpansionOptions,
488) -> TCallableSignature {
489 let parameters: Vec<_> = function_like_metadata
490 .parameters
491 .iter()
492 .map(|parameter_metadata| {
493 let type_signature = if let Some(t) = parameter_metadata.get_type_metadata() {
494 let mut t = t.type_union.clone();
495 expand_union(codebase, &mut t, options);
496 Some(Box::new(t))
497 } else {
498 None
499 };
500
501 TCallableParameter::new(
502 type_signature,
503 parameter_metadata.flags.is_by_reference(),
504 parameter_metadata.flags.is_variadic(),
505 parameter_metadata.flags.has_default(),
506 )
507 })
508 .collect();
509
510 let return_type = if let Some(type_metadata) = function_like_metadata.return_type_metadata.as_ref() {
511 let mut return_type = type_metadata.type_union.clone();
512 expand_union(codebase, &mut return_type, options);
513 Some(Box::new(return_type))
514 } else {
515 None
516 };
517
518 let mut signature = TCallableSignature::new(function_like_metadata.flags.is_pure(), true)
519 .with_parameters(parameters)
520 .with_return_type(return_type)
521 .with_source(Some(*function_like_identifier));
522
523 if let FunctionLikeIdentifier::Closure(file_id, closure_position) = function_like_identifier {
524 signature = signature.with_closure_location(Some((*file_id, *closure_position)));
525 }
526
527 signature
528}
529
530fn expand_key_of(
531 return_type_key_of: &TKeyOf,
532 codebase: &CodebaseMetadata,
533 options: &TypeExpansionOptions,
534) -> Vec<TAtomic> {
535 let mut type_atomics = vec![];
536
537 let mut target_type = return_type_key_of.get_target_type().clone();
538 let mut new_atomics = vec![];
539 let mut remove_target_atomic = false;
540 expand_atomic(&mut target_type, codebase, options, &mut remove_target_atomic, &mut new_atomics);
541 type_atomics.extend(new_atomics);
542 if !remove_target_atomic {
543 type_atomics.push(target_type);
544 }
545
546 let Some(new_return_types) = TKeyOf::get_key_of_targets(&type_atomics, codebase, false) else {
547 return vec![TAtomic::Derived(TDerived::KeyOf(return_type_key_of.clone()))];
548 };
549
550 new_return_types.types.into_owned()
551}
552
553fn expand_value_of(
554 return_type_value_of: &TValueOf,
555 codebase: &CodebaseMetadata,
556 options: &TypeExpansionOptions,
557) -> Vec<TAtomic> {
558 let mut type_atomics = vec![];
559
560 let mut target_type = return_type_value_of.get_target_type().clone();
561 let mut new_atomics = vec![];
562 let mut remove_target_atomic = false;
563 expand_atomic(&mut target_type, codebase, options, &mut remove_target_atomic, &mut new_atomics);
564 type_atomics.extend(new_atomics);
565 if !remove_target_atomic {
566 type_atomics.push(target_type);
567 }
568
569 let Some(new_return_types) = TValueOf::get_value_of_targets(&type_atomics, codebase, false) else {
570 return vec![TAtomic::Derived(TDerived::ValueOf(return_type_value_of.clone()))];
571 };
572
573 new_return_types.types.into_owned()
574}