TypeExpander::expandAtomic()   F
last analyzed

Complexity

Conditions 99
Paths > 20000

Size

Total Lines 517

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 99
nc 1688008
nop 8
dl 0
loc 517
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity    Many Parameters   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
namespace Psalm\Internal\Type;
3
4
use Psalm\Codebase;
5
use Psalm\Type;
6
use Psalm\Type\Atomic\TTemplateParam;
7
use Psalm\Type\Atomic\TNamedObject;
8
use function strpos;
9
use function is_string;
10
use function strtolower;
11
use function count;
12
use function is_array;
13
use function array_merge;
14
use function array_values;
15
16
/**
17
 * @internal
18
 */
19
class TypeExpander
20
{
21
    /**
22
     * @param  Type\Union   $return_type
23
     * @param  string|null  $self_class
24
     * @param  string|Type\Atomic\TNamedObject|Type\Atomic\TTemplateParam|null $static_class_type
25
     *
26
     * @return Type\Union
27
     */
28
    public static function expandUnion(
29
        Codebase $codebase,
30
        Type\Union $return_type,
31
        ?string $self_class,
32
        $static_class_type,
33
        ?string $parent_class,
34
        bool $evaluate_class_constants = true,
35
        bool $evaluate_conditional_types = false,
36
        bool $final = false
37
    ) {
38
        $return_type = clone $return_type;
39
40
        $new_return_type_parts = [];
41
42
        foreach ($return_type->getAtomicTypes() as $return_type_part) {
43
            $parts = self::expandAtomic(
44
                $codebase,
45
                $return_type_part,
46
                $self_class,
47
                $static_class_type,
48
                $parent_class,
49
                $evaluate_class_constants,
50
                $evaluate_conditional_types,
51
                $final
52
            );
53
54
            if (is_array($parts)) {
55
                $new_return_type_parts = array_merge($new_return_type_parts, $parts);
56
            } else {
57
                $new_return_type_parts[] = $parts;
58
            }
59
        }
60
61
        $fleshed_out_type = new Type\Union($new_return_type_parts);
62
63
        $fleshed_out_type->from_docblock = $return_type->from_docblock;
64
        $fleshed_out_type->ignore_nullable_issues = $return_type->ignore_nullable_issues;
65
        $fleshed_out_type->ignore_falsable_issues = $return_type->ignore_falsable_issues;
66
        $fleshed_out_type->possibly_undefined = $return_type->possibly_undefined;
67
        $fleshed_out_type->by_ref = $return_type->by_ref;
68
        $fleshed_out_type->initialized = $return_type->initialized;
69
        $fleshed_out_type->had_template = $return_type->had_template;
70
        $fleshed_out_type->parent_nodes = $return_type->parent_nodes;
71
72
        return $fleshed_out_type;
73
    }
74
75
    /**
76
     * @param  Type\Atomic  &$return_type
77
     * @param  string|null  $self_class
78
     * @param  string|Type\Atomic\TNamedObject|Type\Atomic\TTemplateParam|null $static_class_type
79
     *
80
     * @return Type\Atomic|non-empty-array<int, Type\Atomic>
0 ignored issues
show
Documentation introduced by
The doc-type Type\Atomic|non-empty-array<int, could not be parsed: Unknown type name "non-empty-array" at position 12. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
81
     */
82
    public static function expandAtomic(
83
        Codebase $codebase,
84
        Type\Atomic &$return_type,
85
        ?string $self_class,
86
        $static_class_type,
87
        ?string $parent_class,
88
        bool $evaluate_class_constants = true,
89
        bool $evaluate_conditional_types = false,
90
        bool $final = false
91
    ) {
92
        if ($return_type instanceof TNamedObject
93
            || $return_type instanceof TTemplateParam
94
        ) {
95
            if ($return_type->extra_types) {
96
                $new_intersection_types = [];
97
98
                foreach ($return_type->extra_types as &$extra_type) {
99
                    self::expandAtomic(
100
                        $codebase,
101
                        $extra_type,
102
                        $self_class,
103
                        $static_class_type,
104
                        $parent_class,
105
                        $evaluate_class_constants,
106
                        $evaluate_conditional_types
107
                    );
108
109
                    if ($extra_type instanceof TNamedObject && $extra_type->extra_types) {
110
                        $new_intersection_types = array_merge(
111
                            $new_intersection_types,
112
                            $extra_type->extra_types
113
                        );
114
                        $extra_type->extra_types = [];
115
                    }
116
                }
117
118
                if ($new_intersection_types) {
119
                    $return_type->extra_types = array_merge($return_type->extra_types, $new_intersection_types);
120
                }
121
            }
122
123
            if ($return_type instanceof TNamedObject) {
124
                $return_type_lc = strtolower($return_type->value);
125
126
                if ($static_class_type && ($return_type_lc === 'static' || $return_type_lc === '$this')) {
127
                    if (is_string($static_class_type)) {
128
                        $return_type->value = $static_class_type;
129
                    } else {
130
                        if ($return_type instanceof Type\Atomic\TGenericObject
131
                            && $static_class_type instanceof Type\Atomic\TGenericObject
132
                        ) {
133
                            $return_type->value = $static_class_type->value;
134
                        } else {
135
                            $return_type = clone $static_class_type;
136
                        }
137
                    }
138
139
                    if (!$final && $return_type instanceof TNamedObject) {
140
                        $return_type->was_static = true;
141
                    }
142
                } elseif ($return_type->was_static
143
                    && ($static_class_type instanceof Type\Atomic\TNamedObject
144
                        || $static_class_type instanceof Type\Atomic\TTemplateParam)
145
                ) {
146
                    $return_type = clone $return_type;
147
                    $cloned_static = clone $static_class_type;
148
                    $extra_static = $cloned_static->extra_types ?: [];
149
                    $cloned_static->extra_types = null;
150
151
                    if ($cloned_static->getKey(false) !== $return_type->getKey(false)) {
152
                        $return_type->extra_types[$static_class_type->getKey()] = clone $cloned_static;
153
                    }
154
155
                    foreach ($extra_static as $extra_static_type) {
156
                        if ($extra_static_type->getKey(false) !== $return_type->getKey(false)) {
157
                            $return_type->extra_types[$extra_static_type->getKey()] = clone $extra_static_type;
158
                        }
159
                    }
160
                } elseif ($self_class && $return_type_lc === 'self') {
161
                    $return_type->value = $self_class;
162
                } elseif ($parent_class && $return_type_lc === 'parent') {
163
                    $return_type->value = $parent_class;
164
                } else {
165
                    $return_type->value = $codebase->classlikes->getUnAliasedName($return_type->value);
166
                }
167
            }
168
        }
169
170
        if ($return_type instanceof Type\Atomic\TClassString
171
            && $return_type->as_type
172
        ) {
173
            $new_as_type = clone $return_type->as_type;
174
175
            self::expandAtomic(
176
                $codebase,
177
                $new_as_type,
178
                $self_class,
179
                $static_class_type,
180
                $parent_class,
181
                $evaluate_class_constants,
182
                $evaluate_conditional_types,
183
                $final
184
            );
185
186
            if ($new_as_type instanceof TNamedObject) {
187
                $return_type->as_type = $new_as_type;
188
                $return_type->as = $return_type->as_type->value;
189
            }
190
        } elseif ($return_type instanceof Type\Atomic\TTemplateParam) {
191
            $new_as_type = self::expandUnion(
192
                $codebase,
193
                clone $return_type->as,
194
                $self_class,
195
                $static_class_type,
196
                $parent_class,
197
                $evaluate_class_constants,
198
                $evaluate_conditional_types,
199
                $final
200
            );
201
202
            $return_type->as = $new_as_type;
203
        }
204
205
        if ($return_type instanceof Type\Atomic\TScalarClassConstant) {
206
            if ($return_type->fq_classlike_name === 'self' && $self_class) {
207
                $return_type->fq_classlike_name = $self_class;
208
            }
209
210
            if ($evaluate_class_constants && $codebase->classOrInterfaceExists($return_type->fq_classlike_name)) {
211
                if (strtolower($return_type->const_name) === 'class') {
212
                    return new Type\Atomic\TLiteralClassString($return_type->fq_classlike_name);
213
                }
214
215
                if (strpos($return_type->const_name, '*') !== false) {
216
                    $class_storage = $codebase->classlike_storage_provider->get($return_type->fq_classlike_name);
217
218
                    $all_class_constants = $class_storage->public_class_constants
219
                        + $class_storage->protected_class_constants
220
                        + $class_storage->private_class_constants
221
                        + $class_storage->public_class_constant_nodes
222
                        + $class_storage->protected_class_constant_nodes
223
                        + $class_storage->private_class_constant_nodes;
224
225
                    $matching_constants = \array_keys($all_class_constants);
226
227
                    $const_name_part = \substr($return_type->const_name, 0, -1);
228
229
                    if ($const_name_part) {
230
                        $matching_constants = \array_filter(
231
                            $matching_constants,
232
                            function ($constant_name) use ($const_name_part) {
233
                                return $constant_name !== $const_name_part
234
                                    && \strpos($constant_name, $const_name_part) === 0;
235
                            }
236
                        );
237
                    }
238
                } else {
239
                    $matching_constants = [$return_type->const_name];
240
                }
241
242
                $matching_constant_types = [];
243
244
                foreach ($matching_constants as $matching_constant) {
245
                    try {
246
                        $class_constant = $codebase->classlikes->getConstantForClass(
247
                            $return_type->fq_classlike_name,
248
                            $matching_constant,
249
                            \ReflectionProperty::IS_PRIVATE
250
                        );
251
                    } catch (\Psalm\Exception\CircularReferenceException $e) {
252
                        $class_constant = null;
253
                    }
254
255
                    if ($class_constant) {
256
                        if ($class_constant->isSingle()) {
257
                            $class_constant = clone $class_constant;
258
259
                            $matching_constant_types = \array_merge(
260
                                \array_values($class_constant->getAtomicTypes()),
261
                                $matching_constant_types
262
                            );
263
                        }
264
                    }
265
                }
266
267
                if ($matching_constant_types) {
268
                    return $matching_constant_types;
269
                }
270
            }
271
272
            return $return_type;
273
        }
274
275
        if ($return_type instanceof Type\Atomic\TTypeAlias) {
276
            $declaring_fq_classlike_name = $return_type->declaring_fq_classlike_name;
277
278
            if ($declaring_fq_classlike_name === 'self' && $self_class) {
279
                $declaring_fq_classlike_name = $self_class;
280
            }
281
282
            if ($evaluate_class_constants && $codebase->classOrInterfaceExists($declaring_fq_classlike_name)) {
283
                $class_storage = $codebase->classlike_storage_provider->get($declaring_fq_classlike_name);
284
285
                $type_alias_name = $return_type->alias_name;
286
287
                if (isset($class_storage->type_aliases[$type_alias_name])) {
288
                    $resolved_type_alias = $class_storage->type_aliases[$type_alias_name];
289
290
                    if ($resolved_type_alias->replacement_atomic_types) {
291
                        $replacement_atomic_types = $resolved_type_alias->replacement_atomic_types;
292
293
                        $recursively_fleshed_out_types = [];
294
295
                        foreach ($replacement_atomic_types as $replacement_atomic_type) {
296
                            $recursively_fleshed_out_type = self::expandAtomic(
297
                                $codebase,
298
                                $replacement_atomic_type,
299
                                $self_class,
300
                                $static_class_type,
301
                                $parent_class,
302
                                $evaluate_class_constants,
303
                                $evaluate_conditional_types
304
                            );
305
306
                            if (is_array($recursively_fleshed_out_type)) {
307
                                $recursively_fleshed_out_types = array_merge(
308
                                    $recursively_fleshed_out_type,
309
                                    $recursively_fleshed_out_types
310
                                );
311
                            } else {
312
                                $recursively_fleshed_out_types[] = $recursively_fleshed_out_type;
313
                            }
314
                        }
315
316
                        return $recursively_fleshed_out_types;
317
                    }
318
                }
319
            }
320
321
            return $return_type;
322
        }
323
324
        if ($return_type instanceof Type\Atomic\TKeyOfClassConstant
325
            || $return_type instanceof Type\Atomic\TValueOfClassConstant
326
        ) {
327
            if ($return_type->fq_classlike_name === 'self' && $self_class) {
328
                $return_type->fq_classlike_name = $self_class;
329
            }
330
331
            if ($evaluate_class_constants && $codebase->classOrInterfaceExists($return_type->fq_classlike_name)) {
332
                try {
333
                    $class_constant_type = $codebase->classlikes->getConstantForClass(
334
                        $return_type->fq_classlike_name,
335
                        $return_type->const_name,
336
                        \ReflectionProperty::IS_PRIVATE
337
                    );
338
                } catch (\Psalm\Exception\CircularReferenceException $e) {
339
                    $class_constant_type = null;
340
                }
341
342
                if ($class_constant_type) {
343
                    foreach ($class_constant_type->getAtomicTypes() as $const_type_atomic) {
344
                        if ($const_type_atomic instanceof Type\Atomic\ObjectLike
345
                            || $const_type_atomic instanceof Type\Atomic\TArray
346
                        ) {
347
                            if ($const_type_atomic instanceof Type\Atomic\ObjectLike) {
348
                                $const_type_atomic = $const_type_atomic->getGenericArrayType();
349
                            }
350
351
                            if ($return_type instanceof Type\Atomic\TKeyOfClassConstant) {
352
                                return array_values($const_type_atomic->type_params[0]->getAtomicTypes());
353
                            }
354
355
                            return array_values($const_type_atomic->type_params[1]->getAtomicTypes());
356
                        }
357
                    }
358
                }
359
            }
360
361
            return $return_type;
362
        }
363
364
        if ($return_type instanceof Type\Atomic\TArray
365
            || $return_type instanceof Type\Atomic\TGenericObject
366
            || $return_type instanceof Type\Atomic\TIterable
367
        ) {
368
            foreach ($return_type->type_params as $k => $type_param) {
369
                /** @psalm-suppress PropertyTypeCoercion */
370
                $return_type->type_params[$k] = self::expandUnion(
371
                    $codebase,
372
                    $type_param,
373
                    $self_class,
374
                    $static_class_type,
375
                    $parent_class,
376
                    $evaluate_class_constants,
377
                    $evaluate_conditional_types,
378
                    $final
379
                );
380
            }
381
        } elseif ($return_type instanceof Type\Atomic\ObjectLike) {
382
            foreach ($return_type->properties as &$property_type) {
383
                $property_type = self::expandUnion(
384
                    $codebase,
385
                    $property_type,
386
                    $self_class,
387
                    $static_class_type,
388
                    $parent_class,
389
                    $evaluate_class_constants,
390
                    $evaluate_conditional_types,
391
                    $final
392
                );
393
            }
394
        } elseif ($return_type instanceof Type\Atomic\TList) {
395
            $return_type->type_param = self::expandUnion(
396
                $codebase,
397
                $return_type->type_param,
398
                $self_class,
399
                $static_class_type,
400
                $parent_class,
401
                $evaluate_class_constants,
402
                $evaluate_conditional_types,
403
                $final
404
            );
405
        }
406
407
        if ($return_type instanceof Type\Atomic\TCallable
408
            || $return_type instanceof Type\Atomic\TFn
409
        ) {
410
            if ($return_type->params) {
411
                foreach ($return_type->params as $param) {
412
                    if ($param->type) {
413
                        $param->type = self::expandUnion(
414
                            $codebase,
415
                            $param->type,
416
                            $self_class,
417
                            $static_class_type,
418
                            $parent_class,
419
                            $evaluate_class_constants,
420
                            $evaluate_conditional_types,
421
                            $final
422
                        );
423
                    }
424
                }
425
            }
426
            if ($return_type->return_type) {
427
                $return_type->return_type = self::expandUnion(
428
                    $codebase,
429
                    $return_type->return_type,
430
                    $self_class,
431
                    $static_class_type,
432
                    $parent_class,
433
                    $evaluate_class_constants,
434
                    $evaluate_conditional_types,
435
                    $final
436
                );
437
            }
438
        }
439
440
        if ($return_type instanceof Type\Atomic\TConditional) {
441
            if ($evaluate_conditional_types) {
442
                $assertion = null;
443
444
                if ($return_type->conditional_type->isSingle()) {
445
                    foreach ($return_type->conditional_type->getAtomicTypes() as $condition_atomic_type) {
446
                        $candidate = self::expandAtomic(
447
                            $codebase,
448
                            $condition_atomic_type,
449
                            $self_class,
450
                            $static_class_type,
451
                            $parent_class,
452
                            $evaluate_class_constants,
453
                            $evaluate_conditional_types,
454
                            $final
455
                        );
456
457
                        if (!is_array($candidate)) {
458
                            $assertion = $candidate->getAssertionString();
459
                        }
460
                    }
461
                }
462
463
                $if_conditional_return_types = [];
464
465
                foreach ($return_type->if_type->getAtomicTypes() as $if_atomic_type) {
466
                    $candidate = self::expandAtomic(
467
                        $codebase,
468
                        $if_atomic_type,
469
                        $self_class,
470
                        $static_class_type,
471
                        $parent_class,
472
                        $evaluate_class_constants,
473
                        $evaluate_conditional_types,
474
                        $final
475
                    );
476
477
                    $candidate_types = is_array($candidate) ? $candidate : [$candidate];
478
479
                    $if_conditional_return_types = array_merge(
480
                        $if_conditional_return_types,
481
                        $candidate_types
482
                    );
483
                }
484
485
                $else_conditional_return_types = [];
486
487
                foreach ($return_type->else_type->getAtomicTypes() as $else_atomic_type) {
488
                    $candidate = self::expandAtomic(
489
                        $codebase,
490
                        $else_atomic_type,
491
                        $self_class,
492
                        $static_class_type,
493
                        $parent_class,
494
                        $evaluate_class_constants,
495
                        $evaluate_conditional_types,
496
                        $final
497
                    );
498
499
                    $candidate_types = is_array($candidate) ? $candidate : [$candidate];
500
501
                    $else_conditional_return_types = array_merge(
502
                        $else_conditional_return_types,
503
                        $candidate_types
504
                    );
505
                }
506
507
                if ($assertion && $return_type->param_name === (string) $return_type->if_type) {
508
                    $if_conditional_return_type = TypeCombination::combineTypes(
509
                        $if_conditional_return_types,
510
                        $codebase
511
                    );
512
513
                    $if_conditional_return_type = \Psalm\Internal\Type\SimpleAssertionReconciler::reconcile(
514
                        $assertion,
515
                        $codebase,
516
                        $if_conditional_return_type
517
                    );
518
519
520
                    if ($if_conditional_return_type) {
521
                        $if_conditional_return_types = array_values($if_conditional_return_type->getAtomicTypes());
522
                    }
523
                }
524
525
                if ($assertion && $return_type->param_name === (string) $return_type->else_type) {
526
                    $else_conditional_return_type = TypeCombination::combineTypes(
527
                        $else_conditional_return_types,
528
                        $codebase
529
                    );
530
531
                    $else_conditional_return_type = \Psalm\Internal\Type\SimpleNegatedAssertionReconciler::reconcile(
532
                        $assertion,
533
                        $else_conditional_return_type
534
                    );
535
536
                    if ($else_conditional_return_type) {
537
                        $else_conditional_return_types = array_values($else_conditional_return_type->getAtomicTypes());
538
                    }
539
                }
540
541
                $all_conditional_return_types = array_merge(
542
                    $if_conditional_return_types,
543
                    $else_conditional_return_types
544
                );
545
546
                foreach ($all_conditional_return_types as $i => $conditional_return_type) {
547
                    if ($conditional_return_type instanceof Type\Atomic\TVoid
548
                        && count($all_conditional_return_types) > 1
549
                    ) {
550
                        $all_conditional_return_types[$i] = new Type\Atomic\TNull();
551
                        $all_conditional_return_types[$i]->from_docblock = true;
552
                    }
553
                }
554
555
                $combined = TypeCombination::combineTypes(
556
                    array_values($all_conditional_return_types),
557
                    $codebase
558
                );
559
560
                return array_values($combined->getAtomicTypes());
561
            }
562
563
            $return_type->conditional_type = self::expandUnion(
564
                $codebase,
565
                $return_type->conditional_type,
566
                $self_class,
567
                $static_class_type,
568
                $parent_class,
569
                $evaluate_class_constants,
570
                $evaluate_conditional_types,
571
                $final
572
            );
573
574
            $return_type->if_type = self::expandUnion(
575
                $codebase,
576
                $return_type->if_type,
577
                $self_class,
578
                $static_class_type,
579
                $parent_class,
580
                $evaluate_class_constants,
581
                $evaluate_conditional_types,
582
                $final
583
            );
584
585
            $return_type->else_type = self::expandUnion(
586
                $codebase,
587
                $return_type->else_type,
588
                $self_class,
589
                $static_class_type,
590
                $parent_class,
591
                $evaluate_class_constants,
592
                $evaluate_conditional_types,
593
                $final
594
            );
595
        }
596
597
        return $return_type;
598
    }
599
}
600