TypeExpander   F
last analyzed

Complexity

Total Complexity 102

Size/Duplication

Total Lines 581
Duplicated Lines 0 %

Coupling/Cohesion

Components 0
Dependencies 21

Importance

Changes 0
Metric Value
dl 0
loc 581
rs 2
c 0
b 0
f 0
wmc 102
lcom 0
cbo 21

2 Methods

Rating   Name   Duplication   Size   Complexity  
A expandUnion() 0 46 3
F expandAtomic() 0 517 99

How to fix   Complexity   

Complex Class

Complex classes like TypeExpander often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use TypeExpander, and based on these observations, apply Extract Interface, too.

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