SimpleAssertionReconciler   F
last analyzed

Complexity

Total Complexity 423

Size/Duplication

Total Lines 2020
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 44

Importance

Changes 0
Metric Value
dl 0
loc 2020
rs 0.8
c 0
b 0
f 0
wmc 423
lcom 1
cbo 44

21 Methods

Rating   Name   Duplication   Size   Complexity  
F reconcile() 0 316 42
D reconcileNonEmptyCountable() 0 71 18
D reconcileHasMethod() 0 98 24
D reconcileString() 0 86 21
D reconcileInt() 0 87 21
C reconcileBool() 0 71 15
C reconcileScalar() 0 67 14
D reconcileNumeric() 0 84 19
C reconcileObject() 0 77 17
B reconcileResource() 0 50 11
C reconcileCountable() 0 62 14
C reconcileIterable() 0 53 12
B reconcileInArray() 0 39 8
B reconcileHasArrayKey() 0 27 6
C reconcileTraversable() 0 64 15
D reconcileArray() 0 70 18
F reconcileList() 0 83 23
C reconcileStringArrayAccess() 0 58 12
B reconcileIntArrayAccess() 0 53 10
D reconcileCallable() 0 90 23
F reconcileFalsyOrEmpty() 0 307 80

How to fix   Complexity   

Complex Class

Complex classes like SimpleAssertionReconciler 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 SimpleAssertionReconciler, and based on these observations, apply Extract Interface, too.

