SimpleNegatedAssertionReconciler   F
last analyzed

Complexity

Total Complexity 284

Size/Duplication

Total Lines 1439
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 30

Importance

Changes 0
Metric Value
dl 0
loc 1439
rs 0.8
c 0
b 0
f 0
wmc 284
lcom 1
cbo 30

15 Methods

Rating   Name   Duplication   Size   Complexity  
F reconcile() 0 165 26
A reconcileCallable() 0 17 5
C reconcileBool() 0 61 15
C reconcileNonEmptyCountable() 0 64 16
C reconcileNull() 0 58 11
C reconcileFalse() 0 58 11
F reconcileFalsyOrEmpty() 0 264 65
C reconcileScalar() 0 78 14
C reconcileObject() 0 85 15
D reconcileNumeric() 0 82 17
D reconcileInt() 0 88 18
D reconcileFloat() 0 83 16
F reconcileString() 0 96 18
D reconcileArray() 0 93 20
C removeFalsyNegatedLiteralTypes() 0 68 17

How to fix   Complexity   

Complex Class

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

1
<?php
2
3
namespace Psalm\Internal\Type;
4
5
use function get_class;
6
use Psalm\CodeLocation;
7
use Psalm\Issue\ParadoxicalCondition;
8
use Psalm\Issue\RedundantCondition;
9
use Psalm\IssueBuffer;
10
use Psalm\Type;
11
use Psalm\Type\Atomic;
12
use Psalm\Type\Atomic\ObjectLike;
13
use Psalm\Type\Atomic\Scalar;
14
use Psalm\Type\Atomic\TArray;
15
use Psalm\Type\Atomic\TArrayKey;
16
use Psalm\Type\Atomic\TBool;
17
use Psalm\Type\Atomic\TCallable;
18
use Psalm\Type\Atomic\TEmpty;
19
use Psalm\Type\Atomic\TFloat;
20
use Psalm\Type\Atomic\TInt;
21
use Psalm\Type\Atomic\TNamedObject;
22
use Psalm\Type\Atomic\TNumeric;
23
use Psalm\Type\Atomic\TScalar;
24
use Psalm\Type\Atomic\TString;
25
use Psalm\Type\Atomic\TTemplateParam;
26
use Psalm\Type\Atomic\TTrue;
27
use Psalm\Type\Reconciler;
28
use function substr;
29
30
class SimpleNegatedAssertionReconciler extends Reconciler
31
{
32
    /**
33
     * @param  array<string, array<string, array{Type\Union}>> $template_type_map
34
     * @param  string[]   $suppressed_issues
35
     * @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...
36
     *
37
     * @return Type\Union
38
     */
39
    public static function reconcile(
40
        string $assertion,
41
        Type\Union $existing_var_type,
42
        ?string $key = null,
43
        ?CodeLocation $code_location = null,
44
        array $suppressed_issues = [],
45
        int &$failed_reconciliation = 0,
46
        bool $is_equality = false,
47
        bool $is_strict_equality = false
48
    ) : ?Type\Union {
49
        if ($assertion === 'object' && !$existing_var_type->hasMixed()) {
50
            return self::reconcileObject(
51
                $existing_var_type,
52
                $key,
53
                $code_location,
54
                $suppressed_issues,
55
                $failed_reconciliation,
56
                $is_equality
57
            );
58
        }
59
60
        if ($assertion === 'scalar' && !$existing_var_type->hasMixed()) {
61
            return self::reconcileScalar(
62
                $existing_var_type,
63
                $key,
64
                $code_location,
65
                $suppressed_issues,
66
                $failed_reconciliation,
67
                $is_equality
68
            );
69
        }
70
71
        if ($assertion === 'bool' && !$existing_var_type->hasMixed()) {
72
            return self::reconcileBool(
73
                $existing_var_type,
74
                $key,
75
                $code_location,
76
                $suppressed_issues,
77
                $failed_reconciliation,
78
                $is_equality
79
            );
80
        }
81
82
        if ($assertion === 'numeric' && !$existing_var_type->hasMixed()) {
83
            return self::reconcileNumeric(
84
                $existing_var_type,
85
                $key,
86
                $code_location,
87
                $suppressed_issues,
88
                $failed_reconciliation,
89
                $is_equality
90
            );
91
        }
92
93
        if ($assertion === 'float' && !$existing_var_type->hasMixed()) {
94
            return self::reconcileFloat(
95
                $existing_var_type,
96
                $key,
97
                $code_location,
98
                $suppressed_issues,
99
                $failed_reconciliation,
100
                $is_equality
101
            );
102
        }
103
104
        if ($assertion === 'int' && !$existing_var_type->hasMixed()) {
105
            return self::reconcileInt(
106
                $existing_var_type,
107
                $key,
108
                $code_location,
109
                $suppressed_issues,
110
                $failed_reconciliation,
111
                $is_equality
112
            );
113
        }
114
115
        if ($assertion === 'string' && !$existing_var_type->hasMixed()) {
116
            return self::reconcileString(
117
                $existing_var_type,
118
                $key,
119
                $code_location,
120
                $suppressed_issues,
121
                $failed_reconciliation,
122
                $is_equality
123
            );
124
        }
125
126
        if ($assertion === 'array' && !$existing_var_type->hasMixed()) {
127
            return self::reconcileArray(
128
                $existing_var_type,
129
                $key,
130
                $code_location,
131
                $suppressed_issues,
132
                $failed_reconciliation,
133
                $is_equality
134
            );
135
        }
136
137
        if ($assertion === 'falsy' || $assertion === 'empty') {
138
            return self::reconcileFalsyOrEmpty(
139
                $assertion,
140
                $existing_var_type,
141
                $key,
142
                $code_location,
143
                $suppressed_issues,
144
                $failed_reconciliation,
145
                $is_equality,
146
                $is_strict_equality
147
            );
148
        }
149
150
        if ($assertion === 'null' && !$existing_var_type->hasMixed()) {
151
            return self::reconcileNull(
152
                $existing_var_type,
153
                $key,
154
                $code_location,
155
                $suppressed_issues,
156
                $failed_reconciliation,
157
                $is_equality
158
            );
159
        }
160
161
        if ($assertion === 'false' && !$existing_var_type->hasMixed()) {
162
            return self::reconcileFalse(
163
                $existing_var_type,
164
                $key,
165
                $code_location,
166
                $suppressed_issues,
167
                $failed_reconciliation,
168
                $is_equality
169
            );
170
        }
171
172
        if ($assertion === 'non-empty-countable') {
173
            return self::reconcileNonEmptyCountable(
174
                $existing_var_type,
175
                $key,
176
                $code_location,
177
                $suppressed_issues,
178
                $failed_reconciliation,
179
                $is_equality,
180
                null
181
            );
182
        }
183
184
        if ($assertion === 'callable') {
185
            return self::reconcileCallable(
186
                $existing_var_type
187
            );
188
        }
189
190
        if (substr($assertion, 0, 13) === 'has-at-least-') {
191
            return self::reconcileNonEmptyCountable(
192
                $existing_var_type,
193
                $key,
194
                $code_location,
195
                $suppressed_issues,
196
                $failed_reconciliation,
197
                $is_equality,
198
                (int) substr($assertion, 13)
199
            );
200
        }
201
202
        return null;
203
    }
204
205
    /**
206
     * @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...
207
     * @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...
208
     */
209
    private static function reconcileCallable(
210
        Type\Union $existing_var_type
211
    ) : Type\Union {
212
        foreach ($existing_var_type->getAtomicTypes() as $atomic_key => $type) {
213
            if ($type instanceof Type\Atomic\TLiteralString
214
                && \Psalm\Internal\Codebase\InternalCallMapHandler::inCallMap($type->value)
215
            ) {
216
                $existing_var_type->removeType($atomic_key);
217
            }
218
219
            if ($type->isCallableType()) {
220
                $existing_var_type->removeType($atomic_key);
221
            }
222
        }
223
224
        return $existing_var_type;
225
    }
226
227
    /**
228
     * @param   string[]  $suppressed_issues
229
     * @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...
230
     */
231
    private static function reconcileBool(
232
        Type\Union $existing_var_type,
233
        ?string $key,
234
        ?CodeLocation $code_location,
235
        array $suppressed_issues,
236
        int &$failed_reconciliation,
237
        bool $is_equality
238
    ) : Type\Union {
239
        $old_var_type_string = $existing_var_type->getId();
240
        $non_bool_types = [];
241
        $did_remove_type = false;
242
243
        foreach ($existing_var_type->getAtomicTypes() as $type) {
244
            if ($type instanceof TTemplateParam) {
245
                if (!$type->as->hasBool()) {
246
                    $non_bool_types[] = $type;
247
                }
248
249
                $did_remove_type = true;
250
            } elseif (!$type instanceof TBool
251
                || ($is_equality && get_class($type) === TBool::class)
252
            ) {
253
                if ($type instanceof TScalar) {
254
                    $did_remove_type = true;
255
                    $non_bool_types[] = new TString();
256
                    $non_bool_types[] = new TInt();
257
                    $non_bool_types[] = new TFloat();
258
                } else {
259
                    $non_bool_types[] = $type;
260
                }
261
            } else {
262
                $did_remove_type = true;
263
            }
264
        }
265
266
        if (!$did_remove_type || !$non_bool_types) {
267
            if ($key && $code_location && !$is_equality) {
268
                self::triggerIssueForImpossible(
269
                    $existing_var_type,
270
                    $old_var_type_string,
271
                    $key,
272
                    '!bool',
273
                    !$did_remove_type,
274
                    $code_location,
275
                    $suppressed_issues
276
                );
277
            }
278
279
            if (!$did_remove_type) {
280
                $failed_reconciliation = 1;
281
            }
282
        }
283
284
        if ($non_bool_types) {
285
            return new Type\Union($non_bool_types);
286
        }
287
288
        $failed_reconciliation = 2;
289
290
        return Type::getMixed();
291
    }
292
293
    /**
294
     * @param   string[]  $suppressed_issues
295
     * @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...
296
     */
297
    private static function reconcileNonEmptyCountable(
298
        Type\Union $existing_var_type,
299
        ?string $key,
300
        ?CodeLocation $code_location,
301
        array $suppressed_issues,
302
        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...
303
        bool $is_equality,
304
        ?int $min_count
305
    ) : Type\Union {
306
        $old_var_type_string = $existing_var_type->getId();
307
        $existing_var_atomic_types = $existing_var_type->getAtomicTypes();
308
309
        if (isset($existing_var_atomic_types['array'])) {
310
            $array_atomic_type = $existing_var_atomic_types['array'];
311
            $did_remove_type = false;
312
313
            if (($array_atomic_type instanceof Type\Atomic\TNonEmptyArray
314
                    || $array_atomic_type instanceof Type\Atomic\TNonEmptyList)
315
                && ($min_count === null
316
                    || $array_atomic_type->count >= $min_count)
317
            ) {
318
                $did_remove_type = true;
319
320
                $existing_var_type->removeType('array');
321
            } elseif ($array_atomic_type->getId() !== 'array<empty, empty>') {
322
                $did_remove_type = true;
323
324
                $existing_var_type->addType(new TArray(
325
                    [
326
                        new Type\Union([new TEmpty]),
327
                        new Type\Union([new TEmpty]),
328
                    ]
329
                ));
330
            } elseif ($array_atomic_type instanceof Type\Atomic\ObjectLike) {
331
                $did_remove_type = true;
332
333
                foreach ($array_atomic_type->properties as $property_type) {
334
                    if (!$property_type->possibly_undefined) {
335
                        $did_remove_type = false;
336
                        break;
337
                    }
338
                }
339
            }
340
341
            if (!$is_equality
342
                && !$existing_var_type->hasMixed()
343
                && (!$did_remove_type || empty($existing_var_type->getAtomicTypes()))
344
            ) {
345
                if ($key && $code_location) {
346
                    self::triggerIssueForImpossible(
347
                        $existing_var_type,
348
                        $old_var_type_string,
349
                        $key,
350
                        '!non-empty-countable',
351
                        !$did_remove_type,
352
                        $code_location,
353
                        $suppressed_issues
354
                    );
355
                }
356
            }
357
        }
358
359
        return $existing_var_type;
360
    }
361
362
    /**
363
     * @param   string[]  $suppressed_issues
364
     * @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...
365
     */
366
    private static function reconcileNull(
367
        Type\Union $existing_var_type,
368
        ?string $key,
369
        ?CodeLocation $code_location,
370
        array $suppressed_issues,
371
        int &$failed_reconciliation,
372
        bool $is_equality
373
    ) : Type\Union {
374
        $old_var_type_string = $existing_var_type->getId();
375
        $did_remove_type = false;
376
377
        if ($existing_var_type->hasType('null')) {
378
            $did_remove_type = true;
379
            $existing_var_type->removeType('null');
380
        }
381
382
        foreach ($existing_var_type->getAtomicTypes() as $type) {
383
            if ($type instanceof TTemplateParam) {
384
                $type->as = self::reconcileNull(
385
                    $type->as,
386
                    null,
387
                    null,
388
                    $suppressed_issues,
389
                    $failed_reconciliation,
390
                    $is_equality
391
                );
392
393
                $did_remove_type = true;
394
                $existing_var_type->bustCache();
395
            }
396
        }
397
398
        if (!$did_remove_type || empty($existing_var_type->getAtomicTypes())) {
399
            if ($key && $code_location && !$is_equality) {
400
                self::triggerIssueForImpossible(
401
                    $existing_var_type,
402
                    $old_var_type_string,
403
                    $key,
404
                    '!null',
405
                    !$did_remove_type,
406
                    $code_location,
407
                    $suppressed_issues
408
                );
409
            }
410
411
            if (!$did_remove_type) {
412
                $failed_reconciliation = 1;
413
            }
414
        }
415
416
        if ($existing_var_type->getAtomicTypes()) {
417
            return $existing_var_type;
418
        }
419
420
        $failed_reconciliation = 2;
421
422
        return Type::getMixed();
423
    }
424
425
    /**
426
     * @param   string[]  $suppressed_issues
427
     * @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...
428
     */
429
    private static function reconcileFalse(
430
        Type\Union $existing_var_type,
431
        ?string $key,
432
        ?CodeLocation $code_location,
433
        array $suppressed_issues,
434
        int &$failed_reconciliation,
435
        bool $is_equality
436
    ) : Type\Union {
437
        $old_var_type_string = $existing_var_type->getId();
438
        $did_remove_type = false;
439
440
        if ($existing_var_type->hasType('false')) {
441
            $did_remove_type = true;
442
            $existing_var_type->removeType('false');
443
        }
444
445
        foreach ($existing_var_type->getAtomicTypes() as $type) {
446
            if ($type instanceof TTemplateParam) {
447
                $type->as = self::reconcileFalse(
448
                    $type->as,
449
                    null,
450
                    null,
451
                    $suppressed_issues,
452
                    $failed_reconciliation,
453
                    $is_equality
454
                );
455
456
                $did_remove_type = true;
457
                $existing_var_type->bustCache();
458
            }
459
        }
460
461
        if (!$did_remove_type || empty($existing_var_type->getAtomicTypes())) {
462
            if ($key && $code_location && !$is_equality) {
463
                self::triggerIssueForImpossible(
464
                    $existing_var_type,
465
                    $old_var_type_string,
466
                    $key,
467
                    '!false',
468
                    !$did_remove_type,
469
                    $code_location,
470
                    $suppressed_issues
471
                );
472
            }
473
474
            if (!$did_remove_type) {
475
                $failed_reconciliation = 1;
476
            }
477
        }
478
479
        if ($existing_var_type->getAtomicTypes()) {
480
            return $existing_var_type;
481
        }
482
483
        $failed_reconciliation = 2;
484
485
        return Type::getMixed();
486
    }
487
488
    /**
489
     * @param   string[]  $suppressed_issues
490
     * @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...
491
     */
492
    private static function reconcileFalsyOrEmpty(
493
        string $assertion,
494
        Type\Union $existing_var_type,
495
        ?string $key,
496
        ?CodeLocation $code_location,
497
        array $suppressed_issues,
498
        int &$failed_reconciliation,
499
        bool $is_equality,
500
        bool $is_strict_equality
501
    ) : Type\Union {
502
        $old_var_type_string = $existing_var_type->getId();
503
        $existing_var_atomic_types = $existing_var_type->getAtomicTypes();
504
505
        $did_remove_type = $existing_var_type->hasDefinitelyNumericType(false)
506
            || $existing_var_type->isEmpty()
507
            || $existing_var_type->hasType('bool')
508
            || $existing_var_type->possibly_undefined
509
            || $existing_var_type->possibly_undefined_from_try
510
            || $existing_var_type->hasType('iterable');
511
512
        if ($is_strict_equality && $assertion === 'empty') {
513
            $existing_var_type->removeType('null');
514
            $existing_var_type->removeType('false');
515
516
            if ($existing_var_type->hasType('array')
517
                && $existing_var_type->getAtomicTypes()['array']->getId() === 'array<empty, empty>'
518
            ) {
519
                $existing_var_type->removeType('array');
520
            }
521
522
            if ($existing_var_type->hasMixed()) {
523
                $existing_var_type->removeType('mixed');
524
525
                if (!$existing_var_atomic_types['mixed'] instanceof Type\Atomic\TEmptyMixed) {
526
                    $existing_var_type->addType(new Type\Atomic\TNonEmptyMixed);
527
                }
528
            }
529
530
            if ($existing_var_type->hasScalar()) {
531
                $existing_var_type->removeType('scalar');
532
533
                if (!$existing_var_atomic_types['scalar'] instanceof Type\Atomic\TEmptyScalar) {
534
                    $existing_var_type->addType(new Type\Atomic\TNonEmptyScalar);
535
                }
536
            }
537
538
            if (isset($existing_var_atomic_types['string'])) {
539
                $existing_var_type->removeType('string');
540
541
                if ($existing_var_atomic_types['string'] instanceof Type\Atomic\TLowercaseString) {
542
                    $existing_var_type->addType(new Type\Atomic\TNonEmptyLowercaseString);
543
                } else {
544
                    $existing_var_type->addType(new Type\Atomic\TNonEmptyString);
545
                }
546
            }
547
548
            self::removeFalsyNegatedLiteralTypes(
549
                $existing_var_type,
550
                $did_remove_type
551
            );
552
553
            $existing_var_type->possibly_undefined = false;
554
            $existing_var_type->possibly_undefined_from_try = false;
555
556
            if ($existing_var_type->getAtomicTypes()) {
557
                return $existing_var_type;
558
            }
559
560
            $failed_reconciliation = 2;
561
562
            return Type::getMixed();
563
        }
564
565
        if ($existing_var_type->hasMixed()) {
566
            if ($existing_var_type->isMixed()
567
                && $existing_var_atomic_types['mixed'] instanceof Type\Atomic\TEmptyMixed
568
            ) {
569
                if ($code_location
570
                    && $key
571
                    && IssueBuffer::accepts(
572
                        new ParadoxicalCondition(
573
                            'Found a paradox when evaluating ' . $key
574
                                . ' of type ' . $existing_var_type->getId()
575
                                . ' and trying to reconcile it with a non-' . $assertion . ' assertion',
576
                            $code_location
577
                        ),
578
                        $suppressed_issues
579
                    )
580
                ) {
581
                    // fall through
582
                }
583
584
                return Type::getMixed();
585
            }
586
587
            if (!$existing_var_atomic_types['mixed'] instanceof Type\Atomic\TNonEmptyMixed) {
588
                $did_remove_type = true;
589
                $existing_var_type->removeType('mixed');
590
591
                if (!$existing_var_atomic_types['mixed'] instanceof Type\Atomic\TEmptyMixed) {
592
                    $existing_var_type->addType(new Type\Atomic\TNonEmptyMixed);
593
                }
594
            } elseif ($existing_var_type->isMixed() && !$is_equality) {
595
                if ($code_location
596
                    && $key
597
                    && IssueBuffer::accepts(
598
                        new RedundantCondition(
599
                            'Found a redundant condition when evaluating ' . $key
600
                                . ' of type ' . $existing_var_type->getId()
601
                                . ' and trying to reconcile it with a non-' . $assertion . ' assertion',
602
                            $code_location
603
                        ),
604
                        $suppressed_issues
605
                    )
606
                ) {
607
                    // fall through
608
                }
609
            }
610
611
            if ($existing_var_type->isMixed()) {
612
                return $existing_var_type;
613
            }
614
        }
615
616
        if ($existing_var_type->hasScalar()) {
617
            if (!$existing_var_atomic_types['scalar'] instanceof Type\Atomic\TNonEmptyScalar) {
618
                $did_remove_type = true;
619
                $existing_var_type->removeType('scalar');
620
621
                if (!$existing_var_atomic_types['scalar'] instanceof Type\Atomic\TEmptyScalar) {
622
                    $existing_var_type->addType(new Type\Atomic\TNonEmptyScalar);
623
                }
624
            } elseif ($existing_var_type->isSingle() && !$is_equality) {
625
                if ($code_location
626
                    && $key
627
                    && IssueBuffer::accepts(
628
                        new RedundantCondition(
629
                            'Found a redundant condition when evaluating ' . $key
630
                                . ' of type ' . $existing_var_type->getId()
631
                                . ' and trying to reconcile it with a non-' . $assertion . ' assertion',
632
                            $code_location
633
                        ),
634
                        $suppressed_issues
635
                    )
636
                ) {
637
                    // fall through
638
                }
639
            }
640
641
            if ($existing_var_type->isSingle()) {
642
                return $existing_var_type;
643
            }
644
        }
645
646
        if (isset($existing_var_atomic_types['string'])) {
647
            if (!$existing_var_atomic_types['string'] instanceof Type\Atomic\TNonEmptyString) {
648
                $did_remove_type = true;
649
650
                $existing_var_type->removeType('string');
651
652
                if ($existing_var_atomic_types['string'] instanceof Type\Atomic\TLowercaseString) {
653
                    $existing_var_type->addType(new Type\Atomic\TNonEmptyLowercaseString);
654
                } else {
655
                    $existing_var_type->addType(new Type\Atomic\TNonEmptyString);
656
                }
657
            } elseif ($existing_var_type->isSingle() && !$is_equality) {
658
                if ($code_location
659
                    && $key
660
                    && IssueBuffer::accepts(
661
                        new RedundantCondition(
662
                            'Found a redundant condition when evaluating ' . $key
663
                                . ' of type ' . $existing_var_type->getId()
664
                                . ' and trying to reconcile it with a non-' . $assertion . ' assertion',
665
                            $code_location
666
                        ),
667
                        $suppressed_issues
668
                    )
669
                ) {
670
                    // fall through
671
                }
672
            }
673
674
            if ($existing_var_type->isSingle()) {
675
                return $existing_var_type;
676
            }
677
        }
678
679
        if ($existing_var_type->hasType('null')) {
680
            $did_remove_type = true;
681
            $existing_var_type->removeType('null');
682
        }
683
684
        if ($existing_var_type->hasType('false')) {
685
            $did_remove_type = true;
686
            $existing_var_type->removeType('false');
687
        }
688
689
        if ($existing_var_type->hasType('bool')) {
690
            $did_remove_type = true;
691
            $existing_var_type->removeType('bool');
692
            $existing_var_type->addType(new TTrue);
693
        }
694
695
        foreach ($existing_var_atomic_types as $existing_var_atomic_type) {
696
            if ($existing_var_atomic_type instanceof Type\Atomic\TTemplateParam) {
697
                if (!$is_equality && !$existing_var_atomic_type->as->isMixed()) {
698
                    $template_did_fail = 0;
699
700
                    $existing_var_atomic_type = clone $existing_var_atomic_type;
701
702
                    $existing_var_atomic_type->as = self::reconcileFalsyOrEmpty(
703
                        $assertion,
704
                        $existing_var_atomic_type->as,
705
                        $key,
706
                        $code_location,
707
                        $suppressed_issues,
708
                        $template_did_fail,
709
                        $is_equality,
710
                        $is_strict_equality
711
                    );
712
713
                    $did_remove_type = true;
714
715
                    if (!$template_did_fail) {
716
                        $existing_var_type->addType($existing_var_atomic_type);
717
                    }
718
                }
719
            }
720
        }
721
722
        self::removeFalsyNegatedLiteralTypes(
723
            $existing_var_type,
724
            $did_remove_type
725
        );
726
727
        $existing_var_type->possibly_undefined = false;
728
        $existing_var_type->possibly_undefined_from_try = false;
729
730
        if ((!$did_remove_type || empty($existing_var_type->getAtomicTypes())) && !$existing_var_type->hasTemplate()) {
731
            if ($key && $code_location && !$is_equality) {
732
                self::triggerIssueForImpossible(
733
                    $existing_var_type,
734
                    $old_var_type_string,
735
                    $key,
736
                    '!' . $assertion,
737
                    !$did_remove_type,
738
                    $code_location,
739
                    $suppressed_issues
740
                );
741
            }
742
743
            if (!$did_remove_type) {
744
                $failed_reconciliation = 1;
745
            }
746
        }
747
748
        if ($existing_var_type->getAtomicTypes()) {
749
            return $existing_var_type;
750
        }
751
752
        $failed_reconciliation = 2;
753
754
        return Type::getEmpty();
755
    }
756
757
    /**
758
     * @param   string[]  $suppressed_issues
759
     * @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...
760
     */
761
    private static function reconcileScalar(
762
        Type\Union $existing_var_type,
763
        ?string $key,
764
        ?CodeLocation $code_location,
765
        array $suppressed_issues,
766
        int &$failed_reconciliation,
767
        bool $is_equality
768
    ) : Type\Union {
769
        $old_var_type_string = $existing_var_type->getId();
770
        $non_scalar_types = [];
771
        $did_remove_type = false;
772
773
        foreach ($existing_var_type->getAtomicTypes() as $type) {
774
            if ($type instanceof TTemplateParam) {
775
                if (!$is_equality && !$type->as->isMixed()) {
776
                    $template_did_fail = 0;
777
778
                    $type = clone $type;
779
780
                    $type->as = self::reconcileScalar(
781
                        $type->as,
782
                        null,
783
                        null,
784
                        $suppressed_issues,
785
                        $template_did_fail,
786
                        $is_equality
787
                    );
788
789
                    $did_remove_type = true;
790
791
                    if (!$template_did_fail) {
792
                        $non_scalar_types[] = $type;
793
                    }
794
                } else {
795
                    $did_remove_type = true;
796
                    $non_scalar_types[] = $type;
797
                }
798
            } elseif (!($type instanceof Scalar)) {
799
                $non_scalar_types[] = $type;
800
            } else {
801
                $did_remove_type = true;
802
803
                if ($is_equality) {
804
                    $non_scalar_types[] = $type;
805
                }
806
            }
807
        }
808
809
        if (!$did_remove_type || !$non_scalar_types) {
810
            if ($key && $code_location) {
811
                self::triggerIssueForImpossible(
812
                    $existing_var_type,
813
                    $old_var_type_string,
814
                    $key,
815
                    '!scalar',
816
                    !$did_remove_type,
817
                    $code_location,
818
                    $suppressed_issues
819
                );
820
            }
821
822
            if (!$did_remove_type) {
823
                $failed_reconciliation = 1;
824
            }
825
        }
826
827
        if ($non_scalar_types) {
828
            $type = new Type\Union($non_scalar_types);
829
            $type->ignore_falsable_issues = $existing_var_type->ignore_falsable_issues;
830
            $type->ignore_nullable_issues = $existing_var_type->ignore_nullable_issues;
831
            $type->from_docblock = $existing_var_type->from_docblock;
832
            return $type;
833
        }
834
835
        $failed_reconciliation = 2;
836
837
        return Type::getMixed();
838
    }
839
840
    /**
841
     * @param   string[]  $suppressed_issues
842
     * @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...
843
     */
844
    private static function reconcileObject(
845
        Type\Union $existing_var_type,
846
        ?string $key,
847
        ?CodeLocation $code_location,
848
        array $suppressed_issues,
849
        int &$failed_reconciliation,
850
        bool $is_equality
851
    ) : Type\Union {
852
        $old_var_type_string = $existing_var_type->getId();
853
        $non_object_types = [];
854
        $did_remove_type = false;
855
856
        foreach ($existing_var_type->getAtomicTypes() as $type) {
857
            if ($type instanceof TTemplateParam) {
858
                if (!$is_equality && !$type->as->isMixed()) {
859
                    $template_did_fail = 0;
860
861
                    $type = clone $type;
862
863
                    $type->as = self::reconcileObject(
864
                        $type->as,
865
                        null,
866
                        null,
867
                        $suppressed_issues,
868
                        $template_did_fail,
869
                        $is_equality
870
                    );
871
872
                    $did_remove_type = true;
873
874
                    if (!$template_did_fail) {
875
                        $non_object_types[] = $type;
876
                    }
877
                } else {
878
                    $did_remove_type = true;
879
                    $non_object_types[] = $type;
880
                }
881
            } elseif ($type instanceof TCallable) {
882
                $non_object_types[] = new Atomic\TCallableArray([
883
                    Type::getArrayKey(),
884
                    Type::getMixed()
885
                ]);
886
                $non_object_types[] = new Atomic\TCallableString();
887
                $did_remove_type = true;
888
            } elseif (!$type->isObjectType()) {
889
                $non_object_types[] = $type;
890
            } else {
891
                $did_remove_type = true;
892
893
                if ($is_equality) {
894
                    $non_object_types[] = $type;
895
                }
896
            }
897
        }
898
899
        if (!$non_object_types || !$did_remove_type) {
900
            if ($key && $code_location) {
901
                self::triggerIssueForImpossible(
902
                    $existing_var_type,
903
                    $old_var_type_string,
904
                    $key,
905
                    '!object',
906
                    !$did_remove_type,
907
                    $code_location,
908
                    $suppressed_issues
909
                );
910
            }
911
912
            if (!$did_remove_type) {
913
                $failed_reconciliation = 1;
914
            }
915
        }
916
917
        if ($non_object_types) {
918
            $type = new Type\Union($non_object_types);
919
            $type->ignore_falsable_issues = $existing_var_type->ignore_falsable_issues;
920
            $type->ignore_nullable_issues = $existing_var_type->ignore_nullable_issues;
921
            $type->from_docblock = $existing_var_type->from_docblock;
922
            return $type;
923
        }
924
925
        $failed_reconciliation = 2;
926
927
        return Type::getMixed();
928
    }
929
930
    /**
931
     * @param   string[]  $suppressed_issues
932
     * @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...
933
     */
934
    private static function reconcileNumeric(
935
        Type\Union $existing_var_type,
936
        ?string $key,
937
        ?CodeLocation $code_location,
938
        array $suppressed_issues,
939
        int &$failed_reconciliation,
940
        bool $is_equality
941
    ) : Type\Union {
942
        $old_var_type_string = $existing_var_type->getId();
943
        $non_numeric_types = [];
944
        $did_remove_type = $existing_var_type->hasString()
945
            || $existing_var_type->hasScalar();
946
947
        foreach ($existing_var_type->getAtomicTypes() as $type) {
948
            if ($type instanceof TTemplateParam) {
949
                if (!$is_equality && !$type->as->isMixed()) {
950
                    $template_did_fail = 0;
951
952
                    $type = clone $type;
953
954
                    $type->as = self::reconcileNumeric(
955
                        $type->as,
956
                        null,
957
                        null,
958
                        $suppressed_issues,
959
                        $template_did_fail,
960
                        $is_equality
961
                    );
962
963
                    $did_remove_type = true;
964
965
                    if (!$template_did_fail) {
966
                        $non_numeric_types[] = $type;
967
                    }
968
                } else {
969
                    $did_remove_type = true;
970
                    $non_numeric_types[] = $type;
971
                }
972
            } elseif ($type instanceof TArrayKey) {
973
                $did_remove_type = true;
974
                $non_numeric_types[] = new TString();
975
            } elseif (!$type->isNumericType()) {
976
                $non_numeric_types[] = $type;
977
            } else {
978
                $did_remove_type = true;
979
980
                if ($is_equality) {
981
                    $non_numeric_types[] = $type;
982
                }
983
            }
984
        }
985
986
        if (!$non_numeric_types || !$did_remove_type) {
987
            if ($key && $code_location && !$is_equality) {
988
                self::triggerIssueForImpossible(
989
                    $existing_var_type,
990
                    $old_var_type_string,
991
                    $key,
992
                    '!numeric',
993
                    !$did_remove_type,
994
                    $code_location,
995
                    $suppressed_issues
996
                );
997
            }
998
999
            if (!$did_remove_type) {
1000
                $failed_reconciliation = 1;
1001
            }
1002
        }
1003
1004
        if ($non_numeric_types) {
1005
            $type = new Type\Union($non_numeric_types);
1006
            $type->ignore_falsable_issues = $existing_var_type->ignore_falsable_issues;
1007
            $type->ignore_nullable_issues = $existing_var_type->ignore_nullable_issues;
1008
            $type->from_docblock = $existing_var_type->from_docblock;
1009
            return $type;
1010
        }
1011
1012
        $failed_reconciliation = 2;
1013
1014
        return Type::getMixed();
1015
    }
1016
1017
    /**
1018
     * @param   string[]  $suppressed_issues
1019
     * @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...
1020
     */
1021
    private static function reconcileInt(
1022
        Type\Union $existing_var_type,
1023
        ?string $key,
1024
        ?CodeLocation $code_location,
1025
        array $suppressed_issues,
1026
        int &$failed_reconciliation,
1027
        bool $is_equality
1028
    ) : Type\Union {
1029
        $old_var_type_string = $existing_var_type->getId();
1030
        $non_int_types = [];
1031
        $did_remove_type = false;
1032
1033
        foreach ($existing_var_type->getAtomicTypes() as $type) {
1034
            if ($type instanceof TTemplateParam) {
1035
                if (!$is_equality && !$type->as->isMixed()) {
1036
                    $template_did_fail = 0;
1037
1038
                    $type = clone $type;
1039
1040
                    $type->as = self::reconcileInt(
1041
                        $type->as,
1042
                        null,
1043
                        null,
1044
                        $suppressed_issues,
1045
                        $template_did_fail,
1046
                        $is_equality
1047
                    );
1048
1049
                    $did_remove_type = true;
1050
1051
                    if (!$template_did_fail) {
1052
                        $non_int_types[] = $type;
1053
                    }
1054
                } else {
1055
                    $did_remove_type = true;
1056
                    $non_int_types[] = $type;
1057
                }
1058
            } elseif ($type instanceof TArrayKey) {
1059
                $did_remove_type = true;
1060
                $non_int_types[] = new TString();
1061
            } elseif ($type instanceof TScalar) {
1062
                $did_remove_type = true;
1063
                $non_int_types[] = new TString();
1064
                $non_int_types[] = new TFloat();
1065
                $non_int_types[] = new TBool();
1066
            } elseif ($type instanceof TInt) {
1067
                $did_remove_type = true;
1068
1069
                if ($is_equality) {
1070
                    $non_int_types[] = $type;
1071
                } elseif ($existing_var_type->from_calculation) {
1072
                    $non_int_types[] = new TFloat();
1073
                }
1074
            } else {
1075
                $non_int_types[] = $type;
1076
            }
1077
        }
1078
1079
        if (!$non_int_types || !$did_remove_type) {
1080
            if ($key && $code_location && !$is_equality) {
1081
                self::triggerIssueForImpossible(
1082
                    $existing_var_type,
1083
                    $old_var_type_string,
1084
                    $key,
1085
                    '!int',
1086
                    !$did_remove_type,
1087
                    $code_location,
1088
                    $suppressed_issues
1089
                );
1090
            }
1091
1092
            if (!$did_remove_type) {
1093
                $failed_reconciliation = 1;
1094
            }
1095
        }
1096
1097
        if ($non_int_types) {
1098
            $type = new Type\Union($non_int_types);
1099
            $type->ignore_falsable_issues = $existing_var_type->ignore_falsable_issues;
1100
            $type->ignore_nullable_issues = $existing_var_type->ignore_nullable_issues;
1101
            $type->from_docblock = $existing_var_type->from_docblock;
1102
            return $type;
1103
        }
1104
1105
        $failed_reconciliation = 2;
1106
1107
        return Type::getMixed();
1108
    }
1109
1110
    /**
1111
     * @param   string[]  $suppressed_issues
1112
     * @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...
1113
     */
1114
    private static function reconcileFloat(
1115
        Type\Union $existing_var_type,
1116
        ?string $key,
1117
        ?CodeLocation $code_location,
1118
        array $suppressed_issues,
1119
        int &$failed_reconciliation,
1120
        bool $is_equality
1121
    ) : Type\Union {
1122
        $old_var_type_string = $existing_var_type->getId();
1123
        $non_float_types = [];
1124
        $did_remove_type = false;
1125
1126
        foreach ($existing_var_type->getAtomicTypes() as $type) {
1127
            if ($type instanceof TTemplateParam) {
1128
                if (!$is_equality && !$type->as->isMixed()) {
1129
                    $template_did_fail = 0;
1130
1131
                    $type = clone $type;
1132
1133
                    $type->as = self::reconcileFloat(
1134
                        $type->as,
1135
                        null,
1136
                        null,
1137
                        $suppressed_issues,
1138
                        $template_did_fail,
1139
                        $is_equality
1140
                    );
1141
1142
                    $did_remove_type = true;
1143
1144
                    if (!$template_did_fail) {
1145
                        $non_float_types[] = $type;
1146
                    }
1147
                } else {
1148
                    $did_remove_type = true;
1149
                    $non_float_types[] = $type;
1150
                }
1151
            } elseif ($type instanceof TScalar) {
1152
                $did_remove_type = true;
1153
                $non_float_types[] = new TString();
1154
                $non_float_types[] = new TInt();
1155
                $non_float_types[] = new TBool();
1156
            } elseif ($type instanceof TFloat) {
1157
                $did_remove_type = true;
1158
1159
                if ($is_equality) {
1160
                    $non_float_types[] = $type;
1161
                }
1162
            } else {
1163
                $non_float_types[] = $type;
1164
            }
1165
        }
1166
1167
        if (!$non_float_types || !$did_remove_type) {
1168
            if ($key && $code_location && !$is_equality) {
1169
                self::triggerIssueForImpossible(
1170
                    $existing_var_type,
1171
                    $old_var_type_string,
1172
                    $key,
1173
                    '!float',
1174
                    !$did_remove_type,
1175
                    $code_location,
1176
                    $suppressed_issues
1177
                );
1178
            }
1179
1180
            if (!$did_remove_type) {
1181
                $failed_reconciliation = 1;
1182
            }
1183
        }
1184
1185
        if ($non_float_types) {
1186
            $type = new Type\Union($non_float_types);
1187
            $type->ignore_falsable_issues = $existing_var_type->ignore_falsable_issues;
1188
            $type->ignore_nullable_issues = $existing_var_type->ignore_nullable_issues;
1189
            $type->from_docblock = $existing_var_type->from_docblock;
1190
            return $type;
1191
        }
1192
1193
        $failed_reconciliation = 2;
1194
1195
        return Type::getMixed();
1196
    }
1197
1198
    /**
1199
     * @param   string[]  $suppressed_issues
1200
     * @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...
1201
     */
1202
    private static function reconcileString(
1203
        Type\Union $existing_var_type,
1204
        ?string $key,
1205
        ?CodeLocation $code_location,
1206
        array $suppressed_issues,
1207
        int &$failed_reconciliation,
1208
        bool $is_equality
1209
    ) : Type\Union {
1210
        $old_var_type_string = $existing_var_type->getId();
1211
        $non_string_types = [];
1212
        $did_remove_type = $existing_var_type->hasScalar();
1213
1214
        foreach ($existing_var_type->getAtomicTypes() as $type) {
1215
            if ($type instanceof TTemplateParam) {
1216
                if (!$is_equality && !$type->as->isMixed()) {
1217
                    $template_did_fail = 0;
1218
1219
                    $type = clone $type;
1220
1221
                    $type->as = self::reconcileString(
1222
                        $type->as,
1223
                        null,
1224
                        null,
1225
                        $suppressed_issues,
1226
                        $template_did_fail,
1227
                        $is_equality
1228
                    );
1229
1230
                    $did_remove_type = true;
1231
1232
                    if (!$template_did_fail) {
1233
                        $non_string_types[] = $type;
1234
                    }
1235
                } else {
1236
                    $did_remove_type = true;
1237
                    $non_string_types[] = $type;
1238
                }
1239
            } elseif ($type instanceof TArrayKey) {
1240
                $non_string_types[] = new TInt();
1241
                $did_remove_type = true;
1242
            } elseif ($type instanceof TCallable) {
1243
                $non_string_types[] = new Atomic\TCallableArray([
1244
                    Type::getArrayKey(),
1245
                    Type::getMixed()
1246
                ]);
1247
                $non_string_types[] = new Atomic\TCallableObject();
1248
                $did_remove_type = true;
1249
            } elseif ($type instanceof TNumeric) {
1250
                $non_string_types[] = $type;
1251
                $did_remove_type = true;
1252
            } elseif ($type instanceof TScalar) {
1253
                $did_remove_type = true;
1254
                $non_string_types[] = new TFloat();
1255
                $non_string_types[] = new TInt();
1256
                $non_string_types[] = new TBool();
1257
            } elseif (!$type instanceof TString) {
1258
                $non_string_types[] = $type;
1259
            } else {
1260
                $did_remove_type = true;
1261
1262
                if ($is_equality) {
1263
                    $non_string_types[] = $type;
1264
                }
1265
            }
1266
        }
1267
1268
        if (!$non_string_types || !$did_remove_type) {
1269
            if ($key && $code_location) {
1270
                self::triggerIssueForImpossible(
1271
                    $existing_var_type,
1272
                    $old_var_type_string,
1273
                    $key,
1274
                    '!string',
1275
                    !$did_remove_type,
1276
                    $code_location,
1277
                    $suppressed_issues
1278
                );
1279
            }
1280
1281
            if (!$did_remove_type) {
1282
                $failed_reconciliation = 1;
1283
            }
1284
        }
1285
1286
        if ($non_string_types) {
1287
            $type = new Type\Union($non_string_types);
1288
            $type->ignore_falsable_issues = $existing_var_type->ignore_falsable_issues;
1289
            $type->ignore_nullable_issues = $existing_var_type->ignore_nullable_issues;
1290
            $type->from_docblock = $existing_var_type->from_docblock;
1291
            return $type;
1292
        }
1293
1294
        $failed_reconciliation = 2;
1295
1296
        return Type::getMixed();
1297
    }
1298
1299
    /**
1300
     * @param   string[]  $suppressed_issues
1301
     * @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...
1302
     */
1303
    private static function reconcileArray(
1304
        Type\Union $existing_var_type,
1305
        ?string $key,
1306
        ?CodeLocation $code_location,
1307
        array $suppressed_issues,
1308
        int &$failed_reconciliation,
1309
        bool $is_equality
1310
    ) : Type\Union {
1311
        $old_var_type_string = $existing_var_type->getId();
1312
        $non_array_types = [];
1313
        $did_remove_type = $existing_var_type->hasScalar();
1314
1315
        foreach ($existing_var_type->getAtomicTypes() as $type) {
1316
            if ($type instanceof TTemplateParam) {
1317
                if (!$is_equality && !$type->as->isMixed()) {
1318
                    $template_did_fail = 0;
1319
1320
                    $type = clone $type;
1321
1322
                    $type->as = self::reconcileArray(
1323
                        $type->as,
1324
                        null,
1325
                        null,
1326
                        $suppressed_issues,
1327
                        $template_did_fail,
1328
                        $is_equality
1329
                    );
1330
1331
                    $did_remove_type = true;
1332
1333
                    if (!$template_did_fail) {
1334
                        $non_array_types[] = $type;
1335
                    }
1336
                } else {
1337
                    $did_remove_type = true;
1338
                    $non_array_types[] = $type;
1339
                }
1340
            } elseif ($type instanceof TCallable) {
1341
                $non_array_types[] = new Atomic\TCallableString();
1342
                $non_array_types[] = new Atomic\TCallableObject();
1343
                $did_remove_type = true;
1344
            } elseif ($type instanceof Atomic\TIterable) {
1345
                if (!$type->type_params[0]->isMixed() || !$type->type_params[1]->isMixed()) {
1346
                    $non_array_types[] = new Atomic\TGenericObject('Traversable', $type->type_params);
1347
                } else {
1348
                    $non_array_types[] = new TNamedObject('Traversable');
1349
                }
1350
1351
                $did_remove_type = true;
1352
            } elseif (!$type instanceof TArray
1353
                && !$type instanceof ObjectLike
1354
                && !$type instanceof Atomic\TList
1355
            ) {
1356
                $non_array_types[] = $type;
1357
            } else {
1358
                $did_remove_type = true;
1359
1360
                if ($is_equality) {
1361
                    $non_array_types[] = $type;
1362
                }
1363
            }
1364
        }
1365
1366
        if ((!$non_array_types || !$did_remove_type)) {
1367
            if ($key && $code_location) {
1368
                self::triggerIssueForImpossible(
1369
                    $existing_var_type,
1370
                    $old_var_type_string,
1371
                    $key,
1372
                    '!array',
1373
                    !$did_remove_type,
1374
                    $code_location,
1375
                    $suppressed_issues
1376
                );
1377
            }
1378
1379
            if (!$did_remove_type) {
1380
                $failed_reconciliation = 1;
1381
            }
1382
        }
1383
1384
        if ($non_array_types) {
1385
            $type = new Type\Union($non_array_types);
1386
            $type->ignore_falsable_issues = $existing_var_type->ignore_falsable_issues;
1387
            $type->ignore_nullable_issues = $existing_var_type->ignore_nullable_issues;
1388
            $type->from_docblock = $existing_var_type->from_docblock;
1389
            return $type;
1390
        }
1391
1392
        $failed_reconciliation = 2;
1393
1394
        return Type::getMixed();
1395
    }
1396
1397
    /**
1398
     * @return void
1399
     */
1400
    private static function removeFalsyNegatedLiteralTypes(
1401
        Type\Union $existing_var_type,
1402
        bool &$did_remove_type
1403
    ) {
1404
        if ($existing_var_type->hasString()) {
1405
            $existing_string_types = $existing_var_type->getLiteralStrings();
1406
1407
            if ($existing_string_types) {
1408
                foreach ($existing_string_types as $string_key => $literal_type) {
1409
                    if (!$literal_type->value) {
1410
                        $existing_var_type->removeType($string_key);
1411
                        $did_remove_type = true;
1412
                    }
1413
                }
1414
            } else {
1415
                $did_remove_type = true;
1416
            }
1417
        }
1418
1419
        if ($existing_var_type->hasInt()) {
1420
            $existing_int_types = $existing_var_type->getLiteralInts();
1421
1422
            if ($existing_int_types) {
1423
                foreach ($existing_int_types as $int_key => $literal_type) {
1424
                    if (!$literal_type->value) {
1425
                        $existing_var_type->removeType($int_key);
1426
                        $did_remove_type = true;
1427
                    }
1428
                }
1429
            } else {
1430
                $did_remove_type = true;
1431
            }
1432
        }
1433
1434
        if ($existing_var_type->hasType('array')) {
1435
            $array_atomic_type = $existing_var_type->getAtomicTypes()['array'];
1436
1437
            if ($array_atomic_type instanceof Type\Atomic\TArray
1438
                && !$array_atomic_type instanceof Type\Atomic\TNonEmptyArray
1439
            ) {
1440
                $did_remove_type = true;
1441
1442
                if ($array_atomic_type->getId() === 'array<empty, empty>') {
1443
                    $existing_var_type->removeType('array');
1444
                } else {
1445
                    $existing_var_type->addType(
1446
                        new Type\Atomic\TNonEmptyArray(
1447
                            $array_atomic_type->type_params
1448
                        )
1449
                    );
1450
                }
1451
            } elseif ($array_atomic_type instanceof Type\Atomic\TList
1452
                && !$array_atomic_type instanceof Type\Atomic\TNonEmptyList
1453
            ) {
1454
                $did_remove_type = true;
1455
1456
                $existing_var_type->addType(
1457
                    new Type\Atomic\TNonEmptyList(
1458
                        $array_atomic_type->type_param
1459
                    )
1460
                );
1461
            } elseif ($array_atomic_type instanceof Type\Atomic\ObjectLike
1462
                && !$array_atomic_type->sealed
1463
            ) {
1464
                $did_remove_type = true;
1465
            }
1466
        }
1467
    }
1468
}
1469