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