1
<?php
2
namespace Psalm\Internal\Type;
3
4
use function array_filter;
5
use function explode;
6
use function get_class;
7
use function is_string;
8
use Psalm\Codebase;
9
use Psalm\CodeLocation;
10
use Psalm\Internal\Analyzer\TypeAnalyzer;
11
use Psalm\Issue\ParadoxicalCondition;
12
use Psalm\Issue\RedundantCondition;
13
use Psalm\Issue\TypeDoesNotContainType;
14
use Psalm\IssueBuffer;
15
use Psalm\Type;
16
use Psalm\Type\Atomic;
17
use Psalm\Type\Union;
18
use Psalm\Type\Atomic\ObjectLike;
19
use Psalm\Type\Atomic\Scalar;
20
use Psalm\Type\Atomic\TArray;
21
use Psalm\Type\Atomic\TArrayKey;
22
use Psalm\Type\Atomic\TBool;
23
use Psalm\Type\Atomic\TCallable;
24
use Psalm\Type\Atomic\TCallableArray;
25
use Psalm\Type\Atomic\TCallableList;
26
use Psalm\Type\Atomic\TCallableObjectLikeArray;
27
use Psalm\Type\Atomic\TClassString;
28
use Psalm\Type\Atomic\TEmpty;
29
use Psalm\Type\Atomic\TFalse;
30
use Psalm\Type\Atomic\TInt;
31
use Psalm\Type\Atomic\TList;
32
use Psalm\Type\Atomic\TMixed;
33
use Psalm\Type\Atomic\TNamedObject;
34
use Psalm\Type\Atomic\TNonEmptyArray;
35
use Psalm\Type\Atomic\TNonEmptyList;
36
use Psalm\Type\Atomic\TNumeric;
37
use Psalm\Type\Atomic\TNumericString;
38
use Psalm\Type\Atomic\TObject;
39
use Psalm\Type\Atomic\TResource;
40
use Psalm\Type\Atomic\TScalar;
41
use Psalm\Type\Atomic\TString;
42
use Psalm\Type\Atomic\TTemplateParam;
43
use function strpos;
44
use function substr;
45
46
class SimpleAssertionReconciler extends \Psalm\Type\Reconciler
47
{
48
    /**
49
     * @param   string[]  $suppressed_issues
50
     * @param   0|1|2    $failed_reconciliation
0 ignored issues
show
Documentation introduced by
The doc-type 0|1|2 could not be parsed: Unknown type name "0" at position 0. (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...
51
     */
52
    public static function reconcile(
53
        string $assertion,
54
        Codebase $codebase,
55
        Union $existing_var_type,
56
        ?string $key = null,
57
        ?CodeLocation $code_location = null,
58
        array $suppressed_issues = [],
59
        int &$failed_reconciliation = 0,
60
        bool $is_equality = false,
61
        bool $is_strict_equality = false,
62
        bool $inside_loop = false
63
    ) : ?Type\Union {
64
        if ($assertion === 'mixed' && $existing_var_type->hasMixed()) {
65
            return $existing_var_type;
66
        }
67
68
        if ($assertion === 'isset') {
69
            $existing_var_type->removeType('null');
70
71
            if (empty($existing_var_type->getAtomicTypes())) {
72
                $failed_reconciliation = 2;
73
74
                if ($code_location) {
75
                    if (IssueBuffer::accepts(
76
                        new TypeDoesNotContainType(
77
                            'Cannot resolve types for ' . $key . ' on null var',
78
                            $code_location
79
                        ),
80
                        $suppressed_issues
81
                    )) {
82
                        // fall through
83
                    }
84
                }
85
86
                return Type::getEmpty();
87
            }
88
89
            if ($existing_var_type->hasType('empty')) {
90
                $existing_var_type->removeType('empty');
91
                $existing_var_type->addType(new TMixed($inside_loop));
92
            }
93
94
            $existing_var_type->possibly_undefined = false;
95
            $existing_var_type->possibly_undefined_from_try = false;
96
97
            return $existing_var_type;
98
        }
99
100
        if ($assertion === 'array-key-exists') {
101
            $existing_var_type->possibly_undefined = false;
102
103
            return $existing_var_type;
104
        }
105
106
        if (substr($assertion, 0, 9) === 'in-array-') {
107
            return self::reconcileInArray(
108
                $codebase,
109
                $existing_var_type,
110
                substr($assertion, 9)
111
            );
112
        }
113
114
        if (substr($assertion, 0, 14) === 'has-array-key-') {
115
            return self::reconcileHasArrayKey(
116
                $existing_var_type,
117
                substr($assertion, 14)
118
            );
119
        }
120
121
        if ($assertion === 'falsy' || $assertion === 'empty') {
122
            return self::reconcileFalsyOrEmpty(
123
                $assertion,
124
                $existing_var_type,
125
                $key,
126
                $code_location,
127
                $suppressed_issues,
128
                $failed_reconciliation
129
            );
130
        }
131
132
        if ($assertion === 'object') {
133
            return self::reconcileObject(
134
                $existing_var_type,
135
                $key,
136
                $code_location,
137
                $suppressed_issues,
138
                $failed_reconciliation,
139
                $is_equality
140
            );
141
        }
142
143
        if ($assertion === 'resource') {
144
            return self::reconcileResource(
145
                $existing_var_type,
146
                $key,
147
                $code_location,
148
                $suppressed_issues,
149
                $failed_reconciliation,
150
                $is_equality
151
            );
152
        }
153
154
        if ($assertion === 'callable') {
155
            return self::reconcileCallable(
156
                $codebase,
157
                $existing_var_type,
158
                $key,
159
                $code_location,
160
                $suppressed_issues,
161
                $failed_reconciliation,
162
                $is_equality
163
            );
164
        }
165
166
        if ($assertion === 'iterable') {
167
            return self::reconcileIterable(
168
                $codebase,
169
                $existing_var_type,
170
                $key,
171
                $code_location,
172
                $suppressed_issues,
173
                $failed_reconciliation,
174
                $is_equality
175
            );
176
        }
177
178
        if ($assertion === 'array') {
179
            return self::reconcileArray(
180
                $existing_var_type,
181
                $key,
182
                $code_location,
183
                $suppressed_issues,
184
                $failed_reconciliation,
185
                $is_equality
186
            );
187
        }
188
189
        if ($assertion === 'list') {
190
            return self::reconcileList(
191
                $existing_var_type,
192
                $key,
193
                $code_location,
194
                $suppressed_issues,
195
                $failed_reconciliation,
196
                $is_equality
197
            );
198
        }
199
200
        if ($assertion === 'Traversable') {
201
            return self::reconcileTraversable(
202
                $codebase,
203
                $existing_var_type,
204
                $key,
205
                $code_location,
206
                $suppressed_issues,
207
                $failed_reconciliation,
208
                $is_equality
209
            );
210
        }
211
212
        if ($assertion === 'countable') {
213
            return self::reconcileCountable(
214
                $codebase,
215
                $existing_var_type,
216
                $key,
217
                $code_location,
218
                $suppressed_issues,
219
                $failed_reconciliation,
220
                $is_equality
221
            );
222
        }
223
224
        if ($assertion === 'string-array-access') {
225
            return self::reconcileStringArrayAccess(
226
                $codebase,
227
                $existing_var_type,
228
                $key,
229
                $code_location,
230
                $suppressed_issues,
231
                $failed_reconciliation,
232
                $inside_loop
233
            );
234
        }
235
236
        if ($assertion === 'int-or-string-array-access') {
237
            return self::reconcileIntArrayAccess(
238
                $codebase,
239
                $existing_var_type,
240
                $key,
241
                $code_location,
242
                $suppressed_issues,
243
                $failed_reconciliation,
244
                $inside_loop
245
            );
246
        }
247
248
        if ($assertion === 'numeric') {
249
            return self::reconcileNumeric(
250
                $existing_var_type,
251
                $key,
252
                $code_location,
253
                $suppressed_issues,
254
                $failed_reconciliation,
255
                $is_equality
256
            );
257
        }
258
259
        if ($assertion === 'scalar') {
260
            return self::reconcileScalar(
261
                $existing_var_type,
262
                $key,
263
                $code_location,
264
                $suppressed_issues,
265
                $failed_reconciliation,
266
                $is_equality
267
            );
268
        }
269
270
        if ($assertion === 'bool') {
271
            return self::reconcileBool(
272
                $existing_var_type,
273
                $key,
274
                $code_location,
275
                $suppressed_issues,
276
                $failed_reconciliation,
277
                $is_equality
278
            );
279
        }
280
281
        if ($assertion === 'string') {
282
            return self::reconcileString(
283
                $existing_var_type,
284
                $key,
285
                $code_location,
286
                $suppressed_issues,
287
                $failed_reconciliation,
288
                $is_equality,
289
                $is_strict_equality
290
            );
291
        }
292
293
        if ($assertion === 'int') {
294
            return self::reconcileInt(
295
                $existing_var_type,
296
                $key,
297
                $code_location,
298
                $suppressed_issues,
299
                $failed_reconciliation,
300
                $is_equality,
301
                $is_strict_equality
302
            );
303
        }
304
305
        if ($assertion === 'float'
306
            && $existing_var_type->from_calculation
307
            && $existing_var_type->hasInt()
308
        ) {
309
            return Type::getFloat();
310
        }
311
312
        if ($assertion === 'non-empty-countable') {
313
            return self::reconcileNonEmptyCountable(
314
                $existing_var_type,
315
                $key,
316
                $code_location,
317
                $suppressed_issues,
318
                $failed_reconciliation,
319
                $is_equality,
320
                null
321
            );
322
        }
323
324
        if (substr($assertion, 0, 13) === 'has-at-least-') {
325
            return self::reconcileNonEmptyCountable(
326
                $existing_var_type,
327
                $key,
328
                $code_location,
329
                $suppressed_issues,
330
                $failed_reconciliation,
331
                $is_equality,
332
                (int) substr($assertion, 13)
333
            );
334
        }
335
336
        if (substr($assertion, 0, 10) === 'hasmethod-') {
337
            return self::reconcileHasMethod(
338
                $codebase,
339
                substr($assertion, 10),
340
                $existing_var_type,
341
                $key,
342
                $code_location,
343
                $suppressed_issues,
344
                $failed_reconciliation
345
            );
346
        }
347
348
        if ($existing_var_type->isSingle()
349
            && $existing_var_type->hasTemplate()
350
            && strpos($assertion, '-') === false
351
            && strpos($assertion, '(') === false
352
        ) {
353
            foreach ($existing_var_type->getAtomicTypes() as $atomic_type) {
354
                if ($atomic_type instanceof TTemplateParam) {
355
                    if ($atomic_type->as->hasMixed()
356
                        || $atomic_type->as->hasObject()
357
                    ) {
358
                        $atomic_type->as = Type::parseString($assertion);
359
360
                        return $existing_var_type;
361
                    }
362
                }
363
            }
364
        }
365
366
        return null;
367
    }
368
369
    /**
370
     * @param   string[]  $suppressed_issues
371
     * @param   0|1|2    $failed_reconciliation
0 ignored issues
show
Documentation introduced by
The doc-type 0|1|2 could not be parsed: Unknown type name "0" at position 0. (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...
372
     */
373
    private static function reconcileNonEmptyCountable(
374
        Union $existing_var_type,
375
        ?string $key,
376
        ?CodeLocation $code_location,
377
        array $suppressed_issues,
378
        int &$failed_reconciliation,
0 ignored issues
show
Unused Code introduced by
The parameter $failed_reconciliation is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
379
        bool $is_equality,
380
        ?int $min_count
381
    ) : Union {
382
        $old_var_type_string = $existing_var_type->getId();
383
384
        if ($existing_var_type->hasType('array')) {
385
            $array_atomic_type = $existing_var_type->getAtomicTypes()['array'];
386
            $did_remove_type = false;
387
388
            if ($array_atomic_type instanceof TArray
389
                && !$array_atomic_type instanceof Type\Atomic\TNonEmptyArray
390
            ) {
391
                $did_remove_type = true;
392
                if ($array_atomic_type->getId() === 'array<empty, empty>') {
393
                    $existing_var_type->removeType('array');
394
                } else {
395
                    $existing_var_type->addType(
396
                        new Type\Atomic\TNonEmptyArray(
397
                            $array_atomic_type->type_params
398
                        )
399
                    );
400
                }
401
            } elseif ($array_atomic_type instanceof TList) {
402
                if (!$array_atomic_type instanceof Type\Atomic\TNonEmptyList
403
                    || ($array_atomic_type->count < $min_count)
404
                ) {
405
                    $non_empty_list = new Type\Atomic\TNonEmptyList(
406
                        $array_atomic_type->type_param
407
                    );
408
409
                    if ($min_count) {
410
                        $non_empty_list->count = $min_count;
411
                    }
412
413
                    $did_remove_type = true;
414
                    $existing_var_type->addType($non_empty_list);
415
                }
416
            } elseif ($array_atomic_type instanceof Type\Atomic\ObjectLike) {
417
                foreach ($array_atomic_type->properties as $property_type) {
418
                    if ($property_type->possibly_undefined) {
419
                        $did_remove_type = true;
420
                    }
421
                }
422
            }
423
424
            if (!$is_equality
425
                && !$existing_var_type->hasMixed()
426
                && (!$did_remove_type || empty($existing_var_type->getAtomicTypes()))
427
            ) {
428
                if ($key && $code_location) {
429
                    self::triggerIssueForImpossible(
430
                        $existing_var_type,
431
                        $old_var_type_string,
432
                        $key,
433
                        'non-empty-countable',
434
                        !$did_remove_type,
435
                        $code_location,
436
                        $suppressed_issues
437
                    );
438
                }
439
            }
440
        }
441
442
        return $existing_var_type;
443
    }
444
445
    /**
446
     * @param   string[]  $suppressed_issues
447
     * @param   0|1|2    $failed_reconciliation
0 ignored issues
show
Documentation introduced by
The doc-type 0|1|2 could not be parsed: Unknown type name "0" at position 0. (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...
448
     */
449
    private static function reconcileHasMethod(
450
        Codebase $codebase,
451
        string $method_name,
452
        Union $existing_var_type,
453
        ?string $key,
454
        ?CodeLocation $code_location,
455
        array $suppressed_issues,
456
        int &$failed_reconciliation
457
    ) : Union {
458
        $old_var_type_string = $existing_var_type->getId();
459
        $existing_var_atomic_types = $existing_var_type->getAtomicTypes();
460
461
        $object_types = [];
462
        $did_remove_type = false;
463
464
        foreach ($existing_var_atomic_types as $type) {
465
            if ($type instanceof TNamedObject
466
                && $codebase->classOrInterfaceExists($type->value)
467
            ) {
468
                $object_types[] = $type;
469
470
                if (!$codebase->methodExists($type->value . '::' . $method_name)) {
471
                    $match_found = false;
472
473
                    if ($type->extra_types) {
474
                        foreach ($type->extra_types as $extra_type) {
475
                            if ($extra_type instanceof TNamedObject
476
                                && $codebase->classOrInterfaceExists($extra_type->value)
477
                                && $codebase->methodExists($extra_type->value . '::' . $method_name)
478
                            ) {
479
                                $match_found = true;
480
                            } elseif ($extra_type instanceof Atomic\TObjectWithProperties) {
481
                                $match_found = true;
482
483
                                if (!isset($extra_type->methods[$method_name])) {
484
                                    $extra_type->methods[$method_name] = 'object::' . $method_name;
485
                                    $did_remove_type = true;
486
                                }
487
                            }
488
                        }
489
                    }
490
491
                    if (!$match_found) {
492
                        $obj = new Atomic\TObjectWithProperties(
493
                            [],
494
                            [$method_name => $type->value . '::' . $method_name]
495
                        );
496
                        $type->extra_types[$obj->getKey()] = $obj;
497
                        $did_remove_type = true;
498
                    }
499
                }
500
            } elseif ($type instanceof Atomic\TObjectWithProperties) {
501
                $object_types[] = $type;
502
503
                if (!isset($type->methods[$method_name])) {
504
                    $type->methods[$method_name] = 'object::' . $method_name;
505
                    $did_remove_type = true;
506
                }
507
            } elseif ($type instanceof TObject || $type instanceof TMixed) {
508
                $object_types[] = new Atomic\TObjectWithProperties(
509
                    [],
510
                    [$method_name =>  'object::' . $method_name]
511
                );
512
                $did_remove_type = true;
513
            } elseif ($type instanceof TString) {
514
                // we don’t know
515
                $object_types[] = $type;
516
                $did_remove_type = true;
517
            } elseif ($type instanceof TTemplateParam) {
518
                $object_types[] = $type;
519
                $did_remove_type = true;
520
            } else {
521
                $did_remove_type = true;
522
            }
523
        }
524
525
        if (!$object_types || !$did_remove_type) {
526
            if ($key && $code_location) {
527
                self::triggerIssueForImpossible(
528
                    $existing_var_type,
529
                    $old_var_type_string,
530
                    $key,
531
                    'object with method ' . $method_name,
532
                    !$did_remove_type,
533
                    $code_location,
534
                    $suppressed_issues
535
                );
536
            }
537
        }
538
539
        if ($object_types) {
540
            return new Type\Union($object_types);
541
        }
542
543
        $failed_reconciliation = 2;
544
545
        return Type::getMixed();
546
    }
547
548
    /**
549
     * @param   string[]  $suppressed_issues
550
     * @param   0|1|2    $failed_reconciliation
0 ignored issues
show
Documentation introduced by
The doc-type 0|1|2 could not be parsed: Unknown type name "0" at position 0. (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...
551
     */
552
    private static function reconcileString(
553
        Union $existing_var_type,
554
        ?string $key,
555
        ?CodeLocation $code_location,
556
        array $suppressed_issues,
557
        int &$failed_reconciliation,
558
        bool $is_equality,
559
        bool $is_strict_equality
560
    ) : Union {
561
        $old_var_type_string = $existing_var_type->getId();
562
        $existing_var_atomic_types = $existing_var_type->getAtomicTypes();
563
564
        if ($existing_var_type->hasMixed()) {
565
            if ($is_equality && !$is_strict_equality) {
566
                return $existing_var_type;
567
            }
568
569
            return Type::getString();
570
        }
571
572
        $string_types = [];
573
        $did_remove_type = false;
574
575
        foreach ($existing_var_atomic_types as $type) {
576
            if ($type instanceof TString) {
577
                $string_types[] = $type;
578
579
                if (get_class($type) === TString::class) {
580
                    $type->from_docblock = false;
581
                }
582
            } elseif ($type instanceof TCallable) {
583
                $string_types[] = new Type\Atomic\TCallableString;
584
                $did_remove_type = true;
585
            } elseif ($type instanceof TNumeric) {
586
                $string_types[] = new TNumericString;
587
                $did_remove_type = true;
588
            } elseif ($type instanceof TScalar || $type instanceof TArrayKey) {
589
                $string_types[] = new TString;
590
                $did_remove_type = true;
591
            } elseif ($type instanceof TTemplateParam) {
592
                if ($type->as->hasString() || $type->as->hasMixed()) {
593
                    $type = clone $type;
594
595
                    $type->as = self::reconcileString(
596
                        $type->as,
597
                        null,
598
                        null,
599
                        $suppressed_issues,
600
                        $failed_reconciliation,
601
                        $is_equality,
602
                        $is_strict_equality
603
                    );
604
605
                    $string_types[] = $type;
606
                }
607
608
                $did_remove_type = true;
609
            } else {
610
                $did_remove_type = true;
611
            }
612
        }
613
614
        if ((!$did_remove_type || !$string_types) && !$is_equality) {
615
            if ($key && $code_location) {
616
                self::triggerIssueForImpossible(
617
                    $existing_var_type,
618
                    $old_var_type_string,
619
                    $key,
620
                    'string',
621
                    !$did_remove_type,
622
                    $code_location,
623
                    $suppressed_issues
624
                );
625
            }
626
        }
627
628
        if ($string_types) {
629
            return new Type\Union($string_types);
630
        }
631
632
        $failed_reconciliation = 2;
633
634
        return $existing_var_type->from_docblock
635
            ? Type::getMixed()
636
            : Type::getEmpty();
637
    }
638
639
    /**
640
     * @param   string[]  $suppressed_issues
641
     * @param   0|1|2    $failed_reconciliation
0 ignored issues
show
Documentation introduced by
The doc-type 0|1|2 could not be parsed: Unknown type name "0" at position 0. (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...
642
     */
643
    private static function reconcileInt(
644
        Union $existing_var_type,
645
        ?string $key,
646
        ?CodeLocation $code_location,
647
        array $suppressed_issues,
648
        int &$failed_reconciliation,
649
        bool $is_equality,
650
        bool $is_strict_equality
651
    ) : Union {
652
        if ($existing_var_type->hasMixed()) {
653
            if ($is_equality && !$is_strict_equality) {
654
                return $existing_var_type;
655
            }
656
657
            return Type::getInt();
658
        }
659
660
        $old_var_type_string = $existing_var_type->getId();
661
        $existing_var_atomic_types = $existing_var_type->getAtomicTypes();
662
663
        $int_types = [];
664
        $did_remove_type = false;
665
666
        foreach ($existing_var_atomic_types as $type) {
667
            if ($type instanceof TInt) {
668
                $int_types[] = $type;
669
670
                if (get_class($type) === TInt::class) {
671
                    $type->from_docblock = false;
672
                }
673
674
                if ($existing_var_type->from_calculation) {
675
                    $did_remove_type = true;
676
                }
677
            } elseif ($type instanceof TNumeric) {
678
                $int_types[] = new TInt;
679
                $did_remove_type = true;
680
            } elseif ($type instanceof TScalar || $type instanceof TArrayKey) {
681
                $int_types[] = new TInt;
682
                $did_remove_type = true;
683
            } elseif ($type instanceof TTemplateParam) {
684
                if ($type->as->hasInt() || $type->as->hasMixed()) {
685
                    $type = clone $type;
686
687
                    $type->as = self::reconcileInt(
688
                        $type->as,
689
                        null,
690
                        null,
691
                        $suppressed_issues,
692
                        $failed_reconciliation,
693
                        $is_equality,
694
                        $is_strict_equality
695
                    );
696
697
                    $int_types[] = $type;
698
                }
699
700
                $did_remove_type = true;
701
            } else {
702
                $did_remove_type = true;
703
            }
704
        }
705
706
        if ((!$did_remove_type || !$int_types) && !$is_equality) {
707
            if ($key && $code_location) {
708
                self::triggerIssueForImpossible(
709
                    $existing_var_type,
710
                    $old_var_type_string,
711
                    $key,
712
                    'int',
713
                    !$did_remove_type,
714
                    $code_location,
715
                    $suppressed_issues
716
                );
717
            }
718
        }
719
720
        if ($int_types) {
721
            return new Type\Union($int_types);
722
        }
723
724
        $failed_reconciliation = 2;
725
726
        return $existing_var_type->from_docblock
727
            ? Type::getMixed()
728
            : Type::getEmpty();
729
    }
730
731
    /**
732
     * @param   string[]  $suppressed_issues
733
     * @param   0|1|2    $failed_reconciliation
0 ignored issues
show
Documentation introduced by
The doc-type 0|1|2 could not be parsed: Unknown type name "0" at position 0. (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...
734
     */
735
    private static function reconcileBool(
736
        Union $existing_var_type,
737
        ?string $key,
738
        ?CodeLocation $code_location,
739
        array $suppressed_issues,
740
        int &$failed_reconciliation,
741
        bool $is_equality
742
    ) : Union {
743
        if ($existing_var_type->hasMixed()) {
744
            return Type::getBool();
745
        }
746
747
        $bool_types = [];
748
        $did_remove_type = false;
749
750
        $old_var_type_string = $existing_var_type->getId();
751
        $existing_var_atomic_types = $existing_var_type->getAtomicTypes();
752
753
        foreach ($existing_var_atomic_types as $type) {
754
            if ($type instanceof TBool) {
755
                $bool_types[] = $type;
756
                $type->from_docblock = false;
757
            } elseif ($type instanceof TScalar) {
758
                $bool_types[] = new TBool;
759
                $did_remove_type = true;
760
            } elseif ($type instanceof TTemplateParam) {
761
                if ($type->as->hasBool() || $type->as->hasMixed()) {
762
                    $type = clone $type;
763
764
                    $type->as = self::reconcileBool(
765
                        $type->as,
766
                        null,
767
                        null,
768
                        $suppressed_issues,
769
                        $failed_reconciliation,
770
                        $is_equality
771
                    );
772
773
                    $bool_types[] = $type;
774
                }
775
776
                $did_remove_type = true;
777
            } else {
778
                $did_remove_type = true;
779
            }
780
        }
781
782
        if ((!$did_remove_type || !$bool_types) && !$is_equality) {
783
            if ($key && $code_location) {
784
                self::triggerIssueForImpossible(
785
                    $existing_var_type,
786
                    $old_var_type_string,
787
                    $key,
788
                    'bool',
789
                    !$did_remove_type,
790
                    $code_location,
791
                    $suppressed_issues
792
                );
793
            }
794
        }
795
796
        if ($bool_types) {
797
            return new Type\Union($bool_types);
798
        }
799
800
        $failed_reconciliation = 2;
801
802
        return $existing_var_type->from_docblock
803
            ? Type::getMixed()
804
            : Type::getEmpty();
805
    }
806
807
    /**
808
     * @param   string[]  $suppressed_issues
809
     * @param   0|1|2    $failed_reconciliation
0 ignored issues
show
Documentation introduced by
The doc-type 0|1|2 could not be parsed: Unknown type name "0" at position 0. (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...
810
     */
811
    private static function reconcileScalar(
812
        Union $existing_var_type,
813
        ?string $key,
814
        ?CodeLocation $code_location,
815
        array $suppressed_issues,
816
        int &$failed_reconciliation,
817
        bool $is_equality
818
    ) : Union {
819
        if ($existing_var_type->hasMixed()) {
820
            return Type::getScalar();
821
        }
822
823
        $scalar_types = [];
824
        $did_remove_type = false;
825
826
        $old_var_type_string = $existing_var_type->getId();
827
        $existing_var_atomic_types = $existing_var_type->getAtomicTypes();
828
829
        foreach ($existing_var_atomic_types as $type) {
830
            if ($type instanceof Scalar) {
831
                $scalar_types[] = $type;
832
            } elseif ($type instanceof TTemplateParam) {
833
                if ($type->as->hasScalar() || $type->as->hasMixed()) {
834
                    $type = clone $type;
835
836
                    $type->as = self::reconcileScalar(
837
                        $type->as,
838
                        null,
839
                        null,
840
                        $suppressed_issues,
841
                        $failed_reconciliation,
842
                        $is_equality
843
                    );
844
845
                    $scalar_types[] = $type;
846
                }
847
848
                $did_remove_type = true;
849
            } else {
850
                $did_remove_type = true;
851
            }
852
        }
853
854
        if ((!$did_remove_type || !$scalar_types) && !$is_equality) {
855
            if ($key && $code_location) {
856
                self::triggerIssueForImpossible(
857
                    $existing_var_type,
858
                    $old_var_type_string,
859
                    $key,
860
                    'scalar',
861
                    !$did_remove_type,
862
                    $code_location,
863
                    $suppressed_issues
864
                );
865
            }
866
        }
867
868
        if ($scalar_types) {
869
            return new Type\Union($scalar_types);
870
        }
871
872
        $failed_reconciliation = 2;
873
874
        return $existing_var_type->from_docblock
875
            ? Type::getMixed()
876
            : Type::getEmpty();
877
    }
878
879
    /**
880
     * @param   string[]  $suppressed_issues
881
     * @param   0|1|2    $failed_reconciliation
0 ignored issues
show
Documentation introduced by
The doc-type 0|1|2 could not be parsed: Unknown type name "0" at position 0. (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...
882
     */
883
    private static function reconcileNumeric(
884
        Union $existing_var_type,
885
        ?string $key,
886
        ?CodeLocation $code_location,
887
        array $suppressed_issues,
888
        int &$failed_reconciliation,
889
        bool $is_equality
890
    ) : Union {
891
        if ($existing_var_type->hasMixed()) {
892
            return Type::getNumeric();
893
        }
894
895
        $old_var_type_string = $existing_var_type->getId();
896
897
        $numeric_types = [];
898
        $did_remove_type = false;
899
900
        if ($existing_var_type->hasString()) {
901
            $did_remove_type = true;
902
            $existing_var_type->removeType('string');
903
            $existing_var_type->addType(new TNumericString);
904
        }
905
906
        foreach ($existing_var_type->getAtomicTypes() as $type) {
907
            if ($type instanceof TNumeric || $type instanceof TNumericString) {
908
                // this is a workaround for a possible issue running
909
                // is_numeric($a) && is_string($a)
910
                $did_remove_type = true;
911
                $numeric_types[] = $type;
912
            } elseif ($type->isNumericType()) {
913
                $numeric_types[] = $type;
914
            } elseif ($type instanceof TScalar) {
915
                $did_remove_type = true;
916
                $numeric_types[] = new TNumeric();
917
            } elseif ($type instanceof TArrayKey) {
918
                $did_remove_type = true;
919
                $numeric_types[] = new TInt();
920
                $numeric_types[] = new TNumericString();
921
            } elseif ($type instanceof TTemplateParam) {
922
                if ($type->as->hasNumeric() || $type->as->hasMixed()) {
923
                    $type = clone $type;
924
925
                    $type->as = self::reconcileNumeric(
926
                        $type->as,
927
                        null,
928
                        null,
929
                        $suppressed_issues,
930
                        $failed_reconciliation,
931
                        $is_equality
932
                    );
933
934
                    $numeric_types[] = $type;
935
                }
936
937
                $did_remove_type = true;
938
            } else {
939
                $did_remove_type = true;
940
            }
941
        }
942
943
        if ((!$did_remove_type || !$numeric_types) && !$is_equality) {
944
            if ($key && $code_location) {
945
                self::triggerIssueForImpossible(
946
                    $existing_var_type,
947
                    $old_var_type_string,
948
                    $key,
949
                    'numeric',
950
                    !$did_remove_type,
951
                    $code_location,
952
                    $suppressed_issues
953
                );
954
            }
955
        }
956
957
        if ($numeric_types) {
958
            return new Type\Union($numeric_types);
959
        }
960
961
        $failed_reconciliation = 2;
962
963
        return $existing_var_type->from_docblock
964
            ? Type::getMixed()
965
            : Type::getEmpty();
966
    }
967
968
    /**
969
     * @param   string[]  $suppressed_issues
970
     * @param   0|1|2    $failed_reconciliation
0 ignored issues
show
Documentation introduced by
The doc-type 0|1|2 could not be parsed: Unknown type name "0" at position 0. (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...
971
     */
972
    private static function reconcileObject(
973
        Union $existing_var_type,
974
        ?string $key,
975
        ?CodeLocation $code_location,
976
        array $suppressed_issues,
977
        int &$failed_reconciliation,
978
        bool $is_equality
979
    ) : Union {
980
        if ($existing_var_type->hasMixed()) {
981
            return Type::getObject();
982
        }
983
984
        $old_var_type_string = $existing_var_type->getId();
985
        $existing_var_atomic_types = $existing_var_type->getAtomicTypes();
986
987
        $object_types = [];
988
        $did_remove_type = false;
989
990
        foreach ($existing_var_atomic_types as $type) {
991
            if ($type->isObjectType()) {
992
                $object_types[] = $type;
993
            } elseif ($type instanceof TCallable) {
994
                $object_types[] = new Type\Atomic\TCallableObject();
995
                $did_remove_type = true;
996
            } elseif ($type instanceof TTemplateParam
997
                && $type->as->isMixed()
998
            ) {
999
                $type = clone $type;
1000
                $type->as = Type::getObject();
1001
                $object_types[] = $type;
1002
                $did_remove_type = true;
1003
            } elseif ($type instanceof TTemplateParam) {
1004
                if ($type->as->hasObject() || $type->as->hasMixed()) {
1005
                    $type = clone $type;
1006
1007
                    $type->as = self::reconcileObject(
1008
                        $type->as,
1009
                        null,
1010
                        null,
1011
                        $suppressed_issues,
1012
                        $failed_reconciliation,
1013
                        $is_equality
1014
                    );
1015
1016
                    $object_types[] = $type;
1017
                }
1018
1019
                $did_remove_type = true;
1020
            } else {
1021
                $did_remove_type = true;
1022
            }
1023
        }
1024
1025
        if ((!$object_types || !$did_remove_type) && !$is_equality) {
1026
            if ($key && $code_location) {
1027
                self::triggerIssueForImpossible(
1028
                    $existing_var_type,
1029
                    $old_var_type_string,
1030
                    $key,
1031
                    'object',
1032
                    !$did_remove_type,
1033
                    $code_location,
1034
                    $suppressed_issues
1035
                );
1036
            }
1037
        }
1038
1039
        if ($object_types) {
1040
            return new Type\Union($object_types);
1041
        }
1042
1043
        $failed_reconciliation = 2;
1044
1045
        return $existing_var_type->from_docblock
1046
            ? Type::getMixed()
1047
            : Type::getEmpty();
1048
    }
1049
1050
    /**
1051
     * @param   string[]  $suppressed_issues
1052
     * @param   0|1|2    $failed_reconciliation
0 ignored issues
show
Documentation introduced by
The doc-type 0|1|2 could not be parsed: Unknown type name "0" at position 0. (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...
1053
     */
1054
    private static function reconcileResource(
1055
        Union $existing_var_type,
1056
        ?string $key,
1057
        ?CodeLocation $code_location,
1058
        array $suppressed_issues,
1059
        int &$failed_reconciliation,
1060
        bool $is_equality
1061
    ) : Union {
1062
        if ($existing_var_type->hasMixed()) {
1063
            return Type::getResource();
1064
        }
1065
1066
        $old_var_type_string = $existing_var_type->getId();
1067
        $existing_var_atomic_types = $existing_var_type->getAtomicTypes();
1068
1069
        $resource_types = [];
1070
        $did_remove_type = false;
1071
1072
        foreach ($existing_var_atomic_types as $type) {
1073
            if ($type instanceof TResource) {
1074
                $resource_types[] = $type;
1075
            } else {
1076
                $did_remove_type = true;
1077
            }
1078
        }
1079
1080
        if ((!$resource_types || !$did_remove_type) && !$is_equality) {
1081
            if ($key && $code_location) {
1082
                self::triggerIssueForImpossible(
1083
                    $existing_var_type,
1084
                    $old_var_type_string,
1085
                    $key,
1086
                    'resource',
1087
                    !$did_remove_type,
1088
                    $code_location,
1089
                    $suppressed_issues
1090
                );
1091
            }
1092
        }
1093
1094
        if ($resource_types) {
1095
            return new Type\Union($resource_types);
1096
        }
1097
1098
        $failed_reconciliation = 2;
1099
1100
        return $existing_var_type->from_docblock
1101
            ? Type::getMixed()
1102
            : Type::getEmpty();
1103
    }
1104
1105
    /**
1106
     * @param   string[]  $suppressed_issues
1107
     * @param   0|1|2    $failed_reconciliation
0 ignored issues
show
Documentation introduced by
The doc-type 0|1|2 could not be parsed: Unknown type name "0" at position 0. (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...
1108
     */
1109
    private static function reconcileCountable(
1110
        Codebase $codebase,
1111
        Union $existing_var_type,
1112
        ?string $key,
1113
        ?CodeLocation $code_location,
1114
        array $suppressed_issues,
1115
        int &$failed_reconciliation,
1116
        bool $is_equality
1117
    ) : Union {
1118
        $old_var_type_string = $existing_var_type->getId();
1119
1120
        $existing_var_atomic_types = $existing_var_type->getAtomicTypes();
1121
1122
1123
        if ($existing_var_type->hasMixed() || $existing_var_type->hasTemplate()) {
1124
            return new Type\Union([
1125
                new Type\Atomic\TArray([Type::getArrayKey(), Type::getMixed()]),
1126
                new Type\Atomic\TNamedObject('Countable'),
1127
            ]);
1128
        }
1129
1130
        $iterable_types = [];
1131
        $did_remove_type = false;
1132
1133
        foreach ($existing_var_atomic_types as $type) {
1134
            if ($type->isCountable($codebase)) {
1135
                $iterable_types[] = $type;
1136
            } elseif ($type instanceof TObject) {
1137
                $iterable_types[] = new TNamedObject('Countable');
1138
                $did_remove_type = true;
1139
            } elseif ($type instanceof TNamedObject || $type instanceof Type\Atomic\TIterable) {
1140
                $countable = new TNamedObject('Countable');
1141
                $type->extra_types[$countable->getKey()] = $countable;
1142
                $iterable_types[] = $type;
1143
                $did_remove_type = true;
1144
            } else {
1145
                $did_remove_type = true;
1146
            }
1147
        }
1148
1149
        if ((!$iterable_types || !$did_remove_type) && !$is_equality) {
1150
            if ($key && $code_location) {
1151
                self::triggerIssueForImpossible(
1152
                    $existing_var_type,
1153
                    $old_var_type_string,
1154
                    $key,
1155
                    'countable',
1156
                    !$did_remove_type,
1157
                    $code_location,
1158
                    $suppressed_issues
1159
                );
1160
            }
1161
        }
1162
1163
        if ($iterable_types) {
1164
            return new Type\Union($iterable_types);
1165
        }
1166
1167
        $failed_reconciliation = 2;
1168
1169
        return Type::getMixed();
1170
    }
1171
1172
    /**
1173
     * @param   string[]  $suppressed_issues
1174
     * @param   0|1|2    $failed_reconciliation
0 ignored issues
show
Documentation introduced by
The doc-type 0|1|2 could not be parsed: Unknown type name "0" at position 0. (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...
1175
     */
1176
    private static function reconcileIterable(
1177
        Codebase $codebase,
1178
        Union $existing_var_type,
1179
        ?string $key,
1180
        ?CodeLocation $code_location,
1181
        array $suppressed_issues,
1182
        int &$failed_reconciliation,
1183
        bool $is_equality
1184
    ) : Union {
1185
        $old_var_type_string = $existing_var_type->getId();
1186
1187
        $existing_var_atomic_types = $existing_var_type->getAtomicTypes();
1188
1189
        if ($existing_var_type->hasMixed() || $existing_var_type->hasTemplate()) {
1190
            return new Type\Union([new Type\Atomic\TIterable]);
1191
        }
1192
1193
        $iterable_types = [];
1194
        $did_remove_type = false;
1195
1196
        foreach ($existing_var_atomic_types as $type) {
1197
            if ($type->isIterable($codebase)) {
1198
                $iterable_types[] = $type;
1199
            } elseif ($type instanceof TObject) {
1200
                $iterable_types[] = new Type\Atomic\TNamedObject('Traversable');
1201
                $did_remove_type = true;
1202
            } else {
1203
                $did_remove_type = true;
1204
            }
1205
        }
1206
1207
        if ((!$iterable_types || !$did_remove_type) && !$is_equality) {
1208
            if ($key && $code_location) {
1209
                self::triggerIssueForImpossible(
1210
                    $existing_var_type,
1211
                    $old_var_type_string,
1212
                    $key,
1213
                    'iterable',
1214
                    !$did_remove_type,
1215
                    $code_location,
1216
                    $suppressed_issues
1217
                );
1218
            }
1219
        }
1220
1221
        if ($iterable_types) {
1222
            return new Type\Union($iterable_types);
1223
        }
1224
1225
        $failed_reconciliation = 2;
1226
1227
        return Type::getMixed();
1228
    }
1229
1230
    /**
1231
     * @param   string[]  $suppressed_issues
0 ignored issues
show
Bug introduced by
There is no parameter named $suppressed_issues. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
1232
     * @param   0|1|2    $failed_reconciliation
0 ignored issues
show
Documentation introduced by
The doc-type 0|1|2 could not be parsed: Unknown type name "0" at position 0. (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...
Bug introduced by
There is no parameter named $failed_reconciliation. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
1233
     */
1234
    private static function reconcileInArray(
1235
        Codebase $codebase,
1236
        Union $existing_var_type,
1237
        string $assertion
1238
    ) : Union {
1239
        if (strpos($assertion, '::')) {
1240
            list($fq_classlike_name, $const_name) = explode('::', $assertion);
1241
1242
            $class_constant_type = $codebase->classlikes->getConstantForClass(
1243
                $fq_classlike_name,
1244
                $const_name,
1245
                \ReflectionProperty::IS_PRIVATE
1246
            );
1247
1248
            if ($class_constant_type) {
1249
                foreach ($class_constant_type->getAtomicTypes() as $const_type_atomic) {
1250
                    if ($const_type_atomic instanceof Type\Atomic\ObjectLike
1251
                        || $const_type_atomic instanceof Type\Atomic\TArray
1252
                    ) {
1253
                        if ($const_type_atomic instanceof Type\Atomic\ObjectLike) {
1254
                            $const_type_atomic = $const_type_atomic->getGenericArrayType();
1255
                        }
1256
1257
                        if (TypeAnalyzer::isContainedBy(
1258
                            $codebase,
1259
                            $const_type_atomic->type_params[0],
1260
                            $existing_var_type
1261
                        )) {
1262
                            return clone $const_type_atomic->type_params[0];
1263
                        }
1264
                    }
1265
                }
1266
            }
1267
        }
1268
1269
        $existing_var_type->removeType('null');
1270
1271
        return $existing_var_type;
1272
    }
1273
1274
    /**
1275
     * @param   string[]  $suppressed_issues
0 ignored issues
show
Bug introduced by
There is no parameter named $suppressed_issues. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
1276
     * @param   0|1|2    $failed_reconciliation
0 ignored issues
show
Documentation introduced by
The doc-type 0|1|2 could not be parsed: Unknown type name "0" at position 0. (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...
Bug introduced by
There is no parameter named $failed_reconciliation. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
1277
     */
1278
    private static function reconcileHasArrayKey(
1279
        Union $existing_var_type,
1280
        string $assertion
1281
    ) : Union {
1282
        foreach ($existing_var_type->getAtomicTypes() as $atomic_type) {
1283
            if ($atomic_type instanceof Type\Atomic\ObjectLike) {
1284
                $is_class_string = false;
1285
1286
                if (strpos($assertion, '::class')) {
1287
                    list($assertion) = explode('::', $assertion);
1288
                    $is_class_string = true;
1289
                }
1290
1291
                if (isset($atomic_type->properties[$assertion])) {
1292
                    $atomic_type->properties[$assertion]->possibly_undefined = false;
1293
                } else {
1294
                    $atomic_type->properties[$assertion] = Type::getMixed();
1295
1296
                    if ($is_class_string) {
1297
                        $atomic_type->class_strings[$assertion] = true;
1298
                    }
1299
                }
1300
            }
1301
        }
1302
1303
        return $existing_var_type;
1304
    }
1305
1306
    /**
1307
     * @param   string[]  $suppressed_issues
1308
     * @param   0|1|2    $failed_reconciliation
0 ignored issues
show
Documentation introduced by
The doc-type 0|1|2 could not be parsed: Unknown type name "0" at position 0. (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...
1309
     */
1310
    private static function reconcileTraversable(
1311
        Codebase $codebase,
1312
        Union $existing_var_type,
1313
        ?string $key,
1314
        ?CodeLocation $code_location,
1315
        array $suppressed_issues,
1316
        int &$failed_reconciliation,
1317
        bool $is_equality
1318
    ) : Union {
1319
        $old_var_type_string = $existing_var_type->getId();
1320
1321
        $existing_var_atomic_types = $existing_var_type->getAtomicTypes();
1322
1323
        if ($existing_var_type->hasMixed() || $existing_var_type->hasTemplate()) {
1324
            return new Type\Union([new Type\Atomic\TNamedObject('Traversable')]);
1325
        }
1326
1327
        $traversable_types = [];
1328
        $did_remove_type = false;
1329
1330
        foreach ($existing_var_atomic_types as $type) {
1331
            if ($type->hasTraversableInterface($codebase)) {
1332
                $traversable_types[] = $type;
1333
            } elseif ($type instanceof Atomic\TIterable) {
1334
                $clone_type = clone $type;
1335
                $traversable_types[] = new Atomic\TGenericObject('Traversable', $clone_type->type_params);
1336
                $did_remove_type = true;
1337
            } elseif ($type instanceof TObject) {
1338
                $traversable_types[] = new TNamedObject('Traversable');
1339
                $did_remove_type = true;
1340
            } elseif ($type instanceof TNamedObject) {
1341
                $traversable = new TNamedObject('Traversable');
1342
                $type->extra_types[$traversable->getKey()] = $traversable;
1343
                $traversable_types[] = $type;
1344
                $did_remove_type = true;
1345
            } else {
1346
                $did_remove_type = true;
1347
            }
1348
        }
1349
1350
        if ((!$traversable_types || !$did_remove_type) && !$is_equality) {
1351
            if ($key && $code_location) {
1352
                self::triggerIssueForImpossible(
1353
                    $existing_var_type,
1354
                    $old_var_type_string,
1355
                    $key,
1356
                    'Traversable',
1357
                    !$did_remove_type,
1358
                    $code_location,
1359
                    $suppressed_issues
1360
                );
1361
            }
1362
        }
1363
1364
        if ($traversable_types) {
1365
            return new Type\Union($traversable_types);
1366
        }
1367
1368
        $failed_reconciliation = 2;
1369
1370
        return $existing_var_type->from_docblock
1371
            ? Type::getMixed()
1372
            : Type::getEmpty();
1373
    }
1374
1375
    /**
1376
     * @param   string[]  $suppressed_issues
1377
     * @param   0|1|2    $failed_reconciliation
0 ignored issues
show
Documentation introduced by
The doc-type 0|1|2 could not be parsed: Unknown type name "0" at position 0. (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...
1378
     */
1379
    private static function reconcileArray(
1380
        Union $existing_var_type,
1381
        ?string $key,
1382
        ?CodeLocation $code_location,
1383
        array $suppressed_issues,
1384
        int &$failed_reconciliation,
1385
        bool $is_equality
1386
    ) : Union {
1387
        $old_var_type_string = $existing_var_type->getId();
1388
1389
        $existing_var_atomic_types = $existing_var_type->getAtomicTypes();
1390
1391
        if ($existing_var_type->hasMixed() || $existing_var_type->hasTemplate()) {
1392
            return Type::getArray();
1393
        }
1394
1395
        $array_types = [];
1396
        $did_remove_type = false;
1397
1398
        foreach ($existing_var_atomic_types as $type) {
1399
            if ($type instanceof TArray || $type instanceof ObjectLike || $type instanceof TList) {
1400
                $array_types[] = $type;
1401
            } elseif ($type instanceof TCallable) {
1402
                $array_types[] = new TCallableObjectLikeArray([
1403
                    new Union([new TClassString, new TObject]),
1404
                    Type::getString()
1405
                ]);
1406
1407
                $did_remove_type = true;
1408
            } elseif ($type instanceof Atomic\TIterable) {
1409
                $clone_type = clone $type;
1410
                if ($clone_type->type_params[0]->isMixed()) {
1411
                    $clone_type->type_params[0] = Type::getArrayKey();
1412
                }
1413
                $array_types[] = new TArray($clone_type->type_params);
1414
1415
                $did_remove_type = true;
1416
            } else {
1417
                $did_remove_type = true;
1418
            }
1419
        }
1420
1421
        if ((!$array_types || !$did_remove_type) && !$is_equality) {
1422
            if ($key && $code_location) {
1423
                self::triggerIssueForImpossible(
1424
                    $existing_var_type,
1425
                    $old_var_type_string,
1426
                    $key,
1427
                    'array',
1428
                    !$did_remove_type,
1429
                    $code_location,
1430
                    $suppressed_issues
1431
                );
1432
1433
                if (!$did_remove_type) {
1434
                    $failed_reconciliation = 1;
1435
                }
1436
            }
1437
        }
1438
1439
        if ($array_types) {
1440
            return \Psalm\Internal\Type\TypeCombination::combineTypes($array_types);
1441
        }
1442
1443
        $failed_reconciliation = 2;
1444
1445
        return $existing_var_type->from_docblock
1446
            ? Type::getMixed()
1447
            : Type::getEmpty();
1448
    }
1449
1450
    /**
1451
     * @param   string[]  $suppressed_issues
1452
     * @param   0|1|2    $failed_reconciliation
0 ignored issues
show
Documentation introduced by
The doc-type 0|1|2 could not be parsed: Unknown type name "0" at position 0. (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...
1453
     */
1454
    private static function reconcileList(
1455
        Union $existing_var_type,
1456
        ?string $key,
1457
        ?CodeLocation $code_location,
1458
        array $suppressed_issues,
1459
        int &$failed_reconciliation,
1460
        bool $is_equality
1461
    ) : Union {
1462
        $old_var_type_string = $existing_var_type->getId();
1463
1464
        $existing_var_atomic_types = $existing_var_type->getAtomicTypes();
1465
1466
        if ($existing_var_type->hasMixed() || $existing_var_type->hasTemplate()) {
1467
            return Type::getList();
1468
        }
1469
1470
        $array_types = [];
1471
        $did_remove_type = false;
1472
1473
        foreach ($existing_var_atomic_types as $type) {
1474
            if ($type instanceof TList || ($type instanceof ObjectLike && $type->is_list)) {
1475
                $array_types[] = $type;
1476
            } elseif ($type instanceof TArray || $type instanceof ObjectLike) {
1477
                if ($type instanceof ObjectLike) {
1478
                    $type = $type->getGenericArrayType();
1479
                }
1480
1481
                if ($type->type_params[0]->hasArrayKey()
1482
                    || $type->type_params[0]->hasInt()
1483
                ) {
1484
                    if ($type instanceof TNonEmptyArray) {
1485
                        $array_types[] = new TNonEmptyList($type->type_params[1]);
1486
                    } else {
1487
                        $array_types[] = new TList($type->type_params[1]);
1488
                    }
1489
                }
1490
1491
                $did_remove_type = true;
1492
            } elseif ($type instanceof TCallable) {
1493
                $array_types[] = new TCallableObjectLikeArray([
1494
                    new Union([new TClassString, new TObject]),
1495
                    Type::getString()
1496
                ]);
1497
1498
                $did_remove_type = true;
1499
            } elseif ($type instanceof Atomic\TIterable) {
1500
                $clone_type = clone $type;
1501
                $array_types[] = new TList($clone_type->type_params[1]);
1502
1503
                $did_remove_type = true;
1504
            } else {
1505
                $did_remove_type = true;
1506
            }
1507
        }
1508
1509
        if ((!$array_types || !$did_remove_type) && !$is_equality) {
1510
            if ($key && $code_location) {
1511
                self::triggerIssueForImpossible(
1512
                    $existing_var_type,
1513
                    $old_var_type_string,
1514
                    $key,
1515
                    'array',
1516
                    !$did_remove_type,
1517
                    $code_location,
1518
                    $suppressed_issues
1519
                );
1520
1521
                if (!$did_remove_type) {
1522
                    $failed_reconciliation = 1;
1523
                }
1524
            }
1525
        }
1526
1527
        if ($array_types) {
1528
            return \Psalm\Internal\Type\TypeCombination::combineTypes($array_types);
1529
        }
1530
1531
        $failed_reconciliation = 2;
1532
1533
        return $existing_var_type->from_docblock
1534
            ? Type::getMixed()
1535
            : Type::getEmpty();
1536
    }
1537
1538
    /**
1539
     * @param   string[]  $suppressed_issues
1540
     * @param   0|1|2    $failed_reconciliation
0 ignored issues
show
Documentation introduced by
The doc-type 0|1|2 could not be parsed: Unknown type name "0" at position 0. (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...
1541
     */
1542
    private static function reconcileStringArrayAccess(
1543
        Codebase $codebase,
1544
        Union $existing_var_type,
1545
        ?string $key,
1546
        ?CodeLocation $code_location,
1547
        array $suppressed_issues,
1548
        int &$failed_reconciliation,
1549
        bool $inside_loop
1550
    ) : Union {
1551
        $old_var_type_string = $existing_var_type->getId();
1552
1553
        $existing_var_atomic_types = $existing_var_type->getAtomicTypes();
1554
1555
        if ($existing_var_type->hasMixed() || $existing_var_type->hasTemplate()) {
1556
            return new Union([
1557
                new Atomic\TNonEmptyArray([Type::getArrayKey(), Type::getMixed()]),
1558
                new TNamedObject('ArrayAccess'),
1559
            ]);
1560
        }
1561
1562
        $array_types = [];
1563
1564
        foreach ($existing_var_atomic_types as $type) {
1565
            if ($type->isArrayAccessibleWithStringKey($codebase)) {
1566
                if (get_class($type) === TArray::class) {
1567
                    $array_types[] = new Atomic\TNonEmptyArray($type->type_params);
1568
                } elseif (get_class($type) === TList::class) {
1569
                    $array_types[] = new Atomic\TNonEmptyList($type->type_param);
1570
                } else {
1571
                    $array_types[] = $type;
1572
                }
1573
            } elseif ($type instanceof TTemplateParam) {
1574
                $array_types[] = $type;
1575
            }
1576
        }
1577
1578
        if (!$array_types) {
1579
            if ($key && $code_location) {
1580
                self::triggerIssueForImpossible(
1581
                    $existing_var_type,
1582
                    $old_var_type_string,
1583
                    $key,
1584
                    'string-array-access',
1585
                    true,
1586
                    $code_location,
1587
                    $suppressed_issues
1588
                );
1589
            }
1590
        }
1591
1592
        if ($array_types) {
1593
            return new Type\Union($array_types);
1594
        }
1595
1596
        $failed_reconciliation = 2;
1597
1598
        return Type::getMixed($inside_loop);
1599
    }
1600
1601
    /**
1602
     * @param   string[]  $suppressed_issues
1603
     * @param   0|1|2    $failed_reconciliation
0 ignored issues
show
Documentation introduced by
The doc-type 0|1|2 could not be parsed: Unknown type name "0" at position 0. (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...
1604
     */
1605
    private static function reconcileIntArrayAccess(
1606
        Codebase $codebase,
1607
        Union $existing_var_type,
1608
        ?string $key,
1609
        ?CodeLocation $code_location,
1610
        array $suppressed_issues,
1611
        int &$failed_reconciliation,
1612
        bool $inside_loop
1613
    ) : Union {
1614
        $old_var_type_string = $existing_var_type->getId();
1615
1616
        $existing_var_atomic_types = $existing_var_type->getAtomicTypes();
1617
1618
        if ($existing_var_type->hasMixed()) {
1619
            return Type::getMixed();
1620
        }
1621
1622
        $array_types = [];
1623
1624
        foreach ($existing_var_atomic_types as $type) {
1625
            if ($type->isArrayAccessibleWithIntOrStringKey($codebase)) {
1626
                if (get_class($type) === TArray::class) {
1627
                    $array_types[] = new Atomic\TNonEmptyArray($type->type_params);
1628
                } else {
1629
                    $array_types[] = $type;
1630
                }
1631
            } elseif ($type instanceof TTemplateParam) {
1632
                $array_types[] = $type;
1633
            }
1634
        }
1635
1636
        if (!$array_types) {
1637
            if ($key && $code_location) {
1638
                self::triggerIssueForImpossible(
1639
                    $existing_var_type,
1640
                    $old_var_type_string,
1641
                    $key,
1642
                    'int-or-string-array-access',
1643
                    true,
1644
                    $code_location,
1645
                    $suppressed_issues
1646
                );
1647
            }
1648
        }
1649
1650
        if ($array_types) {
1651
            return \Psalm\Internal\Type\TypeCombination::combineTypes($array_types);
1652
        }
1653
1654
        $failed_reconciliation = 2;
1655
1656
        return Type::getMixed($inside_loop);
1657
    }
1658
1659
    /**
1660
     * @param   string[]  $suppressed_issues
1661
     * @param   0|1|2    $failed_reconciliation
0 ignored issues
show
Documentation introduced by
The doc-type 0|1|2 could not be parsed: Unknown type name "0" at position 0. (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...
1662
     */
1663
    private static function reconcileCallable(
1664
        Codebase $codebase,
1665
        Union $existing_var_type,
1666
        ?string $key,
1667
        ?CodeLocation $code_location,
1668
        array $suppressed_issues,
1669
        int &$failed_reconciliation,
1670
        bool $is_equality
1671
    ) : Union {
1672
        if ($existing_var_type->hasMixed()) {
1673
            return Type::parseString('callable');
1674
        }
1675
1676
        $old_var_type_string = $existing_var_type->getId();
1677
1678
        $existing_var_atomic_types = $existing_var_type->getAtomicTypes();
1679
1680
        $callable_types = [];
1681
        $did_remove_type = false;
1682
1683
        foreach ($existing_var_atomic_types as $type) {
1684
            if ($type->isCallableType()) {
1685
                $callable_types[] = $type;
1686
            } elseif ($type instanceof TObject) {
1687
                $callable_types[] = new Type\Atomic\TCallableObject();
1688
                $did_remove_type = true;
1689
            } elseif ($type instanceof TNamedObject
1690
                && $codebase->classExists($type->value)
1691
                && $codebase->methodExists($type->value . '::__invoke')
1692
            ) {
1693
                $callable_types[] = $type;
1694
            } elseif (get_class($type) === TString::class
1695
                || get_class($type) === Type\Atomic\TNonEmptyString::class
1696
            ) {
1697
                $callable_types[] = new Type\Atomic\TCallableString();
1698
                $did_remove_type = true;
1699
            } elseif (get_class($type) === Type\Atomic\TLiteralString::class
1700
                && \Psalm\Internal\Codebase\InternalCallMapHandler::inCallMap($type->value)
1701
            ) {
1702
                $callable_types[] = $type;
1703
                $did_remove_type = true;
1704
            } elseif ($type instanceof TArray) {
1705
                $type = clone $type;
1706
                $type = new TCallableArray($type->type_params);
1707
                $callable_types[] = $type;
1708
                $did_remove_type = true;
1709
            } elseif ($type instanceof TList) {
1710
                $type = clone $type;
1711
                $type = new TCallableList($type->type_param);
1712
                $callable_types[] = $type;
1713
                $did_remove_type = true;
1714
            } elseif ($type instanceof ObjectLike) {
1715
                $type = clone $type;
1716
                $type = new TCallableObjectLikeArray($type->properties);
1717
                $callable_types[] = $type;
1718
                $did_remove_type = true;
1719
            } elseif ($type instanceof TTemplateParam) {
1720
                if ($type->as->isMixed()) {
1721
                    $type = clone $type;
1722
                    $type->as = new Type\Union([new Type\Atomic\TCallable]);
1723
                }
1724
                $callable_types[] = $type;
1725
                $did_remove_type = true;
1726
            } else {
1727
                $did_remove_type = true;
1728
            }
1729
        }
1730
1731
        if ((!$callable_types || !$did_remove_type) && !$is_equality) {
1732
            if ($key && $code_location) {
1733
                self::triggerIssueForImpossible(
1734
                    $existing_var_type,
1735
                    $old_var_type_string,
1736
                    $key,
1737
                    'callable',
1738
                    !$did_remove_type,
1739
                    $code_location,
1740
                    $suppressed_issues
1741
                );
1742
            }
1743
        }
1744
1745
        if ($callable_types) {
1746
            return \Psalm\Internal\Type\TypeCombination::combineTypes($callable_types);
1747
        }
1748
1749
        $failed_reconciliation = 2;
1750
1751
        return Type::getMixed();
1752
    }
1753
1754
    /**
1755
     * @param   string[]  $suppressed_issues
1756
     * @param   0|1|2    $failed_reconciliation
0 ignored issues
show
Documentation introduced by
The doc-type 0|1|2 could not be parsed: Unknown type name "0" at position 0. (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...
1757
     */
1758
    private static function reconcileFalsyOrEmpty(
1759
        string $assertion,
1760
        Union $existing_var_type,
1761
        ?string $key,
1762
        ?CodeLocation $code_location,
1763
        array $suppressed_issues,
1764
        int &$failed_reconciliation
1765
    ) : Union {
1766
        $old_var_type_string = $existing_var_type->getId();
1767
1768
        $existing_var_atomic_types = $existing_var_type->getAtomicTypes();
1769
1770
        $did_remove_type = $existing_var_type->hasDefinitelyNumericType(false)
1771
            || $existing_var_type->hasType('iterable');
1772
1773
        if ($existing_var_type->hasMixed()) {
1774
            if ($existing_var_type->isMixed()
1775
                && $existing_var_atomic_types['mixed'] instanceof Type\Atomic\TNonEmptyMixed
1776
            ) {
1777
                if ($code_location
1778
                    && $key
1779
                    && IssueBuffer::accepts(
1780
                        new ParadoxicalCondition(
1781
                            'Found a paradox when evaluating ' . $key
1782
                                . ' of type ' . $existing_var_type->getId()
1783
                                . ' and trying to reconcile it with a ' . $assertion . ' assertion',
1784
                            $code_location
1785
                        ),
1786
                        $suppressed_issues
1787
                    )
1788
                ) {
1789
                    // fall through
1790
                }
1791
1792
                return Type::getMixed();
1793
            }
1794
1795
            if (!$existing_var_atomic_types['mixed'] instanceof Type\Atomic\TEmptyMixed) {
1796
                $did_remove_type = true;
1797
                $existing_var_type->removeType('mixed');
1798
1799
                if (!$existing_var_atomic_types['mixed'] instanceof Type\Atomic\TNonEmptyMixed) {
1800
                    $existing_var_type->addType(new Type\Atomic\TEmptyMixed);
1801
                }
1802
            } elseif ($existing_var_type->isMixed()) {
1803
                if ($code_location
1804
                    && $key
1805
                    && IssueBuffer::accepts(
1806
                        new RedundantCondition(
1807
                            'Found a redundant condition when evaluating ' . $key
1808
                                . ' of type ' . $existing_var_type->getId()
1809
                                . ' and trying to reconcile it with a ' . $assertion . ' assertion',
1810
                            $code_location
1811
                        ),
1812
                        $suppressed_issues
1813
                    )
1814
                ) {
1815
                    // fall through
1816
                }
1817
            }
1818
1819
            if ($existing_var_type->isMixed()) {
1820
                return $existing_var_type;
1821
            }
1822
        }
1823
1824
        if ($existing_var_type->hasScalar()) {
1825
            if ($existing_var_type->isSingle()
1826
                && $existing_var_atomic_types['scalar'] instanceof Type\Atomic\TNonEmptyScalar
1827
            ) {
1828
                if ($code_location
1829
                    && $key
1830
                    && IssueBuffer::accepts(
1831
                        new ParadoxicalCondition(
1832
                            'Found a paradox when evaluating ' . $key
1833
                                . ' of type ' . $existing_var_type->getId()
1834
                                . ' and trying to reconcile it with a ' . $assertion . ' assertion',
1835
                            $code_location
1836
                        ),
1837
                        $suppressed_issues
1838
                    )
1839
                ) {
1840
                    // fall through
1841
                }
1842
1843
                return Type::getScalar();
1844
            }
1845
1846
            if (!$existing_var_atomic_types['scalar'] instanceof Type\Atomic\TEmptyScalar) {
1847
                $did_remove_type = true;
1848
                $existing_var_type->removeType('scalar');
1849
1850
                if (!$existing_var_atomic_types['scalar'] instanceof Type\Atomic\TNonEmptyScalar) {
1851
                    $existing_var_type->addType(new Type\Atomic\TEmptyScalar);
1852
                }
1853
            } elseif ($existing_var_type->isSingle()) {
1854
                if ($code_location
1855
                    && $key
1856
                    && IssueBuffer::accepts(
1857
                        new RedundantCondition(
1858
                            'Found a redundant condition when evaluating ' . $key
1859
                                . ' of type ' . $existing_var_type->getId()
1860
                                . ' and trying to reconcile it with a ' . $assertion . ' assertion',
1861
                            $code_location
1862
                        ),
1863
                        $suppressed_issues
1864
                    )
1865
                ) {
1866
                    // fall through
1867
                }
1868
            }
1869
1870
            if ($existing_var_type->isSingle()) {
1871
                return $existing_var_type;
1872
            }
1873
        }
1874
1875
        if ($existing_var_type->hasType('bool')) {
1876
            $did_remove_type = true;
1877
            $existing_var_type->removeType('bool');
1878
            $existing_var_type->addType(new TFalse);
1879
        }
1880
1881
        if ($existing_var_type->hasType('true')) {
1882
            $did_remove_type = true;
1883
            $existing_var_type->removeType('true');
1884
        }
1885
1886
        if ($existing_var_type->hasString()) {
1887
            $existing_string_types = $existing_var_type->getLiteralStrings();
1888
1889
            if ($existing_string_types) {
1890
                foreach ($existing_string_types as $string_key => $literal_type) {
1891
                    if ($literal_type->value) {
1892
                        $existing_var_type->removeType($string_key);
1893
                        $did_remove_type = true;
1894
                    }
1895
                }
1896
            } else {
1897
                $did_remove_type = true;
1898
                if ($existing_var_type->hasType('class-string')) {
1899
                    $existing_var_type->removeType('class-string');
1900
                }
1901
1902
                if ($existing_var_type->hasType('callable-string')) {
1903
                    $existing_var_type->removeType('callable-string');
1904
                }
1905
1906
                if ($existing_var_type->hasType('string')) {
1907
                    $existing_var_type->removeType('string');
1908
1909
                    if (!$existing_var_atomic_types['string'] instanceof Type\Atomic\TNonEmptyString) {
1910
                        $existing_var_type->addType(new Type\Atomic\TLiteralString(''));
1911
                        $existing_var_type->addType(new Type\Atomic\TLiteralString('0'));
1912
                    }
1913
                }
1914
            }
1915
        }
1916
1917
        if ($existing_var_type->hasInt()) {
1918
            $existing_int_types = $existing_var_type->getLiteralInts();
1919
1920
            if ($existing_int_types) {
1921
                foreach ($existing_int_types as $int_key => $literal_type) {
1922
                    if ($literal_type->value) {
1923
                        $existing_var_type->removeType($int_key);
1924
                        $did_remove_type = true;
1925
                    }
1926
                }
1927
            } else {
1928
                $did_remove_type = true;
1929
                $existing_var_type->removeType('int');
1930
                $existing_var_type->addType(new Type\Atomic\TLiteralInt(0));
1931
            }
1932
        }
1933
1934
        if ($existing_var_type->hasFloat()) {
1935
            $existing_float_types = $existing_var_type->getLiteralFloats();
1936
1937
            if ($existing_float_types) {
1938
                foreach ($existing_float_types as $float_key => $literal_type) {
1939
                    if ($literal_type->value) {
1940
                        $existing_var_type->removeType($float_key);
1941
                        $did_remove_type = true;
1942
                    }
1943
                }
1944
            } else {
1945
                $did_remove_type = true;
1946
                $existing_var_type->removeType('float');
1947
                $existing_var_type->addType(new Type\Atomic\TLiteralFloat(0));
1948
            }
1949
        }
1950
1951
        if ($existing_var_type->hasNumeric()) {
1952
            $existing_int_types = $existing_var_type->getLiteralInts();
1953
1954
            if ($existing_int_types) {
1955
                foreach ($existing_int_types as $int_key => $literal_type) {
1956
                    if ($literal_type->value) {
1957
                        $existing_var_type->removeType($int_key);
1958
                    }
1959
                }
1960
            }
1961
1962
            $existing_string_types = $existing_var_type->getLiteralStrings();
1963
1964
            if ($existing_string_types) {
1965
                foreach ($existing_string_types as $string_key => $literal_type) {
1966
                    if ($literal_type->value) {
1967
                        $existing_var_type->removeType($string_key);
1968
                    }
1969
                }
1970
            }
1971
1972
            $existing_float_types = $existing_var_type->getLiteralFloats();
1973
1974
            if ($existing_float_types) {
1975
                foreach ($existing_float_types as $float_key => $literal_type) {
1976
                    if ($literal_type->value) {
1977
                        $existing_var_type->removeType($float_key);
1978
                    }
1979
                }
1980
            }
1981
1982
            $did_remove_type = true;
1983
            $existing_var_type->removeType('numeric');
1984
            $existing_var_type->addType(new Type\Atomic\TEmptyNumeric);
1985
        }
1986
1987
        if (isset($existing_var_atomic_types['array'])) {
1988
            $array_atomic_type = $existing_var_atomic_types['array'];
1989
1990
            if ($array_atomic_type instanceof Type\Atomic\TNonEmptyArray
1991
                || $array_atomic_type instanceof Type\Atomic\TNonEmptyList
1992
                || ($array_atomic_type instanceof Type\Atomic\ObjectLike
1993
                    && array_filter(
1994
                        $array_atomic_type->properties,
1995
                        function (Type\Union $t) {
1996
                            return !$t->possibly_undefined;
1997
                        }
1998
                    ))
1999
            ) {
2000
                $did_remove_type = true;
2001
2002
                $existing_var_type->removeType('array');
2003
            } elseif ($array_atomic_type->getId() !== 'array<empty, empty>') {
2004
                $did_remove_type = true;
2005
2006
                $existing_var_type->addType(new TArray(
2007
                    [
2008
                        new Type\Union([new TEmpty]),
2009
                        new Type\Union([new TEmpty]),
2010
                    ]
2011
                ));
2012
            }
2013
        }
2014
2015
        if (isset($existing_var_atomic_types['scalar'])
2016
            && $existing_var_atomic_types['scalar']->getId() !== 'empty-scalar'
2017
        ) {
2018
            $did_remove_type = true;
2019
            $existing_var_type->addType(new Type\Atomic\TEmptyScalar);
2020
        }
2021
2022
        foreach ($existing_var_atomic_types as $type_key => $type) {
2023
            if ($type instanceof TNamedObject
2024
                || $type instanceof TObject
2025
                || $type instanceof TResource
2026
                || $type instanceof TCallable
2027
                || $type instanceof TClassString
2028
            ) {
2029
                $did_remove_type = true;
2030
2031
                $existing_var_type->removeType($type_key);
2032
            }
2033
2034
            if ($type instanceof TTemplateParam) {
2035
                $did_remove_type = true;
2036
            }
2037
        }
2038
2039
        if ((!$did_remove_type || empty($existing_var_type->getAtomicTypes()))
2040
            && ($assertion !== 'empty' || !$existing_var_type->possibly_undefined)
2041
        ) {
2042
            if ($key && $code_location) {
2043
                self::triggerIssueForImpossible(
2044
                    $existing_var_type,
2045
                    $old_var_type_string,
2046
                    $key,
2047
                    $assertion,
2048
                    !$did_remove_type,
2049
                    $code_location,
2050
                    $suppressed_issues
2051
                );
2052
            }
2053
        }
2054
2055
        if ($existing_var_type->getAtomicTypes()) {
2056
            return $existing_var_type;
2057
        }
2058
2059
        $failed_reconciliation = 2;
2060
2061
        return $assertion === 'empty' && $existing_var_type->possibly_undefined
2062
            ? Type::getEmpty()
2063
            : Type::getMixed();
2064
    }
2065
}
2066