hasReconcilableNonEmptyCountEqualityCheck()   F
last analyzed

Complexity

Conditions 21
Paths 2628

Size

Total Lines 40

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 21
nc 2628
nop 1
dl 0
loc 40
rs 0
c 0
b 0
f 0

How to fix   Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
namespace Psalm\Internal\Analyzer\Statements\Expression;
3
4
use PhpParser;
5
use Psalm\Codebase;
6
use Psalm\Internal\Analyzer\ClassLikeAnalyzer;
7
use Psalm\Internal\Analyzer\StatementsAnalyzer;
8
use Psalm\Internal\Analyzer\TypeAnalyzer;
9
use Psalm\CodeLocation;
10
use Psalm\FileSource;
11
use Psalm\Issue\DocblockTypeContradiction;
12
use Psalm\Issue\RedundantCondition;
13
use Psalm\Issue\RedundantConditionGivenDocblockType;
14
use Psalm\Issue\TypeDoesNotContainNull;
15
use Psalm\Issue\TypeDoesNotContainType;
16
use Psalm\Issue\UnevaluatedCode;
17
use Psalm\IssueBuffer;
18
use Psalm\Type;
19
use function substr;
20
use function count;
21
use function strtolower;
22
use function in_array;
23
use function array_merge;
24
use function strpos;
25
use function is_int;
26
27
/**
28
 * @internal
29
 */
30
class AssertionFinder
31
{
32
    const ASSIGNMENT_TO_RIGHT = 1;
33
    const ASSIGNMENT_TO_LEFT = -1;
34
35
    /**
36
     * Gets all the type assertions in a conditional
37
     *
38
     * @param string|null $this_class_name
39
     *
40
     * @return array<string, non-empty-list<non-empty-list<string>>>|null
0 ignored issues
show
Documentation introduced by
The doc-type array<string, could not be parsed: Expected ">" at position 5, but found "end of type". (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...
41
     */
42
    public static function scrapeAssertions(
43
        PhpParser\Node\Expr $conditional,
44
        $this_class_name,
45
        FileSource $source,
46
        Codebase $codebase = null,
47
        bool $inside_negation = false,
48
        bool $cache = true,
49
        bool $inside_conditional = true
50
    ) {
51
        $if_types = [];
52
53
        if ($conditional instanceof PhpParser\Node\Expr\Instanceof_) {
54
            $instanceof_types = self::getInstanceOfTypes($conditional, $this_class_name, $source);
55
56
            if ($instanceof_types) {
57
                $var_name = ExpressionIdentifier::getArrayVarId(
58
                    $conditional->expr,
59
                    $this_class_name,
60
                    $source
61
                );
62
63
                if ($var_name) {
64
                    $if_types[$var_name] = [$instanceof_types];
65
66
                    $var_type = $source instanceof StatementsAnalyzer
67
                        ? $source->node_data->getType($conditional->expr)
68
                        : null;
69
70
                    foreach ($instanceof_types as $instanceof_type) {
71
                        if ($instanceof_type[0] === '=') {
72
                            $instanceof_type = substr($instanceof_type, 1);
73
                        }
74
75
                        if ($codebase
76
                            && $var_type
77
                            && $inside_negation
78
                            && $source instanceof StatementsAnalyzer
79
                        ) {
80
                            if ($codebase->interfaceExists($instanceof_type)) {
81
                                continue;
82
                            }
83
84
                            $instanceof_type = Type::parseString(
85
                                $instanceof_type,
86
                                null,
87
                                $source->getTemplateTypeMap() ?: []
88
                            );
89
90
                            if (!TypeAnalyzer::canExpressionTypesBeIdentical(
91
                                $codebase,
92
                                $instanceof_type,
93
                                $var_type
94
                            )) {
95
                                if ($var_type->from_docblock) {
96
                                    if (IssueBuffer::accepts(
97
                                        new RedundantConditionGivenDocblockType(
98
                                            $var_type->getId() . ' does not contain '
99
                                                . $instanceof_type->getId(),
100
                                            new CodeLocation($source, $conditional)
101
                                        ),
102
                                        $source->getSuppressedIssues()
103
                                    )) {
104
                                        // fall through
105
                                    }
106
                                } else {
107
                                    if (IssueBuffer::accepts(
108
                                        new RedundantCondition(
109
                                            $var_type->getId() . ' cannot be identical to '
110
                                                . $instanceof_type->getId(),
111
                                            new CodeLocation($source, $conditional)
112
                                        ),
113
                                        $source->getSuppressedIssues()
114
                                    )) {
115
                                        // fall through
116
                                    }
117
                                }
118
                            }
119
                        }
120
                    }
121
                }
122
            }
123
124
            return $if_types;
125
        }
126
127
        if ($conditional instanceof PhpParser\Node\Expr\Assign) {
128
            $var_name = ExpressionIdentifier::getArrayVarId(
129
                $conditional->var,
130
                $this_class_name,
131
                $source
132
            );
133
134
            $candidate_if_types = $inside_conditional
135
                ? self::scrapeAssertions(
136
                    $conditional->expr,
137
                    $this_class_name,
138
                    $source,
139
                    $codebase,
140
                    $inside_negation,
141
                    $cache,
142
                    $inside_conditional
143
                )
144
                : [];
145
146
            if ($var_name) {
147
                if ($candidate_if_types) {
148
                    $if_types[$var_name] = [['>' . \json_encode($candidate_if_types)]];
149
                } else {
150
                    $if_types[$var_name] = [['!falsy']];
151
                }
152
            }
153
154
            return $if_types;
155
        }
156
157
        $var_name = ExpressionIdentifier::getArrayVarId(
158
            $conditional,
159
            $this_class_name,
160
            $source
161
        );
162
163
        if ($var_name) {
164
            $if_types[$var_name] = [['!falsy']];
165
166
            if (!$conditional instanceof PhpParser\Node\Expr\MethodCall
167
                && !$conditional instanceof PhpParser\Node\Expr\StaticCall
168
            ) {
169
                return $if_types;
170
            }
171
        }
172
173
        if ($conditional instanceof PhpParser\Node\Expr\BooleanNot) {
174
            $expr_assertions = null;
175
176
            if ($cache && $source instanceof StatementsAnalyzer) {
177
                $expr_assertions = $source->node_data->getAssertions($conditional->expr);
178
            }
179
180
            if ($expr_assertions === null) {
181
                $expr_assertions = self::scrapeAssertions(
182
                    $conditional->expr,
183
                    $this_class_name,
184
                    $source,
185
                    $codebase,
186
                    !$inside_negation,
187
                    $cache,
188
                    $inside_conditional
189
                );
190
191
                if ($cache && $source instanceof StatementsAnalyzer) {
192
                    $source->node_data->setAssertions($conditional->expr, $expr_assertions);
193
                }
194
            }
195
196
            if ($expr_assertions === null) {
197
                throw new \UnexpectedValueException('Assertions should be set');
198
            }
199
200
            if (count($expr_assertions) !== 1) {
201
                return [];
202
            }
203
204
            $if_types = \Psalm\Type\Algebra::negateTypes($expr_assertions);
205
206
            return $if_types;
207
        }
208
209
        if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical ||
210
            $conditional instanceof PhpParser\Node\Expr\BinaryOp\Equal
211
        ) {
212
            $if_types = self::scrapeEqualityAssertions(
213
                $conditional,
214
                $this_class_name,
215
                $source,
216
                $codebase,
217
                false,
218
                $cache,
219
                $inside_conditional
220
            );
221
222
            return $if_types;
223
        }
224
225
        if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\NotIdentical ||
226
            $conditional instanceof PhpParser\Node\Expr\BinaryOp\NotEqual
227
        ) {
228
            $if_types = self::scrapeInequalityAssertions(
229
                $conditional,
230
                $this_class_name,
231
                $source,
232
                $codebase,
233
                false,
234
                $cache,
235
                $inside_conditional
236
            );
237
238
            return $if_types;
239
        }
240
241
        if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\Greater
242
            || $conditional instanceof PhpParser\Node\Expr\BinaryOp\GreaterOrEqual
243
        ) {
244
            $min_count = null;
245
            $count_equality_position = self::hasNonEmptyCountEqualityCheck($conditional, $min_count);
246
            $typed_value_position = self::hasTypedValueComparison($conditional, $source);
247
248
            if ($count_equality_position) {
249
                if ($count_equality_position === self::ASSIGNMENT_TO_RIGHT) {
250
                    $counted_expr = $conditional->left;
251
                } else {
252
                    throw new \UnexpectedValueException('$count_equality_position value');
253
                }
254
255
                /** @var PhpParser\Node\Expr\FuncCall $counted_expr */
256
                $var_name = ExpressionIdentifier::getArrayVarId(
257
                    $counted_expr->args[0]->value,
258
                    $this_class_name,
259
                    $source
260
                );
261
262
                if ($var_name) {
263
                    if (self::hasReconcilableNonEmptyCountEqualityCheck($conditional)) {
264
                        $if_types[$var_name] = [['non-empty-countable']];
265
                    } else {
266
                        if ($min_count) {
267
                            $if_types[$var_name] = [['=has-at-least-' . $min_count]];
268
                        } else {
269
                            $if_types[$var_name] = [['=non-empty-countable']];
270
                        }
271
                    }
272
                }
273
274
                return $if_types;
275
            }
276
277
            if ($typed_value_position) {
278
                if ($typed_value_position === self::ASSIGNMENT_TO_RIGHT) {
279
                    /** @var PhpParser\Node\Expr $conditional->right */
280
                    $var_name = ExpressionIdentifier::getArrayVarId(
281
                        $conditional->left,
282
                        $this_class_name,
283
                        $source
284
                    );
285
                } elseif ($typed_value_position === self::ASSIGNMENT_TO_LEFT) {
286
                    $var_name = null;
287
                } else {
288
                    throw new \UnexpectedValueException('$typed_value_position value');
289
                }
290
291
                if ($var_name) {
292
                    $if_types[$var_name] = [['=isset']];
293
                }
294
295
                return $if_types;
296
            }
297
298
            return [];
299
        }
300
301
        if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\Smaller
302
            || $conditional instanceof PhpParser\Node\Expr\BinaryOp\SmallerOrEqual
303
        ) {
304
            $min_count = null;
305
            $count_equality_position = self::hasNonEmptyCountEqualityCheck($conditional, $min_count);
306
            $typed_value_position = self::hasTypedValueComparison($conditional, $source);
307
308
            if ($count_equality_position) {
309
                if ($count_equality_position === self::ASSIGNMENT_TO_LEFT) {
310
                    $count_expr = $conditional->right;
311
                } else {
312
                    throw new \UnexpectedValueException('$count_equality_position value');
313
                }
314
315
                /** @var PhpParser\Node\Expr\FuncCall $count_expr */
316
                $var_name = ExpressionIdentifier::getArrayVarId(
317
                    $count_expr->args[0]->value,
318
                    $this_class_name,
319
                    $source
320
                );
321
322
                if ($var_name) {
323
                    if ($min_count) {
324
                        $if_types[$var_name] = [['=has-at-least-' . $min_count]];
325
                    } else {
326
                        $if_types[$var_name] = [['=non-empty-countable']];
327
                    }
328
                }
329
330
                return $if_types;
331
            }
332
333
            if ($typed_value_position) {
334
                if ($typed_value_position === self::ASSIGNMENT_TO_RIGHT) {
335
                    /** @var PhpParser\Node\Expr $conditional->left */
336
                    $var_name = ExpressionIdentifier::getArrayVarId(
337
                        $conditional->left,
338
                        $this_class_name,
339
                        $source
340
                    );
341
342
                    $expr = $conditional->right;
343
                } elseif ($typed_value_position === self::ASSIGNMENT_TO_LEFT) {
344
                    /** @var PhpParser\Node\Expr $conditional->left */
345
                    $var_name = ExpressionIdentifier::getArrayVarId(
346
                        $conditional->right,
347
                        $this_class_name,
348
                        $source
349
                    );
350
351
                    $expr = $conditional->left;
352
                } else {
353
                    throw new \UnexpectedValueException('$typed_value_position value');
354
                }
355
356
                $expr_type = $source instanceof StatementsAnalyzer
357
                    ? $source->node_data->getType($expr)
358
                    : null;
359
360
                if ($var_name
361
                    && $expr_type
362
                    && $expr_type->isSingleIntLiteral()
363
                    && ($expr_type->getSingleIntLiteral()->value === 0)
364
                ) {
365
                    $if_types[$var_name] = [['=isset']];
366
                }
367
368
                return $if_types;
369
            }
370
371
            return [];
372
        }
373
374
        if ($conditional instanceof PhpParser\Node\Expr\FuncCall) {
375
            $if_types = self::processFunctionCall($conditional, $this_class_name, $source, $codebase, false);
376
377
            return $if_types;
378
        }
379
380
        if ($conditional instanceof PhpParser\Node\Expr\MethodCall
381
            || $conditional instanceof PhpParser\Node\Expr\StaticCall
382
        ) {
383
            $custom_assertions = self::processCustomAssertion($conditional, $this_class_name, $source, false);
384
385
            if ($custom_assertions) {
386
                return $custom_assertions;
387
            }
388
389
            return $if_types;
390
        }
391
392
        if ($conditional instanceof PhpParser\Node\Expr\Empty_) {
393
            $var_name = ExpressionIdentifier::getArrayVarId(
394
                $conditional->expr,
395
                $this_class_name,
396
                $source
397
            );
398
399
            if ($var_name) {
400
                $if_types[$var_name] = [['empty']];
401
            }
402
403
            return $if_types;
404
        }
405
406
        if ($conditional instanceof PhpParser\Node\Expr\Isset_) {
407
            foreach ($conditional->vars as $isset_var) {
408
                $var_name = ExpressionIdentifier::getArrayVarId(
409
                    $isset_var,
410
                    $this_class_name,
411
                    $source
412
                );
413
414
                if ($var_name) {
415
                    $if_types[$var_name] = [['isset']];
416
                } else {
417
                    // look for any variables we *can* use for an isset assertion
418
                    $array_root = $isset_var;
419
420
                    while ($array_root instanceof PhpParser\Node\Expr\ArrayDimFetch && !$var_name) {
421
                        $array_root = $array_root->var;
422
423
                        $var_name = ExpressionIdentifier::getArrayVarId(
424
                            $array_root,
425
                            $this_class_name,
426
                            $source
427
                        );
428
                    }
429
430
                    if ($var_name) {
431
                        $if_types[$var_name] = [['=isset']];
432
                    }
433
                }
434
            }
435
436
            return $if_types;
437
        }
438
439
        if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\Coalesce) {
440
            return self::scrapeAssertions(
441
                new PhpParser\Node\Expr\Ternary(
442
                    new PhpParser\Node\Expr\Isset_(
443
                        [$conditional->left]
444
                    ),
445
                    $conditional->left,
446
                    $conditional->right,
447
                    $conditional->getAttributes()
448
                ),
449
                $this_class_name,
450
                $source,
451
                $codebase,
452
                $inside_negation,
453
                false,
454
                $inside_conditional
455
            );
456
        }
457
458
        return [];
459
    }
460
461
    /**
462
     * @param PhpParser\Node\Expr\BinaryOp\Identical|PhpParser\Node\Expr\BinaryOp\Equal $conditional
463
     * @param string|null $this_class_name
464
     *
465
     * @return array<string, non-empty-list<non-empty-list<string>>>
0 ignored issues
show
Documentation introduced by
The doc-type array<string, could not be parsed: Expected ">" at position 5, but found "end of type". (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...
466
     */
467
    private static function scrapeEqualityAssertions(
468
        PhpParser\Node\Expr\BinaryOp $conditional,
469
        $this_class_name,
470
        FileSource $source,
471
        Codebase $codebase = null,
472
        bool $inside_negation = false,
473
        bool $cache = true,
474
        bool $inside_conditional = true
475
    ) {
476
        $if_types = [];
477
478
        $null_position = self::hasNullVariable($conditional);
479
        $false_position = self::hasFalseVariable($conditional);
480
        $true_position = self::hasTrueVariable($conditional);
481
        $empty_array_position = self::hasEmptyArrayVariable($conditional);
482
        $gettype_position = self::hasGetTypeCheck($conditional);
483
        $min_count = null;
484
        $count_equality_position = self::hasNonEmptyCountEqualityCheck($conditional, $min_count);
485
486
        if ($null_position !== null) {
487
            if ($null_position === self::ASSIGNMENT_TO_RIGHT) {
488
                $base_conditional = $conditional->left;
489
            } elseif ($null_position === self::ASSIGNMENT_TO_LEFT) {
490
                $base_conditional = $conditional->right;
491
            } else {
492
                throw new \UnexpectedValueException('$null_position value');
493
            }
494
495
            $var_name = ExpressionIdentifier::getArrayVarId(
496
                $base_conditional,
497
                $this_class_name,
498
                $source
499
            );
500
501
            if ($var_name && $base_conditional instanceof PhpParser\Node\Expr\Assign) {
502
                $var_name = '=' . $var_name;
503
            }
504
505
            if ($var_name) {
506
                if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical) {
507
                    $if_types[$var_name] = [['null']];
508
                } else {
509
                    $if_types[$var_name] = [['falsy']];
510
                }
511
            }
512
513
            if ($codebase
514
                && $source instanceof StatementsAnalyzer
515
                && ($var_type = $source->node_data->getType($base_conditional))
516
                && $conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical
517
            ) {
518
                $null_type = Type::getNull();
519
520
                if (!TypeAnalyzer::isContainedBy(
521
                    $codebase,
522
                    $var_type,
523
                    $null_type
524
                ) && !TypeAnalyzer::isContainedBy(
525
                    $codebase,
526
                    $null_type,
527
                    $var_type
528
                )) {
529
                    if ($var_type->from_docblock) {
530
                        if (IssueBuffer::accepts(
531
                            new DocblockTypeContradiction(
532
                                $var_type . ' does not contain null',
533
                                new CodeLocation($source, $conditional)
534
                            ),
535
                            $source->getSuppressedIssues()
536
                        )) {
537
                            // fall through
538
                        }
539
                    } else {
540
                        if (IssueBuffer::accepts(
541
                            new TypeDoesNotContainNull(
542
                                $var_type . ' does not contain null',
543
                                new CodeLocation($source, $conditional)
544
                            ),
545
                            $source->getSuppressedIssues()
546
                        )) {
547
                            // fall through
548
                        }
549
                    }
550
                }
551
            }
552
553
            return $if_types;
554
        }
555
556
        if ($true_position) {
557
            if ($true_position === self::ASSIGNMENT_TO_RIGHT) {
558
                $base_conditional = $conditional->left;
559
            } elseif ($true_position === self::ASSIGNMENT_TO_LEFT) {
560
                $base_conditional = $conditional->right;
561
            } else {
562
                throw new \UnexpectedValueException('Unrecognised position');
563
            }
564
565
            if ($base_conditional instanceof PhpParser\Node\Expr\FuncCall) {
566
                $if_types = self::processFunctionCall(
567
                    $base_conditional,
568
                    $this_class_name,
569
                    $source,
570
                    $codebase,
571
                    false
572
                );
573
            } else {
574
                $var_name = ExpressionIdentifier::getArrayVarId(
575
                    $base_conditional,
576
                    $this_class_name,
577
                    $source
578
                );
579
580
                if ($var_name) {
581
                    if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical) {
582
                        $if_types[$var_name] = [['true']];
583
                    } else {
584
                        $if_types[$var_name] = [['!falsy']];
585
                    }
586
                } else {
587
                    $base_assertions = null;
588
589
                    if ($source instanceof StatementsAnalyzer && $cache) {
590
                        $base_assertions = $source->node_data->getAssertions($base_conditional);
591
                    }
592
593
                    if ($base_assertions === null) {
594
                        $base_assertions = self::scrapeAssertions(
595
                            $base_conditional,
596
                            $this_class_name,
597
                            $source,
598
                            $codebase,
599
                            $inside_negation,
600
                            $cache
601
                        );
602
603
                        if ($source instanceof StatementsAnalyzer && $cache) {
604
                            $source->node_data->setAssertions($base_conditional, $base_assertions);
605
                        }
606
                    }
607
608
                    if ($base_assertions === null) {
609
                        throw new \UnexpectedValueException('Assertions should be set');
610
                    }
611
612
                    $if_types = $base_assertions;
613
                }
614
            }
615
616
            if ($codebase
617
                && $source instanceof StatementsAnalyzer
618
                && ($var_type = $source->node_data->getType($base_conditional))
619
            ) {
620
                if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical) {
621
                    $true_type = Type::getTrue();
622
623
                    if (!TypeAnalyzer::isContainedBy(
624
                        $codebase,
625
                        $var_type,
626
                        $true_type
627
                    ) && !TypeAnalyzer::isContainedBy(
628
                        $codebase,
629
                        $true_type,
630
                        $var_type
631
                    )) {
632
                        if ($var_type->from_docblock) {
633
                            if (IssueBuffer::accepts(
634
                                new DocblockTypeContradiction(
635
                                    $var_type . ' does not contain true',
636
                                    new CodeLocation($source, $conditional)
637
                                ),
638
                                $source->getSuppressedIssues()
639
                            )) {
640
                                // fall through
641
                            }
642
                        } else {
643
                            if (IssueBuffer::accepts(
644
                                new TypeDoesNotContainType(
645
                                    $var_type . ' does not contain true',
646
                                    new CodeLocation($source, $conditional)
647
                                ),
648
                                $source->getSuppressedIssues()
649
                            )) {
650
                                // fall through
651
                            }
652
                        }
653
                    }
654
                }
655
            }
656
657
            return $if_types;
658
        }
659
660
        if ($false_position) {
661
            if ($false_position === self::ASSIGNMENT_TO_RIGHT) {
662
                $base_conditional = $conditional->left;
663
            } elseif ($false_position === self::ASSIGNMENT_TO_LEFT) {
664
                $base_conditional = $conditional->right;
665
            } else {
666
                throw new \UnexpectedValueException('$false_position value');
667
            }
668
669
            if ($base_conditional instanceof PhpParser\Node\Expr\FuncCall) {
670
                $if_types = self::processFunctionCall(
671
                    $base_conditional,
672
                    $this_class_name,
673
                    $source,
674
                    $codebase,
675
                    true
676
                );
677
            } else {
678
                $var_name = ExpressionIdentifier::getArrayVarId(
679
                    $base_conditional,
680
                    $this_class_name,
681
                    $source
682
                );
683
684
                if ($var_name) {
685
                    if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical) {
686
                        $if_types[$var_name] = [['false']];
687
                    } else {
688
                        $if_types[$var_name] = [['falsy']];
689
                    }
690
                } else {
691
                    $base_assertions = null;
692
693
                    if ($source instanceof StatementsAnalyzer && $cache) {
694
                        $base_assertions = $source->node_data->getAssertions($base_conditional);
695
                    }
696
697
                    if ($base_assertions === null) {
698
                        $base_assertions = self::scrapeAssertions(
699
                            $base_conditional,
700
                            $this_class_name,
701
                            $source,
702
                            $codebase,
703
                            $inside_negation,
704
                            $cache,
705
                            $inside_conditional
706
                        );
707
708
                        if ($source instanceof StatementsAnalyzer && $cache) {
709
                            $source->node_data->setAssertions($base_conditional, $base_assertions);
710
                        }
711
                    }
712
713
                    if ($base_assertions === null) {
714
                        throw new \UnexpectedValueException('Assertions should be set');
715
                    }
716
717
                    $notif_types = $base_assertions;
718
719
                    if (count($notif_types) === 1) {
720
                        $if_types = \Psalm\Type\Algebra::negateTypes($notif_types);
721
                    }
722
                }
723
            }
724
725
            if ($codebase
726
                && $source instanceof StatementsAnalyzer
727
                && ($var_type = $source->node_data->getType($base_conditional))
728
            ) {
729
                if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical) {
730
                    $false_type = Type::getFalse();
731
732
                    if (!TypeAnalyzer::canExpressionTypesBeIdentical(
733
                        $codebase,
734
                        $false_type,
735
                        $var_type
736
                    )) {
737
                        if ($var_type->from_docblock) {
738
                            if (IssueBuffer::accepts(
739
                                new DocblockTypeContradiction(
740
                                    $var_type . ' does not contain false',
741
                                    new CodeLocation($source, $conditional)
742
                                ),
743
                                $source->getSuppressedIssues()
744
                            )) {
745
                                // fall through
746
                            }
747
                        } else {
748
                            if (IssueBuffer::accepts(
749
                                new TypeDoesNotContainType(
750
                                    $var_type . ' does not contain false',
751
                                    new CodeLocation($source, $conditional)
752
                                ),
753
                                $source->getSuppressedIssues()
754
                            )) {
755
                                // fall through
756
                            }
757
                        }
758
                    }
759
                }
760
            }
761
762
            return $if_types;
763
        }
764
765
        if ($empty_array_position !== null) {
766
            if ($empty_array_position === self::ASSIGNMENT_TO_RIGHT) {
767
                $base_conditional = $conditional->left;
768
            } elseif ($empty_array_position === self::ASSIGNMENT_TO_LEFT) {
769
                $base_conditional = $conditional->right;
770
            } else {
771
                throw new \UnexpectedValueException('$empty_array_position value');
772
            }
773
774
            $var_name = ExpressionIdentifier::getArrayVarId(
775
                $base_conditional,
776
                $this_class_name,
777
                $source
778
            );
779
780
            if ($var_name) {
781
                if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical) {
782
                    $if_types[$var_name] = [['!non-empty-countable']];
783
                } else {
784
                    $if_types[$var_name] = [['falsy']];
785
                }
786
            }
787
788
            if ($codebase
789
                && $source instanceof StatementsAnalyzer
790
                && ($var_type = $source->node_data->getType($base_conditional))
791
                && $conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical
792
            ) {
793
                $empty_array_type = Type::getEmptyArray();
794
795
                if (!TypeAnalyzer::canExpressionTypesBeIdentical(
796
                    $codebase,
797
                    $empty_array_type,
798
                    $var_type
799
                )) {
800
                    if ($var_type->from_docblock) {
801
                        if (IssueBuffer::accepts(
802
                            new DocblockTypeContradiction(
803
                                $var_type . ' does not contain an empty array',
804
                                new CodeLocation($source, $conditional)
805
                            ),
806
                            $source->getSuppressedIssues()
807
                        )) {
808
                            // fall through
809
                        }
810
                    } else {
811
                        if (IssueBuffer::accepts(
812
                            new TypeDoesNotContainType(
813
                                $var_type . ' does not contain empty array',
814
                                new CodeLocation($source, $conditional)
815
                            ),
816
                            $source->getSuppressedIssues()
817
                        )) {
818
                            // fall through
819
                        }
820
                    }
821
                }
822
            }
823
824
            return $if_types;
825
        }
826
827
        if ($gettype_position) {
828
            if ($gettype_position === self::ASSIGNMENT_TO_RIGHT) {
829
                $string_expr = $conditional->left;
830
                $gettype_expr = $conditional->right;
831
            } elseif ($gettype_position === self::ASSIGNMENT_TO_LEFT) {
832
                $string_expr = $conditional->right;
833
                $gettype_expr = $conditional->left;
834
            } else {
835
                throw new \UnexpectedValueException('$gettype_position value');
836
            }
837
838
            /** @var PhpParser\Node\Expr\FuncCall $gettype_expr */
839
            $var_name = ExpressionIdentifier::getArrayVarId(
840
                $gettype_expr->args[0]->value,
841
                $this_class_name,
842
                $source
843
            );
844
845
            /** @var PhpParser\Node\Scalar\String_ $string_expr */
846
            $var_type = $string_expr->value;
847
848
            if (!isset(ClassLikeAnalyzer::GETTYPE_TYPES[$var_type])) {
849
                if (IssueBuffer::accepts(
850
                    new UnevaluatedCode(
851
                        'gettype cannot return this value',
852
                        new CodeLocation($source, $string_expr)
853
                    )
854
                )) {
855
                    // fall through
856
                }
857
            } else {
858
                if ($var_name && $var_type) {
859
                    $if_types[$var_name] = [[$var_type]];
860
                }
861
            }
862
863
            return $if_types;
864
        }
865
866
        if ($count_equality_position) {
867
            if ($count_equality_position === self::ASSIGNMENT_TO_RIGHT) {
868
                $count_expr = $conditional->left;
869
            } elseif ($count_equality_position === self::ASSIGNMENT_TO_LEFT) {
870
                $count_expr = $conditional->right;
871
            } else {
872
                throw new \UnexpectedValueException('$count_equality_position value');
873
            }
874
875
            /** @var PhpParser\Node\Expr\FuncCall $count_expr */
876
            $var_name = ExpressionIdentifier::getArrayVarId(
877
                $count_expr->args[0]->value,
878
                $this_class_name,
879
                $source
880
            );
881
882
            if ($var_name) {
883
                if ($min_count) {
884
                    $if_types[$var_name] = [['=has-at-least-' . $min_count]];
885
                } else {
886
                    $if_types[$var_name] = [['=non-empty-countable']];
887
                }
888
            }
889
890
            return $if_types;
891
        }
892
893
        if (!$source instanceof StatementsAnalyzer) {
894
            return [];
895
        }
896
897
        $getclass_position = self::hasGetClassCheck($conditional, $source);
898
899
        if ($getclass_position) {
900
            if ($getclass_position === self::ASSIGNMENT_TO_RIGHT) {
901
                $whichclass_expr = $conditional->left;
902
                $getclass_expr = $conditional->right;
903
            } elseif ($getclass_position === self::ASSIGNMENT_TO_LEFT) {
904
                $whichclass_expr = $conditional->right;
905
                $getclass_expr = $conditional->left;
906
            } else {
907
                throw new \UnexpectedValueException('$getclass_position value');
908
            }
909
910
            if ($getclass_expr instanceof PhpParser\Node\Expr\FuncCall && isset($getclass_expr->args[0])) {
911
                $var_name = ExpressionIdentifier::getArrayVarId(
912
                    $getclass_expr->args[0]->value,
913
                    $this_class_name,
914
                    $source
915
                );
916
            } else {
917
                $var_name = '$this';
918
            }
919
920
            if ($whichclass_expr instanceof PhpParser\Node\Expr\ClassConstFetch
921
                && $whichclass_expr->class instanceof PhpParser\Node\Name
922
            ) {
923
                $var_type = ClassLikeAnalyzer::getFQCLNFromNameObject(
924
                    $whichclass_expr->class,
925
                    $source->getAliases()
926
                );
927
928
                if ($var_type === 'self' || $var_type === 'static') {
929
                    $var_type = $this_class_name;
930
                } elseif ($var_type === 'parent') {
931
                    $var_type = null;
932
                }
933
934
                if ($var_type) {
935
                    if (ClassLikeAnalyzer::checkFullyQualifiedClassLikeName(
936
                        $source,
937
                        $var_type,
938
                        new CodeLocation($source, $whichclass_expr),
939
                        null,
940
                        null,
941
                        $source->getSuppressedIssues(),
942
                        true
943
                    ) === false
944
                    ) {
945
                        return $if_types;
946
                    }
947
                }
948
949
                if ($var_name && $var_type) {
950
                    $if_types[$var_name] = [['=getclass-' . $var_type]];
951
                }
952
            } else {
953
                $type = $source->node_data->getType($whichclass_expr);
954
955
                if ($type && $var_name) {
956
                    foreach ($type->getAtomicTypes() as $type_part) {
957
                        if ($type_part instanceof Type\Atomic\TTemplateParamClass) {
958
                            $if_types[$var_name] = [['=' . $type_part->param_name]];
959
                        }
960
                    }
961
                }
962
            }
963
964
            return $if_types;
965
        }
966
967
        $typed_value_position = self::hasTypedValueComparison($conditional, $source);
968
969
        if ($typed_value_position) {
970
            if ($typed_value_position === self::ASSIGNMENT_TO_RIGHT) {
971
                /** @var PhpParser\Node\Expr $conditional->right */
972
                $var_name = ExpressionIdentifier::getArrayVarId(
973
                    $conditional->left,
974
                    $this_class_name,
975
                    $source
976
                );
977
978
                $other_type = $source->node_data->getType($conditional->left);
979
                $var_type = $source->node_data->getType($conditional->right);
980
            } elseif ($typed_value_position === self::ASSIGNMENT_TO_LEFT) {
981
                /** @var PhpParser\Node\Expr $conditional->left */
982
                $var_name = ExpressionIdentifier::getArrayVarId(
983
                    $conditional->right,
984
                    $this_class_name,
985
                    $source
986
                );
987
988
                $var_type = $source->node_data->getType($conditional->left);
989
                $other_type = $source->node_data->getType($conditional->right);
990
            } else {
991
                throw new \UnexpectedValueException('$typed_value_position value');
992
            }
993
994
            if ($var_name && $var_type) {
995
                $identical = $conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical
996
                    || ($other_type
997
                        && (($var_type->isString(true) && $other_type->isString(true))
998
                            || ($var_type->isInt(true) && $other_type->isInt(true))
999
                            || ($var_type->isFloat() && $other_type->isFloat())
1000
                        )
1001
                    );
1002
1003
                if ($identical) {
1004
                    $if_types[$var_name] = [['=' . $var_type->getAssertionString()]];
1005
                } else {
1006
                    $if_types[$var_name] = [['~' . $var_type->getAssertionString()]];
1007
                }
1008
            }
1009
1010
            if ($codebase
1011
                && $other_type
1012
                && $var_type
1013
                && ($conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical
1014
                    || ($other_type->isString()
1015
                        && $var_type->isString())
1016
                )
1017
            ) {
1018
                $parent_source = $source->getSource();
1019
1020
                if ($parent_source->getSource() instanceof \Psalm\Internal\Analyzer\TraitAnalyzer
1021
                    && (($var_type->isSingleStringLiteral()
1022
                            && $var_type->getSingleStringLiteral()->value === $this_class_name)
1023
                        || ($other_type->isSingleStringLiteral()
1024
                            && $other_type->getSingleStringLiteral()->value === $this_class_name))
1025
                ) {
1026
                    // do nothing
1027
                } elseif (!TypeAnalyzer::canExpressionTypesBeIdentical(
1028
                    $codebase,
1029
                    $other_type,
1030
                    $var_type
1031
                )) {
1032
                    if ($var_type->from_docblock || $other_type->from_docblock) {
1033
                        if (IssueBuffer::accepts(
1034
                            new DocblockTypeContradiction(
1035
                                $var_type->getId() . ' does not contain ' . $other_type->getId(),
1036
                                new CodeLocation($source, $conditional)
1037
                            ),
1038
                            $source->getSuppressedIssues()
1039
                        )) {
1040
                            // fall through
1041
                        }
1042
                    } else {
1043
                        if (IssueBuffer::accepts(
1044
                            new TypeDoesNotContainType(
1045
                                $var_type->getId() . ' cannot be identical to ' . $other_type->getId(),
1046
                                new CodeLocation($source, $conditional)
1047
                            ),
1048
                            $source->getSuppressedIssues()
1049
                        )) {
1050
                            // fall through
1051
                        }
1052
                    }
1053
                }
1054
            }
1055
1056
            return $if_types;
1057
        }
1058
1059
        $var_type = $source->node_data->getType($conditional->left);
1060
        $other_type = $source->node_data->getType($conditional->right);
1061
1062
        if ($codebase
1063
            && $var_type
1064
            && $other_type
1065
            && $conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical
1066
        ) {
1067
            if (!TypeAnalyzer::canExpressionTypesBeIdentical($codebase, $var_type, $other_type)) {
1068
                if (IssueBuffer::accepts(
1069
                    new TypeDoesNotContainType(
1070
                        $var_type->getId() . ' cannot be identical to ' . $other_type->getId(),
1071
                        new CodeLocation($source, $conditional)
1072
                    ),
1073
                    $source->getSuppressedIssues()
1074
                )) {
1075
                    // fall through
1076
                }
1077
            }
1078
        }
1079
1080
        return [];
1081
    }
1082
1083
    /**
1084
     * @param PhpParser\Node\Expr\BinaryOp\NotIdentical|PhpParser\Node\Expr\BinaryOp\NotEqual $conditional
1085
     * @param string|null $this_class_name
1086
     *
1087
     * @return array<string, non-empty-list<non-empty-list<string>>>
0 ignored issues
show
Documentation introduced by
The doc-type array<string, could not be parsed: Expected ">" at position 5, but found "end of type". (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...
1088
     */
1089
    private static function scrapeInequalityAssertions(
1090
        PhpParser\Node\Expr\BinaryOp $conditional,
1091
        $this_class_name,
1092
        FileSource $source,
1093
        Codebase $codebase = null,
1094
        bool $inside_negation = false,
1095
        bool $cache = true,
1096
        bool $inside_conditional = true
1097
    ) {
1098
        $if_types = [];
1099
1100
        $null_position = self::hasNullVariable($conditional);
1101
        $false_position = self::hasFalseVariable($conditional);
1102
        $true_position = self::hasTrueVariable($conditional);
1103
        $empty_array_position = self::hasEmptyArrayVariable($conditional);
1104
        $gettype_position = self::hasGetTypeCheck($conditional);
1105
1106
        if ($null_position !== null) {
1107
            if ($null_position === self::ASSIGNMENT_TO_RIGHT) {
1108
                $base_conditional = $conditional->left;
1109
            } elseif ($null_position === self::ASSIGNMENT_TO_LEFT) {
1110
                $base_conditional = $conditional->right;
1111
            } else {
1112
                throw new \UnexpectedValueException('Bad null variable position');
1113
            }
1114
1115
            $var_name = ExpressionIdentifier::getArrayVarId(
1116
                $base_conditional,
1117
                $this_class_name,
1118
                $source
1119
            );
1120
1121
            if ($var_name) {
1122
                if ($base_conditional instanceof PhpParser\Node\Expr\Assign) {
1123
                    $var_name = '=' . $var_name;
1124
                }
1125
1126
                if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\NotIdentical) {
1127
                    $if_types[$var_name] = [['!null']];
1128
                } else {
1129
                    $if_types[$var_name] = [['!falsy']];
1130
                }
1131
            }
1132
1133
            if ($codebase
1134
                && $source instanceof StatementsAnalyzer
1135
                && ($var_type = $source->node_data->getType($base_conditional))
1136
            ) {
1137
                if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\NotIdentical) {
1138
                    $null_type = Type::getNull();
1139
1140
                    if (!TypeAnalyzer::isContainedBy(
1141
                        $codebase,
1142
                        $var_type,
1143
                        $null_type
1144
                    ) && !TypeAnalyzer::isContainedBy(
1145
                        $codebase,
1146
                        $null_type,
1147
                        $var_type
1148
                    )) {
1149
                        if ($var_type->from_docblock) {
1150
                            if (IssueBuffer::accepts(
1151
                                new RedundantConditionGivenDocblockType(
1152
                                    'Docblock-asserted type ' . $var_type . ' can never contain null',
1153
                                    new CodeLocation($source, $conditional)
1154
                                ),
1155
                                $source->getSuppressedIssues()
1156
                            )) {
1157
                                // fall through
1158
                            }
1159
                        } else {
1160
                            if (IssueBuffer::accepts(
1161
                                new RedundantCondition(
1162
                                    $var_type . ' can never contain null',
1163
                                    new CodeLocation($source, $conditional)
1164
                                ),
1165
                                $source->getSuppressedIssues()
1166
                            )) {
1167
                                // fall through
1168
                            }
1169
                        }
1170
                    }
1171
                }
1172
            }
1173
1174
            return $if_types;
1175
        }
1176
1177
        if ($false_position) {
1178
            if ($false_position === self::ASSIGNMENT_TO_RIGHT) {
1179
                $base_conditional = $conditional->left;
1180
            } elseif ($false_position === self::ASSIGNMENT_TO_LEFT) {
1181
                $base_conditional = $conditional->right;
1182
            } else {
1183
                throw new \UnexpectedValueException('Bad false variable position');
1184
            }
1185
1186
            $var_name = ExpressionIdentifier::getArrayVarId(
1187
                $base_conditional,
1188
                $this_class_name,
1189
                $source
1190
            );
1191
1192
            if ($var_name) {
1193
                if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\NotIdentical) {
1194
                    $if_types[$var_name] = [['!false']];
1195
                } else {
1196
                    $if_types[$var_name] = [['!falsy']];
1197
                }
1198
            } else {
1199
                $base_assertions = null;
1200
1201
                if ($source instanceof StatementsAnalyzer && $cache) {
1202
                    $base_assertions = $source->node_data->getAssertions($base_conditional);
1203
                }
1204
1205
                if ($base_assertions === null) {
1206
                    $base_assertions = self::scrapeAssertions(
1207
                        $base_conditional,
1208
                        $this_class_name,
1209
                        $source,
1210
                        $codebase,
1211
                        $inside_negation,
1212
                        $cache,
1213
                        $inside_conditional
1214
                    );
1215
1216
                    if ($source instanceof StatementsAnalyzer && $cache) {
1217
                        $source->node_data->setAssertions($base_conditional, $base_assertions);
1218
                    }
1219
                }
1220
1221
                if ($base_assertions === null) {
1222
                    throw new \UnexpectedValueException('Assertions should be set');
1223
                }
1224
1225
                $notif_types = $base_assertions;
1226
1227
                if (count($notif_types) === 1) {
1228
                    $if_types = \Psalm\Type\Algebra::negateTypes($notif_types);
1229
                }
1230
            }
1231
1232
            if ($codebase
1233
                && $source instanceof StatementsAnalyzer
1234
                && ($var_type = $source->node_data->getType($base_conditional))
1235
            ) {
1236
                if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\NotIdentical) {
1237
                    $false_type = Type::getFalse();
1238
1239
                    if (!TypeAnalyzer::isContainedBy(
1240
                        $codebase,
1241
                        $var_type,
1242
                        $false_type
1243
                    ) && !TypeAnalyzer::isContainedBy(
1244
                        $codebase,
1245
                        $false_type,
1246
                        $var_type
1247
                    )) {
1248
                        if ($var_type->from_docblock) {
1249
                            if (IssueBuffer::accepts(
1250
                                new RedundantConditionGivenDocblockType(
1251
                                    'Docblock-asserted type ' . $var_type . ' can never contain false',
1252
                                    new CodeLocation($source, $conditional)
1253
                                ),
1254
                                $source->getSuppressedIssues()
1255
                            )) {
1256
                                // fall through
1257
                            }
1258
                        } else {
1259
                            if (IssueBuffer::accepts(
1260
                                new RedundantCondition(
1261
                                    $var_type . ' can never contain false',
1262
                                    new CodeLocation($source, $conditional)
1263
                                ),
1264
                                $source->getSuppressedIssues()
1265
                            )) {
1266
                                // fall through
1267
                            }
1268
                        }
1269
                    }
1270
                }
1271
            }
1272
1273
            return $if_types;
1274
        }
1275
1276
        if ($true_position) {
1277
            if ($true_position === self::ASSIGNMENT_TO_RIGHT) {
1278
                $base_conditional = $conditional->left;
1279
            } elseif ($true_position === self::ASSIGNMENT_TO_LEFT) {
1280
                $base_conditional = $conditional->right;
1281
            } else {
1282
                throw new \UnexpectedValueException('Bad null variable position');
1283
            }
1284
1285
            if ($base_conditional instanceof PhpParser\Node\Expr\FuncCall) {
1286
                $if_types = self::processFunctionCall(
1287
                    $base_conditional,
1288
                    $this_class_name,
1289
                    $source,
1290
                    $codebase,
1291
                    true
1292
                );
1293
            } else {
1294
                $var_name = ExpressionIdentifier::getArrayVarId(
1295
                    $base_conditional,
1296
                    $this_class_name,
1297
                    $source
1298
                );
1299
1300
                if ($var_name) {
1301
                    if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\NotIdentical) {
1302
                        $if_types[$var_name] = [['!true']];
1303
                    } else {
1304
                        $if_types[$var_name] = [['falsy']];
1305
                    }
1306
                } else {
1307
                    $base_assertions = null;
1308
1309
                    if ($source instanceof StatementsAnalyzer && $cache) {
1310
                        $base_assertions = $source->node_data->getAssertions($base_conditional);
1311
                    }
1312
1313
                    if ($base_assertions === null) {
1314
                        $base_assertions = self::scrapeAssertions(
1315
                            $base_conditional,
1316
                            $this_class_name,
1317
                            $source,
1318
                            $codebase,
1319
                            $inside_negation,
1320
                            $cache,
1321
                            $inside_conditional
1322
                        );
1323
1324
                        if ($source instanceof StatementsAnalyzer && $cache) {
1325
                            $source->node_data->setAssertions($base_conditional, $base_assertions);
1326
                        }
1327
                    }
1328
1329
                    if ($base_assertions === null) {
1330
                        throw new \UnexpectedValueException('Assertions should be set');
1331
                    }
1332
1333
                    $notif_types = $base_assertions;
1334
1335
                    if (count($notif_types) === 1) {
1336
                        $if_types = \Psalm\Type\Algebra::negateTypes($notif_types);
1337
                    }
1338
                }
1339
            }
1340
1341
            if ($codebase
1342
                && $source instanceof StatementsAnalyzer
1343
                && ($var_type = $source->node_data->getType($base_conditional))
1344
            ) {
1345
                if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\NotIdentical) {
1346
                    $true_type = Type::getTrue();
1347
1348
                    if (!TypeAnalyzer::isContainedBy(
1349
                        $codebase,
1350
                        $var_type,
1351
                        $true_type
1352
                    ) && !TypeAnalyzer::isContainedBy(
1353
                        $codebase,
1354
                        $true_type,
1355
                        $var_type
1356
                    )) {
1357
                        if ($var_type->from_docblock) {
1358
                            if (IssueBuffer::accepts(
1359
                                new RedundantConditionGivenDocblockType(
1360
                                    'Docblock-asserted type ' . $var_type . ' can never contain true',
1361
                                    new CodeLocation($source, $conditional)
1362
                                ),
1363
                                $source->getSuppressedIssues()
1364
                            )) {
1365
                                // fall through
1366
                            }
1367
                        } else {
1368
                            if (IssueBuffer::accepts(
1369
                                new RedundantCondition(
1370
                                    $var_type . ' can never contain ' . $true_type,
1371
                                    new CodeLocation($source, $conditional)
1372
                                ),
1373
                                $source->getSuppressedIssues()
1374
                            )) {
1375
                                // fall through
1376
                            }
1377
                        }
1378
                    }
1379
                }
1380
            }
1381
1382
            return $if_types;
1383
        }
1384
1385
        if ($empty_array_position !== null) {
1386
            if ($empty_array_position === self::ASSIGNMENT_TO_RIGHT) {
1387
                $base_conditional = $conditional->left;
1388
            } elseif ($empty_array_position === self::ASSIGNMENT_TO_LEFT) {
1389
                $base_conditional = $conditional->right;
1390
            } else {
1391
                throw new \UnexpectedValueException('Bad empty array variable position');
1392
            }
1393
1394
            $var_name = ExpressionIdentifier::getArrayVarId(
1395
                $base_conditional,
1396
                $this_class_name,
1397
                $source
1398
            );
1399
1400
            if ($var_name) {
1401
                if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\NotIdentical) {
1402
                    $if_types[$var_name] = [['non-empty-countable']];
1403
                } else {
1404
                    $if_types[$var_name] = [['!falsy']];
1405
                }
1406
            }
1407
1408
            if ($codebase
1409
                && $source instanceof StatementsAnalyzer
1410
                && ($var_type = $source->node_data->getType($base_conditional))
1411
            ) {
1412
                if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\NotIdentical) {
1413
                    $empty_array_type = Type::getEmptyArray();
1414
1415
                    if (!TypeAnalyzer::isContainedBy(
1416
                        $codebase,
1417
                        $var_type,
1418
                        $empty_array_type
1419
                    ) && !TypeAnalyzer::isContainedBy(
1420
                        $codebase,
1421
                        $empty_array_type,
1422
                        $var_type
1423
                    )) {
1424
                        if ($var_type->from_docblock) {
1425
                            if (IssueBuffer::accepts(
1426
                                new RedundantConditionGivenDocblockType(
1427
                                    'Docblock-asserted type ' . $var_type . ' can never contain null',
1428
                                    new CodeLocation($source, $conditional)
1429
                                ),
1430
                                $source->getSuppressedIssues()
1431
                            )) {
1432
                                // fall through
1433
                            }
1434
                        } else {
1435
                            if (IssueBuffer::accepts(
1436
                                new RedundantCondition(
1437
                                    $var_type . ' can never contain null',
1438
                                    new CodeLocation($source, $conditional)
1439
                                ),
1440
                                $source->getSuppressedIssues()
1441
                            )) {
1442
                                // fall through
1443
                            }
1444
                        }
1445
                    }
1446
                }
1447
            }
1448
1449
            return $if_types;
1450
        }
1451
1452
        if ($gettype_position) {
1453
            if ($gettype_position === self::ASSIGNMENT_TO_RIGHT) {
1454
                $whichclass_expr = $conditional->left;
1455
                $gettype_expr = $conditional->right;
1456
            } elseif ($gettype_position === self::ASSIGNMENT_TO_LEFT) {
1457
                $whichclass_expr = $conditional->right;
1458
                $gettype_expr = $conditional->left;
1459
            } else {
1460
                throw new \UnexpectedValueException('$gettype_position value');
1461
            }
1462
1463
            /** @var PhpParser\Node\Expr\FuncCall $gettype_expr */
1464
            $var_name = ExpressionIdentifier::getArrayVarId(
1465
                $gettype_expr->args[0]->value,
1466
                $this_class_name,
1467
                $source
1468
            );
1469
1470
            if ($whichclass_expr instanceof PhpParser\Node\Scalar\String_) {
1471
                $var_type = $whichclass_expr->value;
1472
            } elseif ($whichclass_expr instanceof PhpParser\Node\Expr\ClassConstFetch
1473
                && $whichclass_expr->class instanceof PhpParser\Node\Name
1474
            ) {
1475
                $var_type = ClassLikeAnalyzer::getFQCLNFromNameObject(
1476
                    $whichclass_expr->class,
1477
                    $source->getAliases()
1478
                );
1479
            } else {
1480
                throw new \UnexpectedValueException('Shouldn’t get here');
1481
            }
1482
1483
            if (!isset(ClassLikeAnalyzer::GETTYPE_TYPES[$var_type])) {
1484
                if (IssueBuffer::accepts(
1485
                    new UnevaluatedCode(
1486
                        'gettype cannot return this value',
1487
                        new CodeLocation($source, $whichclass_expr)
1488
                    )
1489
                )) {
1490
                    // fall through
1491
                }
1492
            } else {
1493
                if ($var_name && $var_type) {
1494
                    $if_types[$var_name] = [['!' . $var_type]];
1495
                }
1496
            }
1497
1498
            return $if_types;
1499
        }
1500
1501
        if (!$source instanceof StatementsAnalyzer) {
1502
            return [];
1503
        }
1504
1505
        $getclass_position = self::hasGetClassCheck($conditional, $source);
1506
        $typed_value_position = self::hasTypedValueComparison($conditional, $source);
1507
1508
        if ($getclass_position) {
1509
            if ($getclass_position === self::ASSIGNMENT_TO_RIGHT) {
1510
                $whichclass_expr = $conditional->left;
1511
                $getclass_expr = $conditional->right;
1512
            } elseif ($getclass_position === self::ASSIGNMENT_TO_LEFT) {
1513
                $whichclass_expr = $conditional->right;
1514
                $getclass_expr = $conditional->left;
1515
            } else {
1516
                throw new \UnexpectedValueException('$getclass_position value');
1517
            }
1518
1519
            if ($getclass_expr instanceof PhpParser\Node\Expr\FuncCall) {
1520
                $var_name = ExpressionIdentifier::getArrayVarId(
1521
                    $getclass_expr->args[0]->value,
1522
                    $this_class_name,
1523
                    $source
1524
                );
1525
            } else {
1526
                $var_name = '$this';
1527
            }
1528
1529
            if ($whichclass_expr instanceof PhpParser\Node\Scalar\String_) {
1530
                $var_type = $whichclass_expr->value;
1531
            } elseif ($whichclass_expr instanceof PhpParser\Node\Expr\ClassConstFetch
1532
                && $whichclass_expr->class instanceof PhpParser\Node\Name
1533
            ) {
1534
                $var_type = ClassLikeAnalyzer::getFQCLNFromNameObject(
1535
                    $whichclass_expr->class,
1536
                    $source->getAliases()
1537
                );
1538
1539
                if ($var_type === 'self' || $var_type === 'static') {
1540
                    $var_type = $this_class_name;
1541
                } elseif ($var_type === 'parent') {
1542
                    $var_type = null;
1543
                }
1544
            } else {
1545
                $type = $source->node_data->getType($whichclass_expr);
1546
1547
                if ($type && $var_name) {
1548
                    foreach ($type->getAtomicTypes() as $type_part) {
1549
                        if ($type_part instanceof Type\Atomic\TTemplateParamClass) {
1550
                            $if_types[$var_name] = [['!=' . $type_part->param_name]];
1551
                        }
1552
                    }
1553
                }
1554
1555
                return $if_types;
1556
            }
1557
1558
            if ($var_type
1559
                && ClassLikeAnalyzer::checkFullyQualifiedClassLikeName(
1560
                    $source,
1561
                    $var_type,
1562
                    new CodeLocation($source, $whichclass_expr),
1563
                    null,
1564
                    null,
1565
                    $source->getSuppressedIssues(),
1566
                    false
1567
                ) === false
1568
            ) {
1569
                // fall through
1570
            } else {
1571
                if ($var_name && $var_type) {
1572
                    $if_types[$var_name] = [['!=getclass-' . $var_type]];
1573
                }
1574
            }
1575
1576
            return $if_types;
1577
        }
1578
1579
        if ($typed_value_position) {
1580
            if ($typed_value_position === self::ASSIGNMENT_TO_RIGHT) {
1581
                /** @var PhpParser\Node\Expr $conditional->right */
1582
                $var_name = ExpressionIdentifier::getArrayVarId(
1583
                    $conditional->left,
1584
                    $this_class_name,
1585
                    $source
1586
                );
1587
1588
                $other_type = $source->node_data->getType($conditional->left);
1589
                $var_type = $source->node_data->getType($conditional->right);
1590
            } elseif ($typed_value_position === self::ASSIGNMENT_TO_LEFT) {
1591
                /** @var PhpParser\Node\Expr $conditional->left */
1592
                $var_name = ExpressionIdentifier::getArrayVarId(
1593
                    $conditional->right,
1594
                    $this_class_name,
1595
                    $source
1596
                );
1597
1598
                $var_type = $source->node_data->getType($conditional->left);
1599
                $other_type = $source->node_data->getType($conditional->right);
1600
            } else {
1601
                throw new \UnexpectedValueException('$typed_value_position value');
1602
            }
1603
1604
            if ($var_type) {
1605
                if ($var_name) {
1606
                    $not_identical = $conditional instanceof PhpParser\Node\Expr\BinaryOp\NotIdentical
1607
                        || ($other_type
1608
                            && (($var_type->isString() && $other_type->isString())
1609
                                || ($var_type->isInt() && $other_type->isInt())
1610
                                || ($var_type->isFloat() && $other_type->isFloat())
1611
                            )
1612
                        );
1613
1614
                    if ($not_identical) {
1615
                        $if_types[$var_name] = [['!=' . $var_type->getAssertionString()]];
1616
                    } else {
1617
                        $if_types[$var_name] = [['!~' . $var_type->getAssertionString()]];
1618
                    }
1619
                }
1620
1621
                if ($codebase
1622
                    && $other_type
1623
                    && $conditional instanceof PhpParser\Node\Expr\BinaryOp\NotIdentical
1624
                ) {
1625
                    $parent_source = $source->getSource();
1626
1627
                    if ($parent_source->getSource() instanceof \Psalm\Internal\Analyzer\TraitAnalyzer
1628
                        && (($var_type->isSingleStringLiteral()
1629
                                && $var_type->getSingleStringLiteral()->value === $this_class_name)
1630
                            || ($other_type->isSingleStringLiteral()
1631
                                && $other_type->getSingleStringLiteral()->value === $this_class_name))
1632
                    ) {
1633
                        // do nothing
1634
                    } elseif (!TypeAnalyzer::canExpressionTypesBeIdentical(
1635
                        $codebase,
1636
                        $other_type,
1637
                        $var_type
1638
                    )) {
1639
                        if ($var_type->from_docblock || $other_type->from_docblock) {
1640
                            if (IssueBuffer::accepts(
1641
                                new DocblockTypeContradiction(
1642
                                    $var_type . ' can never contain ' . $other_type,
1643
                                    new CodeLocation($source, $conditional)
1644
                                ),
1645
                                $source->getSuppressedIssues()
1646
                            )) {
1647
                                // fall through
1648
                            }
1649
                        } else {
1650
                            if (IssueBuffer::accepts(
1651
                                new RedundantCondition(
1652
                                    $var_type->getId() . ' can never contain ' . $other_type->getId(),
1653
                                    new CodeLocation($source, $conditional)
1654
                                ),
1655
                                $source->getSuppressedIssues()
1656
                            )) {
1657
                                // fall through
1658
                            }
1659
                        }
1660
                    }
1661
                }
1662
            }
1663
1664
            return $if_types;
1665
        }
1666
1667
        return [];
1668
    }
1669
1670
    /**
1671
     * @param  PhpParser\Node\Expr\FuncCall $expr
1672
     * @param  string|null                  $this_class_name
1673
     * @param  FileSource                   $source
1674
     * @param  bool                         $negate
1675
     *
1676
     * @return array<string, non-empty-list<non-empty-list<string>>>
0 ignored issues
show
Documentation introduced by
The doc-type array<string, could not be parsed: Expected ">" at position 5, but found "end of type". (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...
1677
     */
1678
    public static function processFunctionCall(
1679
        PhpParser\Node\Expr\FuncCall $expr,
1680
        $this_class_name,
1681
        FileSource $source,
1682
        Codebase $codebase = null,
1683
        $negate = false
1684
    ) {
1685
        $prefix = $negate ? '!' : '';
1686
1687
        $first_var_name = isset($expr->args[0]->value)
1688
            ? ExpressionIdentifier::getArrayVarId(
1689
                $expr->args[0]->value,
1690
                $this_class_name,
1691
                $source
1692
            )
1693
            : null;
1694
1695
        $if_types = [];
1696
1697
        $first_var_type = isset($expr->args[0]->value)
1698
            && $source instanceof StatementsAnalyzer
1699
            ? $source->node_data->getType($expr->args[0]->value)
1700
            : null;
1701
1702
        if (self::hasNullCheck($expr)) {
1703
            if ($first_var_name) {
1704
                $if_types[$first_var_name] = [[$prefix . 'null']];
1705
            }
1706
        } elseif ($source instanceof StatementsAnalyzer && self::hasIsACheck($expr, $source)) {
1707
            if ($expr->args[0]->value instanceof PhpParser\Node\Expr\ClassConstFetch
1708
                && $expr->args[0]->value->name instanceof PhpParser\Node\Identifier
1709
                && strtolower($expr->args[0]->value->name->name) === 'class'
1710
                && $expr->args[0]->value->class instanceof PhpParser\Node\Name
1711
                && count($expr->args[0]->value->class->parts) === 1
1712
                && strtolower($expr->args[0]->value->class->parts[0]) === 'static'
1713
            ) {
1714
                $first_var_name = '$this';
1715
            }
1716
1717
            if ($first_var_name) {
1718
                $first_arg = $expr->args[0]->value;
1719
                $second_arg = $expr->args[1]->value;
1720
                $third_arg = isset($expr->args[2]->value) ? $expr->args[2]->value : null;
1721
1722
                if ($third_arg instanceof PhpParser\Node\Expr\ConstFetch) {
1723
                    if (!in_array(strtolower($third_arg->name->parts[0]), ['true', 'false'])) {
1724
                        return $if_types;
1725
                    }
1726
1727
                    $third_arg_value = strtolower($third_arg->name->parts[0]);
1728
                } else {
1729
                    $third_arg_value = $expr->name instanceof PhpParser\Node\Name
1730
                        && strtolower($expr->name->parts[0]) === 'is_subclass_of'
1731
                        ? 'true'
1732
                        : 'false';
1733
                }
1734
1735
                $is_a_prefix = $third_arg_value === 'true' ? 'isa-string-' : 'isa-';
1736
1737
                if ($first_arg
1738
                    && ($first_arg_type = $source->node_data->getType($first_arg))
1739
                    && $first_arg_type->isSingleStringLiteral()
1740
                    && $source->getSource()->getSource() instanceof \Psalm\Internal\Analyzer\TraitAnalyzer
1741
                    && $first_arg_type->getSingleStringLiteral()->value === $this_class_name
1742
                ) {
1743
                    // do nothing
1744
                } else {
1745
                    if ($second_arg instanceof PhpParser\Node\Scalar\String_) {
1746
                        $fq_class_name = $second_arg->value;
1747
                        if ($fq_class_name[0] === '\\') {
1748
                            $fq_class_name = substr($fq_class_name, 1);
1749
                        }
1750
1751
                        $if_types[$first_var_name] = [[$prefix . $is_a_prefix . $fq_class_name]];
1752
                    } elseif ($second_arg instanceof PhpParser\Node\Expr\ClassConstFetch
1753
                        && $second_arg->class instanceof PhpParser\Node\Name
1754
                        && $second_arg->name instanceof PhpParser\Node\Identifier
1755
                        && strtolower($second_arg->name->name) === 'class'
1756
                    ) {
1757
                        $class_node = $second_arg->class;
1758
1759
                        if ($class_node->parts === ['static']) {
1760
                            if ($this_class_name) {
1761
                                $if_types[$first_var_name] = [[$prefix . $is_a_prefix . $this_class_name . '&static']];
1762
                            }
1763
                        } elseif ($class_node->parts === ['self']) {
1764
                            if ($this_class_name) {
1765
                                $if_types[$first_var_name] = [[$prefix . $is_a_prefix . $this_class_name]];
1766
                            }
1767
                        } elseif ($class_node->parts === ['parent']) {
1768
                            // do nothing
1769
                        } else {
1770
                            $if_types[$first_var_name] = [[
1771
                                $prefix . $is_a_prefix
1772
                                    . ClassLikeAnalyzer::getFQCLNFromNameObject(
1773
                                        $class_node,
1774
                                        $source->getAliases()
1775
                                    )
1776
                            ]];
1777
                        }
1778
                    } elseif (($second_arg_type = $source->node_data->getType($second_arg))
1779
                        && $second_arg_type->hasString()
1780
                    ) {
1781
                        $vals = [];
1782
1783
                        foreach ($second_arg_type->getAtomicTypes() as $second_arg_atomic_type) {
1784
                            if ($second_arg_atomic_type instanceof Type\Atomic\TTemplateParamClass) {
1785
                                $vals[] = [$prefix . $is_a_prefix . $second_arg_atomic_type->param_name];
1786
                            }
1787
                        }
1788
1789
                        if ($vals) {
1790
                            $if_types[$first_var_name] = $vals;
1791
                        }
1792
                    }
1793
                }
1794
            }
1795
        } elseif (self::hasArrayCheck($expr)) {
1796
            if ($first_var_name) {
1797
                $if_types[$first_var_name] = [[$prefix . 'array']];
1798
            } elseif ($first_var_type
1799
                && $codebase
1800
                && $source instanceof StatementsAnalyzer
1801
            ) {
1802
                self::processIrreconcilableFunctionCall(
1803
                    $first_var_type,
1804
                    Type::getArray(),
1805
                    $expr,
1806
                    $source,
1807
                    $codebase,
1808
                    $negate
1809
                );
1810
            }
1811
        } elseif (self::hasBoolCheck($expr)) {
1812
            if ($first_var_name) {
1813
                $if_types[$first_var_name] = [[$prefix . 'bool']];
1814
            } elseif ($first_var_type
1815
                && $codebase
1816
                && $source instanceof StatementsAnalyzer
1817
            ) {
1818
                self::processIrreconcilableFunctionCall(
1819
                    $first_var_type,
1820
                    Type::getBool(),
1821
                    $expr,
1822
                    $source,
1823
                    $codebase,
1824
                    $negate
1825
                );
1826
            }
1827
        } elseif (self::hasStringCheck($expr)) {
1828
            if ($first_var_name) {
1829
                $if_types[$first_var_name] = [[$prefix . 'string']];
1830
            } elseif ($first_var_type
1831
                && $codebase
1832
                && $source instanceof StatementsAnalyzer
1833
            ) {
1834
                self::processIrreconcilableFunctionCall(
1835
                    $first_var_type,
1836
                    Type::getString(),
1837
                    $expr,
1838
                    $source,
1839
                    $codebase,
1840
                    $negate
1841
                );
1842
            }
1843
        } elseif (self::hasObjectCheck($expr)) {
1844
            if ($first_var_name) {
1845
                $if_types[$first_var_name] = [[$prefix . 'object']];
1846
            } elseif ($first_var_type
1847
                && $codebase
1848
                && $source instanceof StatementsAnalyzer
1849
            ) {
1850
                self::processIrreconcilableFunctionCall(
1851
                    $first_var_type,
1852
                    Type::getObject(),
1853
                    $expr,
1854
                    $source,
1855
                    $codebase,
1856
                    $negate
1857
                );
1858
            }
1859
        } elseif (self::hasNumericCheck($expr)) {
1860
            if ($first_var_name) {
1861
                $if_types[$first_var_name] = [[$prefix . 'numeric']];
1862
            } elseif ($first_var_type
1863
                && $codebase
1864
                && $source instanceof StatementsAnalyzer
1865
            ) {
1866
                self::processIrreconcilableFunctionCall(
1867
                    $first_var_type,
1868
                    Type::getNumeric(),
1869
                    $expr,
1870
                    $source,
1871
                    $codebase,
1872
                    $negate
1873
                );
1874
            }
1875
        } elseif (self::hasIntCheck($expr)) {
1876
            if ($first_var_name) {
1877
                $if_types[$first_var_name] = [[$prefix . 'int']];
1878
            } elseif ($first_var_type
1879
                && $codebase
1880
                && $source instanceof StatementsAnalyzer
1881
            ) {
1882
                self::processIrreconcilableFunctionCall(
1883
                    $first_var_type,
1884
                    Type::getInt(),
1885
                    $expr,
1886
                    $source,
1887
                    $codebase,
1888
                    $negate
1889
                );
1890
            }
1891
        } elseif (self::hasFloatCheck($expr)) {
1892
            if ($first_var_name) {
1893
                $if_types[$first_var_name] = [[$prefix . 'float']];
1894
            } elseif ($first_var_type
1895
                && $codebase
1896
                && $source instanceof StatementsAnalyzer
1897
            ) {
1898
                self::processIrreconcilableFunctionCall(
1899
                    $first_var_type,
1900
                    Type::getFloat(),
1901
                    $expr,
1902
                    $source,
1903
                    $codebase,
1904
                    $negate
1905
                );
1906
            }
1907
        } elseif (self::hasResourceCheck($expr)) {
1908
            if ($first_var_name) {
1909
                $if_types[$first_var_name] = [[$prefix . 'resource']];
1910
            } elseif ($first_var_type
1911
                && $codebase
1912
                && $source instanceof StatementsAnalyzer
1913
            ) {
1914
                self::processIrreconcilableFunctionCall(
1915
                    $first_var_type,
1916
                    Type::getResource(),
1917
                    $expr,
1918
                    $source,
1919
                    $codebase,
1920
                    $negate
1921
                );
1922
            }
1923
        } elseif (self::hasScalarCheck($expr)) {
1924
            if ($first_var_name) {
1925
                $if_types[$first_var_name] = [[$prefix . 'scalar']];
1926
            } elseif ($first_var_type
1927
                && $codebase
1928
                && $source instanceof StatementsAnalyzer
1929
            ) {
1930
                self::processIrreconcilableFunctionCall(
1931
                    $first_var_type,
1932
                    Type::getScalar(),
1933
                    $expr,
1934
                    $source,
1935
                    $codebase,
1936
                    $negate
1937
                );
1938
            }
1939
        } elseif (self::hasCallableCheck($expr)) {
1940
            if ($first_var_name) {
1941
                $if_types[$first_var_name] = [[$prefix . 'callable']];
1942
            } elseif ($expr->args[0]->value instanceof PhpParser\Node\Expr\Array_
1943
                && isset($expr->args[0]->value->items[0], $expr->args[0]->value->items[1])
1944
                && $expr->args[0]->value->items[1]->value instanceof PhpParser\Node\Scalar\String_
1945
            ) {
1946
                $first_var_name_in_array_argument = ExpressionIdentifier::getArrayVarId(
1947
                    $expr->args[0]->value->items[0]->value,
1948
                    $this_class_name,
1949
                    $source
1950
                );
1951
                if ($first_var_name_in_array_argument) {
1952
                    $if_types[$first_var_name_in_array_argument] = [
1953
                        [$prefix . 'hasmethod-' . $expr->args[0]->value->items[1]->value->value]
1954
                    ];
1955
                }
1956
            }
1957
        } elseif (self::hasIterableCheck($expr)) {
1958
            if ($first_var_name) {
1959
                $if_types[$first_var_name] = [[$prefix . 'iterable']];
1960
            }
1961
        } elseif (self::hasCountableCheck($expr)) {
1962
            if ($first_var_name) {
1963
                $if_types[$first_var_name] = [[$prefix . 'countable']];
1964
            }
1965
        } elseif ($class_exists_check_type = self::hasClassExistsCheck($expr)) {
1966
            if ($first_var_name) {
1967
                if ($class_exists_check_type === 2 || $prefix) {
1968
                    $if_types[$first_var_name] = [[$prefix . 'class-string']];
1969
                } else {
1970
                    $if_types[$first_var_name] = [['=class-string']];
1971
                }
1972
            }
1973
        } elseif ($class_exists_check_type = self::hasTraitExistsCheck($expr)) {
1974
            if ($first_var_name) {
1975
                if ($class_exists_check_type === 2 || $prefix) {
1976
                    $if_types[$first_var_name] = [[$prefix . 'trait-string']];
1977
                } else {
1978
                    $if_types[$first_var_name] = [['=trait-string']];
1979
                }
1980
            }
1981
        } elseif (self::hasInterfaceExistsCheck($expr)) {
1982
            if ($first_var_name) {
1983
                $if_types[$first_var_name] = [[$prefix . 'interface-string']];
1984
            }
1985
        } elseif (self::hasFunctionExistsCheck($expr)) {
1986
            if ($first_var_name) {
1987
                $if_types[$first_var_name] = [[$prefix . 'callable-string']];
1988
            }
1989
        } elseif ($expr->name instanceof PhpParser\Node\Name
1990
            && strtolower($expr->name->parts[0]) === 'method_exists'
1991
            && isset($expr->args[1])
1992
            && $expr->args[1]->value instanceof PhpParser\Node\Scalar\String_
1993
        ) {
1994
            if ($first_var_name) {
1995
                $if_types[$first_var_name] = [[$prefix . 'hasmethod-' . $expr->args[1]->value->value]];
1996
            }
1997
        } elseif (self::hasInArrayCheck($expr) && $source instanceof StatementsAnalyzer) {
1998
            if ($first_var_name
1999
                && ($second_arg_type = $source->node_data->getType($expr->args[1]->value))
2000
            ) {
2001
                foreach ($second_arg_type->getAtomicTypes() as $atomic_type) {
2002
                    if ($atomic_type instanceof Type\Atomic\TArray
2003
                        || $atomic_type instanceof Type\Atomic\ObjectLike
2004
                    ) {
2005
                        if ($atomic_type instanceof Type\Atomic\ObjectLike) {
2006
                            $atomic_type = $atomic_type->getGenericArrayType();
2007
                        }
2008
2009
                        $array_literal_types = array_merge(
2010
                            $atomic_type->type_params[1]->getLiteralStrings(),
2011
                            $atomic_type->type_params[1]->getLiteralInts(),
2012
                            $atomic_type->type_params[1]->getLiteralFloats()
2013
                        );
2014
2015
                        if ($array_literal_types
2016
                            && count($atomic_type->type_params[1]->getAtomicTypes())
2017
                        ) {
2018
                            $literal_assertions = [];
2019
2020
                            foreach ($array_literal_types as $array_literal_type) {
2021
                                $literal_assertions[] = '=' . $array_literal_type->getId();
2022
                            }
2023
2024
                            if ($negate) {
2025
                                $if_types = \Psalm\Type\Algebra::negateTypes([
2026
                                    $first_var_name => [$literal_assertions]
2027
                                ]);
2028
                            } else {
2029
                                $if_types[$first_var_name] = [$literal_assertions];
2030
                            }
2031
                        }
2032
                    }
2033
                }
2034
            }
2035
        } elseif (self::hasArrayKeyExistsCheck($expr)) {
2036
            $array_root = isset($expr->args[1]->value)
2037
                ? ExpressionIdentifier::getArrayVarId(
2038
                    $expr->args[1]->value,
2039
                    $this_class_name,
2040
                    $source
2041
                )
2042
                : null;
2043
2044
            if ($first_var_name === null && isset($expr->args[0])) {
2045
                $first_arg = $expr->args[0];
2046
2047
                if ($first_arg->value instanceof PhpParser\Node\Scalar\String_) {
2048
                    $first_var_name = '\'' . $first_arg->value->value . '\'';
2049
                } elseif ($first_arg->value instanceof PhpParser\Node\Scalar\LNumber) {
2050
                    $first_var_name = (string) $first_arg->value->value;
2051
                }
2052
            }
2053
2054
            if ($expr->args[0]->value instanceof PhpParser\Node\Expr\ClassConstFetch
2055
                && $expr->args[0]->value->name instanceof PhpParser\Node\Identifier
2056
                && $expr->args[0]->value->name->name !== 'class'
2057
            ) {
2058
                $const_type = null;
2059
2060
                if ($source instanceof StatementsAnalyzer) {
2061
                    $const_type = $source->node_data->getType($expr->args[0]->value);
2062
                }
2063
2064
                if ($const_type) {
2065
                    if ($const_type->isSingleStringLiteral()) {
2066
                        $first_var_name = $const_type->getSingleStringLiteral()->value;
2067
                    } elseif ($const_type->isSingleIntLiteral()) {
2068
                        $first_var_name = (string) $const_type->getSingleIntLiteral()->value;
2069
                    } else {
2070
                        $first_var_name = null;
2071
                    }
2072
                } else {
2073
                    $first_var_name = null;
2074
                }
2075
            }
2076
2077
            if ($first_var_name !== null
2078
                && $array_root
2079
                && !strpos($first_var_name, '->')
2080
                && !strpos($first_var_name, '[')
2081
            ) {
2082
                $if_types[$array_root . '[' . $first_var_name . ']'] = [[$prefix . 'array-key-exists']];
2083
            }
2084
        } elseif (self::hasNonEmptyCountCheck($expr)) {
2085
            if ($first_var_name) {
2086
                $if_types[$first_var_name] = [[$prefix . 'non-empty-countable']];
2087
            }
2088
        } else {
2089
            $if_types = self::processCustomAssertion($expr, $this_class_name, $source, $negate);
2090
        }
2091
2092
        return $if_types;
2093
    }
2094
2095
    private static function processIrreconcilableFunctionCall(
2096
        Type\Union $first_var_type,
2097
        Type\Union $expected_type,
2098
        PhpParser\Node\Expr $expr,
2099
        StatementsAnalyzer $source,
2100
        Codebase $codebase,
2101
        bool $negate
2102
    ) : void {
2103
        if ($first_var_type->hasMixed()) {
2104
            return;
2105
        }
2106
2107
        if (!TypeAnalyzer::isContainedBy(
2108
            $codebase,
2109
            $first_var_type,
2110
            $expected_type
2111
        )) {
2112
            return;
2113
        }
2114
2115
        if (!$negate) {
2116
            if ($first_var_type->from_docblock) {
2117
                if (IssueBuffer::accepts(
2118
                    new RedundantConditionGivenDocblockType(
2119
                        'Docblock type ' . $first_var_type . ' always contains ' . $expected_type,
2120
                        new CodeLocation($source, $expr)
2121
                    ),
2122
                    $source->getSuppressedIssues()
2123
                )) {
2124
                    // fall through
2125
                }
2126
            } else {
2127
                if (IssueBuffer::accepts(
2128
                    new RedundantCondition(
2129
                        $first_var_type . ' always contains ' . $expected_type,
2130
                        new CodeLocation($source, $expr)
2131
                    ),
2132
                    $source->getSuppressedIssues()
2133
                )) {
2134
                    // fall through
2135
                }
2136
            }
2137
        } else {
2138
            if ($first_var_type->from_docblock) {
2139
                if (IssueBuffer::accepts(
2140
                    new DocblockTypeContradiction(
2141
                        $first_var_type . ' does not contain ' . $expected_type,
2142
                        new CodeLocation($source, $expr)
2143
                    ),
2144
                    $source->getSuppressedIssues()
2145
                )) {
2146
                    // fall through
2147
                }
2148
            } else {
2149
                if (IssueBuffer::accepts(
2150
                    new TypeDoesNotContainType(
2151
                        $first_var_type . ' does not contain ' . $expected_type,
2152
                        new CodeLocation($source, $expr)
2153
                    ),
2154
                    $source->getSuppressedIssues()
2155
                )) {
2156
                    // fall through
2157
                }
2158
            }
2159
        }
2160
    }
2161
2162
    /**
2163
     * @param  PhpParser\Node\Expr\FuncCall|PhpParser\Node\Expr\MethodCall|PhpParser\Node\Expr\StaticCall $expr
2164
     * @param  string|null  $this_class_name
2165
     * @param  FileSource   $source
2166
     * @param  bool         $negate
2167
     *
2168
     * @return array<string, non-empty-list<non-empty-list<string>>>
0 ignored issues
show
Documentation introduced by
The doc-type array<string, could not be parsed: Expected ">" at position 5, but found "end of type". (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...
2169
     */
2170
    protected static function processCustomAssertion(
2171
        $expr,
2172
        $this_class_name,
2173
        FileSource $source,
2174
        $negate = false
2175
    ) {
2176
        if (!$source instanceof StatementsAnalyzer) {
2177
            return [];
2178
        }
2179
2180
        $if_true_assertions = $source->node_data->getIfTrueAssertions($expr);
2181
        $if_false_assertions = $source->node_data->getIfFalseAssertions($expr);
2182
2183
        if ($if_true_assertions === null && $if_false_assertions === null) {
2184
            return [];
2185
        }
2186
2187
        $prefix = $negate ? '!' : '';
2188
2189
        $first_var_name = isset($expr->args[0]->value)
2190
            ? ExpressionIdentifier::getArrayVarId(
2191
                $expr->args[0]->value,
2192
                $this_class_name,
2193
                $source
2194
            )
2195
            : null;
2196
2197
        $if_types = [];
2198
2199
        if ($if_true_assertions) {
2200
            foreach ($if_true_assertions as $assertion) {
2201
                if (is_int($assertion->var_id) && isset($expr->args[$assertion->var_id])) {
2202
                    if ($assertion->var_id === 0) {
2203
                        $var_name = $first_var_name;
2204
                    } else {
2205
                        $var_name = ExpressionIdentifier::getArrayVarId(
2206
                            $expr->args[$assertion->var_id]->value,
2207
                            $this_class_name,
2208
                            $source
2209
                        );
2210
                    }
2211
2212
                    if ($var_name) {
2213
                        if ($prefix === $assertion->rule[0][0][0]) {
2214
                            $if_types[$var_name] = [[substr($assertion->rule[0][0], 1)]];
2215
                        } else {
2216
                            $if_types[$var_name] = [[$prefix . $assertion->rule[0][0]]];
2217
                        }
2218
                    }
2219
                } elseif ($assertion->var_id === '$this' && $expr instanceof PhpParser\Node\Expr\MethodCall) {
2220
                    $var_id = ExpressionIdentifier::getArrayVarId(
2221
                        $expr->var,
2222
                        $this_class_name,
2223
                        $source
2224
                    );
2225
2226
                    if ($var_id) {
2227
                        if ($prefix === $assertion->rule[0][0][0]) {
2228
                            $if_types[$var_id] = [[substr($assertion->rule[0][0], 1)]];
2229
                        } else {
2230
                            $if_types[$var_id] = [[$prefix . $assertion->rule[0][0]]];
2231
                        }
2232
                    }
2233
                } elseif (\is_string($assertion->var_id)
2234
                    && $expr instanceof PhpParser\Node\Expr\MethodCall
2235
                ) {
2236
                    if ($prefix === $assertion->rule[0][0][0]) {
2237
                        $if_types[$assertion->var_id] = [[substr($assertion->rule[0][0], 1)]];
2238
                    } else {
2239
                        $if_types[$assertion->var_id] = [[$prefix . $assertion->rule[0][0]]];
2240
                    }
2241
                }
2242
            }
2243
        }
2244
2245
        if ($if_false_assertions) {
2246
            $negated_prefix = !$negate ? '!' : '';
2247
2248
            foreach ($if_false_assertions as $assertion) {
2249
                if (is_int($assertion->var_id) && isset($expr->args[$assertion->var_id])) {
2250
                    if ($assertion->var_id === 0) {
2251
                        $var_name = $first_var_name;
2252
                    } else {
2253
                        $var_name = ExpressionIdentifier::getArrayVarId(
2254
                            $expr->args[$assertion->var_id]->value,
2255
                            $this_class_name,
2256
                            $source
2257
                        );
2258
                    }
2259
2260
                    if ($var_name) {
2261
                        if ($negated_prefix === $assertion->rule[0][0][0]) {
2262
                            $if_types[$var_name] = [[substr($assertion->rule[0][0], 1)]];
2263
                        } else {
2264
                            $if_types[$var_name] = [[$negated_prefix . $assertion->rule[0][0]]];
2265
                        }
2266
                    }
2267
                } elseif ($assertion->var_id === '$this' && $expr instanceof PhpParser\Node\Expr\MethodCall) {
2268
                    $var_id = ExpressionIdentifier::getArrayVarId(
2269
                        $expr->var,
2270
                        $this_class_name,
2271
                        $source
2272
                    );
2273
2274
                    if ($var_id) {
2275
                        if ($negated_prefix === $assertion->rule[0][0][0]) {
2276
                            $if_types[$var_id] = [[substr($assertion->rule[0][0], 1)]];
2277
                        } else {
2278
                            $if_types[$var_id] = [[$negated_prefix . $assertion->rule[0][0]]];
2279
                        }
2280
                    }
2281
                } elseif (\is_string($assertion->var_id)
2282
                    && $expr instanceof PhpParser\Node\Expr\MethodCall
2283
                ) {
2284
                    if ($prefix === $assertion->rule[0][0][0]) {
2285
                        $if_types[$assertion->var_id] = [[substr($assertion->rule[0][0], 1)]];
2286
                    } else {
2287
                        $if_types[$assertion->var_id] = [[$negated_prefix . $assertion->rule[0][0]]];
2288
                    }
2289
                }
2290
            }
2291
        }
2292
2293
        return $if_types;
2294
    }
2295
2296
    /**
2297
     * @param  PhpParser\Node\Expr\Instanceof_ $stmt
2298
     * @param  string|null                     $this_class_name
2299
     * @param  FileSource                $source
2300
     *
2301
     * @return list<string>
0 ignored issues
show
Documentation introduced by
The doc-type list<string> could not be parsed: Expected "|" or "end of type", but got "<" at position 4. (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...
2302
     */
2303
    protected static function getInstanceOfTypes(
2304
        PhpParser\Node\Expr\Instanceof_ $stmt,
2305
        $this_class_name,
2306
        FileSource $source
2307
    ) {
2308
        if ($stmt->class instanceof PhpParser\Node\Name) {
2309
            if (!in_array(strtolower($stmt->class->parts[0]), ['self', 'static', 'parent'], true)) {
2310
                $instanceof_class = ClassLikeAnalyzer::getFQCLNFromNameObject(
2311
                    $stmt->class,
2312
                    $source->getAliases()
2313
                );
2314
2315
                if ($source instanceof StatementsAnalyzer) {
2316
                    $codebase = $source->getCodebase();
2317
                    $instanceof_class = $codebase->classlikes->getUnAliasedName($instanceof_class);
2318
                }
2319
2320
                return [$instanceof_class];
2321
            } elseif ($this_class_name
2322
                && (in_array(strtolower($stmt->class->parts[0]), ['self', 'static'], true))
2323
            ) {
2324
                if ($stmt->class->parts[0] === 'static') {
2325
                    return ['=' . $this_class_name . '&static'];
2326
                }
2327
2328
                return [$this_class_name];
2329
            }
2330
        } elseif ($source instanceof StatementsAnalyzer) {
2331
            $stmt_class_type = $source->node_data->getType($stmt->class);
2332
2333
            if ($stmt_class_type) {
2334
                $literal_class_strings = [];
2335
2336
                foreach ($stmt_class_type->getAtomicTypes() as $atomic_type) {
2337
                    if ($atomic_type instanceof Type\Atomic\TLiteralClassString) {
2338
                        $literal_class_strings[] = $atomic_type->value;
2339
                    } elseif ($atomic_type instanceof Type\Atomic\TTemplateParamClass) {
2340
                        $literal_class_strings[] = $atomic_type->param_name;
2341
                    }
2342
                }
2343
2344
                return $literal_class_strings;
2345
            }
2346
        }
2347
2348
        return [];
2349
    }
2350
2351
    /**
2352
     * @param   PhpParser\Node\Expr\BinaryOp    $conditional
2353
     *
2354
     * @return  int|null
2355
     */
2356
    protected static function hasNullVariable(PhpParser\Node\Expr\BinaryOp $conditional)
2357
    {
2358
        if ($conditional->right instanceof PhpParser\Node\Expr\ConstFetch
2359
            && strtolower($conditional->right->name->parts[0]) === 'null'
2360
        ) {
2361
            return self::ASSIGNMENT_TO_RIGHT;
2362
        }
2363
2364
        if ($conditional->left instanceof PhpParser\Node\Expr\ConstFetch
2365
            && strtolower($conditional->left->name->parts[0]) === 'null'
2366
        ) {
2367
            return self::ASSIGNMENT_TO_LEFT;
2368
        }
2369
2370
        return null;
2371
    }
2372
2373
    /**
2374
     * @param   PhpParser\Node\Expr\BinaryOp    $conditional
2375
     *
2376
     * @return  int|null
2377
     */
2378
    public static function hasFalseVariable(PhpParser\Node\Expr\BinaryOp $conditional)
2379
    {
2380
        if ($conditional->right instanceof PhpParser\Node\Expr\ConstFetch
2381
            && strtolower($conditional->right->name->parts[0]) === 'false'
2382
        ) {
2383
            return self::ASSIGNMENT_TO_RIGHT;
2384
        }
2385
2386
        if ($conditional->left instanceof PhpParser\Node\Expr\ConstFetch
2387
            && strtolower($conditional->left->name->parts[0]) === 'false'
2388
        ) {
2389
            return self::ASSIGNMENT_TO_LEFT;
2390
        }
2391
2392
        return null;
2393
    }
2394
2395
    /**
2396
     * @param   PhpParser\Node\Expr\BinaryOp    $conditional
2397
     *
2398
     * @return  int|null
2399
     */
2400
    public static function hasTrueVariable(PhpParser\Node\Expr\BinaryOp $conditional)
2401
    {
2402
        if ($conditional->right instanceof PhpParser\Node\Expr\ConstFetch
2403
            && strtolower($conditional->right->name->parts[0]) === 'true'
2404
        ) {
2405
            return self::ASSIGNMENT_TO_RIGHT;
2406
        }
2407
2408
        if ($conditional->left instanceof PhpParser\Node\Expr\ConstFetch
2409
            && strtolower($conditional->left->name->parts[0]) === 'true'
2410
        ) {
2411
            return self::ASSIGNMENT_TO_LEFT;
2412
        }
2413
2414
        return null;
2415
    }
2416
2417
    /**
2418
     * @param   PhpParser\Node\Expr\BinaryOp    $conditional
2419
     *
2420
     * @return  int|null
2421
     */
2422
    protected static function hasEmptyArrayVariable(PhpParser\Node\Expr\BinaryOp $conditional)
2423
    {
2424
        if ($conditional->right instanceof PhpParser\Node\Expr\Array_
2425
            && !$conditional->right->items
2426
        ) {
2427
            return self::ASSIGNMENT_TO_RIGHT;
2428
        }
2429
2430
        if ($conditional->left instanceof PhpParser\Node\Expr\Array_
2431
            && !$conditional->left->items
2432
        ) {
2433
            return self::ASSIGNMENT_TO_LEFT;
2434
        }
2435
2436
        return null;
2437
    }
2438
2439
    /**
2440
     * @param   PhpParser\Node\Expr\BinaryOp    $conditional
2441
     *
2442
     * @return  false|int
2443
     */
2444
    protected static function hasGetTypeCheck(PhpParser\Node\Expr\BinaryOp $conditional)
2445
    {
2446
        if ($conditional->right instanceof PhpParser\Node\Expr\FuncCall
2447
            && $conditional->right->name instanceof PhpParser\Node\Name
2448
            && strtolower($conditional->right->name->parts[0]) === 'gettype'
2449
            && $conditional->right->args
2450
            && $conditional->left instanceof PhpParser\Node\Scalar\String_
2451
        ) {
2452
            return self::ASSIGNMENT_TO_RIGHT;
2453
        }
2454
2455
        if ($conditional->left instanceof PhpParser\Node\Expr\FuncCall
2456
            && $conditional->left->name instanceof PhpParser\Node\Name
2457
            && strtolower($conditional->left->name->parts[0]) === 'gettype'
2458
            && $conditional->left->args
2459
            && $conditional->right instanceof PhpParser\Node\Scalar\String_
2460
        ) {
2461
            return self::ASSIGNMENT_TO_LEFT;
2462
        }
2463
2464
        return false;
2465
    }
2466
2467
    /**
2468
     * @param   PhpParser\Node\Expr\BinaryOp    $conditional
2469
     *
2470
     * @return  false|int
2471
     */
2472
    protected static function hasGetClassCheck(
2473
        PhpParser\Node\Expr\BinaryOp $conditional,
2474
        FileSource $source
2475
    ) {
2476
        if (!$source instanceof StatementsAnalyzer) {
2477
            return false;
2478
        }
2479
2480
        $right_get_class = $conditional->right instanceof PhpParser\Node\Expr\FuncCall
2481
            && $conditional->right->name instanceof PhpParser\Node\Name
2482
            && strtolower($conditional->right->name->parts[0]) === 'get_class';
2483
2484
        $right_static_class = $conditional->right instanceof PhpParser\Node\Expr\ClassConstFetch
2485
            && $conditional->right->class instanceof PhpParser\Node\Name
2486
            && $conditional->right->class->parts === ['static']
2487
            && $conditional->right->name instanceof PhpParser\Node\Identifier
2488
            && strtolower($conditional->right->name->name) === 'class';
2489
2490
        $left_class_string = $conditional->left instanceof PhpParser\Node\Expr\ClassConstFetch
2491
            && $conditional->left->class instanceof PhpParser\Node\Name
2492
            && $conditional->left->name instanceof PhpParser\Node\Identifier
2493
            && strtolower($conditional->left->name->name) === 'class';
2494
2495
        $left_type = $source->node_data->getType($conditional->left);
2496
2497
        $left_class_string_t = false;
2498
2499
        if ($left_type && $left_type->isSingle()) {
2500
            foreach ($left_type->getAtomicTypes() as $type_part) {
2501
                if ($type_part instanceof Type\Atomic\TClassString) {
2502
                    $left_class_string_t = true;
2503
                }
2504
            }
2505
        }
2506
2507
        if (($right_get_class || $right_static_class) && ($left_class_string || $left_class_string_t)) {
2508
            return self::ASSIGNMENT_TO_RIGHT;
2509
        }
2510
2511
        $left_get_class = $conditional->left instanceof PhpParser\Node\Expr\FuncCall
2512
            && $conditional->left->name instanceof PhpParser\Node\Name
2513
            && strtolower($conditional->left->name->parts[0]) === 'get_class';
2514
2515
        $left_static_class = $conditional->left instanceof PhpParser\Node\Expr\ClassConstFetch
2516
            && $conditional->left->class instanceof PhpParser\Node\Name
2517
            && $conditional->left->class->parts === ['static']
2518
            && $conditional->left->name instanceof PhpParser\Node\Identifier
2519
            && strtolower($conditional->left->name->name) === 'class';
2520
2521
        $right_class_string = $conditional->right instanceof PhpParser\Node\Expr\ClassConstFetch
2522
            && $conditional->right->class instanceof PhpParser\Node\Name
2523
            && $conditional->right->name instanceof PhpParser\Node\Identifier
2524
            && strtolower($conditional->right->name->name) === 'class';
2525
2526
        $right_type = $source->node_data->getType($conditional->right);
2527
2528
        $right_class_string_t = false;
2529
2530
        if ($right_type && $right_type->isSingle()) {
2531
            foreach ($right_type->getAtomicTypes() as $type_part) {
2532
                if ($type_part instanceof Type\Atomic\TClassString) {
2533
                    $right_class_string_t = true;
2534
                }
2535
            }
2536
        }
2537
2538
        if (($left_get_class || $left_static_class) && ($right_class_string || $right_class_string_t)) {
2539
            return self::ASSIGNMENT_TO_LEFT;
2540
        }
2541
2542
        return false;
2543
    }
2544
2545
    /**
2546
     * @param   PhpParser\Node\Expr\BinaryOp    $conditional
2547
     *
2548
     * @return  false|int
2549
     */
2550
    protected static function hasNonEmptyCountEqualityCheck(
2551
        PhpParser\Node\Expr\BinaryOp $conditional,
2552
        ?int &$min_count
2553
    ) {
2554
        $left_count = $conditional->left instanceof PhpParser\Node\Expr\FuncCall
2555
            && $conditional->left->name instanceof PhpParser\Node\Name
2556
            && strtolower($conditional->left->name->parts[0]) === 'count'
2557
            && $conditional->left->args;
2558
2559
        $operator_greater_than_or_equal =
2560
            $conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical
2561
            || $conditional instanceof PhpParser\Node\Expr\BinaryOp\Equal
2562
            || $conditional instanceof PhpParser\Node\Expr\BinaryOp\Greater
2563
            || $conditional instanceof PhpParser\Node\Expr\BinaryOp\GreaterOrEqual;
2564
2565
        if ($left_count
2566
            && $conditional->right instanceof PhpParser\Node\Scalar\LNumber
2567
            && $operator_greater_than_or_equal
2568
            && $conditional->right->value >= (
2569
                $conditional instanceof PhpParser\Node\Expr\BinaryOp\Greater
2570
                ? 0
2571
                : 1
2572
            )
2573
        ) {
2574
            $min_count = $conditional->right->value +
2575
                ($conditional instanceof PhpParser\Node\Expr\BinaryOp\Greater ? 1 : 0);
2576
2577
            return self::ASSIGNMENT_TO_RIGHT;
2578
        }
2579
2580
        $right_count = $conditional->right instanceof PhpParser\Node\Expr\FuncCall
2581
            && $conditional->right->name instanceof PhpParser\Node\Name
2582
            && strtolower($conditional->right->name->parts[0]) === 'count'
2583
            && $conditional->right->args;
2584
2585
        $operator_less_than_or_equal =
2586
            $conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical
2587
            || $conditional instanceof PhpParser\Node\Expr\BinaryOp\Equal
2588
            || $conditional instanceof PhpParser\Node\Expr\BinaryOp\Smaller
2589
            || $conditional instanceof PhpParser\Node\Expr\BinaryOp\SmallerOrEqual;
2590
2591
        if ($right_count
2592
            && $conditional->left instanceof PhpParser\Node\Scalar\LNumber
2593
            && $operator_less_than_or_equal
2594
            && $conditional->left->value >= (
2595
                $conditional instanceof PhpParser\Node\Expr\BinaryOp\Smaller ? 0 : 1
2596
            )
2597
        ) {
2598
            $min_count = $conditional->left->value +
2599
                ($conditional instanceof PhpParser\Node\Expr\BinaryOp\Smaller ? 1 : 0);
2600
2601
            return self::ASSIGNMENT_TO_LEFT;
2602
        }
2603
2604
        return false;
2605
    }
2606
2607
    /**
2608
     * @param   PhpParser\Node\Expr\BinaryOp    $conditional
2609
     *
2610
     * @return  false|int
2611
     */
2612
    protected static function hasReconcilableNonEmptyCountEqualityCheck(PhpParser\Node\Expr\BinaryOp $conditional)
2613
    {
2614
        $left_count = $conditional->left instanceof PhpParser\Node\Expr\FuncCall
2615
            && $conditional->left->name instanceof PhpParser\Node\Name
2616
            && strtolower($conditional->left->name->parts[0]) === 'count';
2617
2618
        $right_number = $conditional->right instanceof PhpParser\Node\Scalar\LNumber
2619
            && $conditional->right->value === (
2620
                $conditional instanceof PhpParser\Node\Expr\BinaryOp\Greater ? 0 : 1);
2621
2622
        $operator_greater_than_or_equal =
2623
            $conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical
2624
            || $conditional instanceof PhpParser\Node\Expr\BinaryOp\Equal
2625
            || $conditional instanceof PhpParser\Node\Expr\BinaryOp\Greater
2626
            || $conditional instanceof PhpParser\Node\Expr\BinaryOp\GreaterOrEqual;
2627
2628
        if ($left_count && $right_number && $operator_greater_than_or_equal) {
2629
            return self::ASSIGNMENT_TO_RIGHT;
2630
        }
2631
2632
        $right_count = $conditional->right instanceof PhpParser\Node\Expr\FuncCall
2633
            && $conditional->right->name instanceof PhpParser\Node\Name
2634
            && strtolower($conditional->right->name->parts[0]) === 'count';
2635
2636
        $left_number = $conditional->left instanceof PhpParser\Node\Scalar\LNumber
2637
            && $conditional->left->value === (
2638
                $conditional instanceof PhpParser\Node\Expr\BinaryOp\Smaller ? 0 : 1);
2639
2640
        $operator_less_than_or_equal =
2641
            $conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical
2642
            || $conditional instanceof PhpParser\Node\Expr\BinaryOp\Equal
2643
            || $conditional instanceof PhpParser\Node\Expr\BinaryOp\Smaller
2644
            || $conditional instanceof PhpParser\Node\Expr\BinaryOp\SmallerOrEqual;
2645
2646
        if ($right_count && $left_number && $operator_less_than_or_equal) {
2647
            return self::ASSIGNMENT_TO_LEFT;
2648
        }
2649
2650
        return false;
2651
    }
2652
2653
    /**
2654
     * @param   PhpParser\Node\Expr\BinaryOp    $conditional
2655
     *
2656
     * @return  false|int
2657
     */
2658
    protected static function hasTypedValueComparison(
2659
        PhpParser\Node\Expr\BinaryOp $conditional,
2660
        FileSource $source
2661
    ) {
2662
        if (!$source instanceof StatementsAnalyzer) {
2663
            return false;
2664
        }
2665
2666
        if (($right_type = $source->node_data->getType($conditional->right))
2667
            && ((!$conditional->right instanceof PhpParser\Node\Expr\Variable
2668
                    && !$conditional->right instanceof PhpParser\Node\Expr\PropertyFetch
2669
                    && !$conditional->right instanceof PhpParser\Node\Expr\StaticPropertyFetch)
2670
                || $conditional->left instanceof PhpParser\Node\Expr\Variable
2671
                || $conditional->left instanceof PhpParser\Node\Expr\PropertyFetch
2672
                || $conditional->left instanceof PhpParser\Node\Expr\StaticPropertyFetch)
2673
            && count($right_type->getAtomicTypes()) === 1
2674
            && !$right_type->hasMixed()
2675
        ) {
2676
            return self::ASSIGNMENT_TO_RIGHT;
2677
        }
2678
2679
        if (($left_type = $source->node_data->getType($conditional->left))
2680
            && !$conditional->left instanceof PhpParser\Node\Expr\Variable
2681
            && !$conditional->left instanceof PhpParser\Node\Expr\PropertyFetch
2682
            && !$conditional->left instanceof PhpParser\Node\Expr\StaticPropertyFetch
2683
            && count($left_type->getAtomicTypes()) === 1
2684
            && !$left_type->hasMixed()
2685
        ) {
2686
            return self::ASSIGNMENT_TO_LEFT;
2687
        }
2688
2689
        return false;
2690
    }
2691
2692
    /**
2693
     * @param   PhpParser\Node\Expr\FuncCall    $stmt
2694
     *
2695
     * @return  bool
2696
     */
2697
    protected static function hasNullCheck(PhpParser\Node\Expr\FuncCall $stmt)
2698
    {
2699
        if ($stmt->name instanceof PhpParser\Node\Name && strtolower($stmt->name->parts[0]) === 'is_null') {
2700
            return true;
2701
        }
2702
2703
        return false;
2704
    }
2705
2706
    /**
2707
     * @param   PhpParser\Node\Expr\FuncCall    $stmt
2708
     *
2709
     * @return  bool
2710
     */
2711
    protected static function hasIsACheck(
2712
        PhpParser\Node\Expr\FuncCall $stmt,
2713
        StatementsAnalyzer $source
2714
    ) {
2715
        if ($stmt->name instanceof PhpParser\Node\Name
2716
            && (strtolower($stmt->name->parts[0]) === 'is_a'
2717
                || strtolower($stmt->name->parts[0]) === 'is_subclass_of')
2718
            && isset($stmt->args[1])
2719
        ) {
2720
            $second_arg = $stmt->args[1]->value;
2721
2722
            if ($second_arg instanceof PhpParser\Node\Scalar\String_
2723
                || (
2724
                    $second_arg instanceof PhpParser\Node\Expr\ClassConstFetch
2725
                    && $second_arg->class instanceof PhpParser\Node\Name
2726
                    && $second_arg->name instanceof PhpParser\Node\Identifier
2727
                    && strtolower($second_arg->name->name) === 'class'
2728
                )
2729
                || (($second_arg_type = $source->node_data->getType($second_arg))
2730
                    && $second_arg_type->hasString())
2731
            ) {
2732
                return true;
2733
            }
2734
        }
2735
2736
        return false;
2737
    }
2738
2739
    /**
2740
     * @param   PhpParser\Node\Expr\FuncCall    $stmt
2741
     *
2742
     * @return  bool
2743
     */
2744
    protected static function hasArrayCheck(PhpParser\Node\Expr\FuncCall $stmt)
2745
    {
2746
        if ($stmt->name instanceof PhpParser\Node\Name && strtolower($stmt->name->parts[0]) === 'is_array') {
2747
            return true;
2748
        }
2749
2750
        return false;
2751
    }
2752
2753
    /**
2754
     * @param   PhpParser\Node\Expr\FuncCall    $stmt
2755
     *
2756
     * @return  bool
2757
     */
2758
    protected static function hasStringCheck(PhpParser\Node\Expr\FuncCall $stmt)
2759
    {
2760
        if ($stmt->name instanceof PhpParser\Node\Name && strtolower($stmt->name->parts[0]) === 'is_string') {
2761
            return true;
2762
        }
2763
2764
        return false;
2765
    }
2766
2767
    /**
2768
     * @param   PhpParser\Node\Expr\FuncCall    $stmt
2769
     *
2770
     * @return  bool
2771
     */
2772
    protected static function hasBoolCheck(PhpParser\Node\Expr\FuncCall $stmt)
2773
    {
2774
        if ($stmt->name instanceof PhpParser\Node\Name && strtolower($stmt->name->parts[0]) === 'is_bool') {
2775
            return true;
2776
        }
2777
2778
        return false;
2779
    }
2780
2781
    /**
2782
     * @param   PhpParser\Node\Expr\FuncCall    $stmt
2783
     *
2784
     * @return  bool
2785
     */
2786
    protected static function hasObjectCheck(PhpParser\Node\Expr\FuncCall $stmt)
2787
    {
2788
        if ($stmt->name instanceof PhpParser\Node\Name && $stmt->name->parts === ['is_object']) {
2789
            return true;
2790
        }
2791
2792
        return false;
2793
    }
2794
2795
    /**
2796
     * @param   PhpParser\Node\Expr\FuncCall    $stmt
2797
     *
2798
     * @return  bool
2799
     */
2800
    protected static function hasNumericCheck(PhpParser\Node\Expr\FuncCall $stmt)
2801
    {
2802
        if ($stmt->name instanceof PhpParser\Node\Name && $stmt->name->parts === ['is_numeric']) {
2803
            return true;
2804
        }
2805
2806
        return false;
2807
    }
2808
2809
    /**
2810
     * @param   PhpParser\Node\Expr\FuncCall    $stmt
2811
     *
2812
     * @return  bool
2813
     */
2814
    protected static function hasIterableCheck(PhpParser\Node\Expr\FuncCall $stmt)
2815
    {
2816
        if ($stmt->name instanceof PhpParser\Node\Name && strtolower($stmt->name->parts[0]) === 'is_iterable') {
2817
            return true;
2818
        }
2819
2820
        return false;
2821
    }
2822
2823
    /**
2824
     * @param   PhpParser\Node\Expr\FuncCall    $stmt
2825
     *
2826
     * @return  bool
2827
     */
2828
    protected static function hasCountableCheck(PhpParser\Node\Expr\FuncCall $stmt)
2829
    {
2830
        if ($stmt->name instanceof PhpParser\Node\Name && strtolower($stmt->name->parts[0]) === 'is_countable') {
2831
            return true;
2832
        }
2833
2834
        return false;
2835
    }
2836
2837
    /**
2838
     * @param   PhpParser\Node\Expr\FuncCall    $stmt
2839
     *
2840
     * @return  0|1|2
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...
2841
     */
2842
    protected static function hasClassExistsCheck(PhpParser\Node\Expr\FuncCall $stmt)
2843
    {
2844
        if ($stmt->name instanceof PhpParser\Node\Name
2845
            && strtolower($stmt->name->parts[0]) === 'class_exists'
2846
        ) {
2847
            if (!isset($stmt->args[1])) {
2848
                return 2;
2849
            }
2850
2851
            $second_arg = $stmt->args[1]->value;
2852
2853
            if ($second_arg instanceof PhpParser\Node\Expr\ConstFetch
2854
                && strtolower($second_arg->name->parts[0]) === 'true'
2855
            ) {
2856
                return 2;
2857
            }
2858
2859
            return 1;
2860
        }
2861
2862
        return 0;
2863
    }
2864
2865
    /**
2866
     * @param   PhpParser\Node\Expr\FuncCall    $stmt
2867
     *
2868
     * @return  0|1|2
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...
2869
     */
2870
    protected static function hasTraitExistsCheck(PhpParser\Node\Expr\FuncCall $stmt)
2871
    {
2872
        if ($stmt->name instanceof PhpParser\Node\Name
2873
            && strtolower($stmt->name->parts[0]) === 'trait_exists'
2874
        ) {
2875
            if (!isset($stmt->args[1])) {
2876
                return 2;
2877
            }
2878
2879
            $second_arg = $stmt->args[1]->value;
2880
2881
            if ($second_arg instanceof PhpParser\Node\Expr\ConstFetch
2882
                && strtolower($second_arg->name->parts[0]) === 'true'
2883
            ) {
2884
                return 2;
2885
            }
2886
2887
            return 1;
2888
        }
2889
2890
        return 0;
2891
    }
2892
2893
    /**
2894
     * @param   PhpParser\Node\Expr\FuncCall    $stmt
2895
     *
2896
     * @return  bool
2897
     */
2898
    protected static function hasInterfaceExistsCheck(PhpParser\Node\Expr\FuncCall $stmt)
2899
    {
2900
        if ($stmt->name instanceof PhpParser\Node\Name
2901
            && strtolower($stmt->name->parts[0]) === 'interface_exists'
2902
        ) {
2903
            return true;
2904
        }
2905
2906
        return false;
2907
    }
2908
2909
    /**
2910
     * @param   PhpParser\Node\Expr\FuncCall    $stmt
2911
     *
2912
     * @return  bool
2913
     */
2914
    protected static function hasFunctionExistsCheck(PhpParser\Node\Expr\FuncCall $stmt)
2915
    {
2916
        if ($stmt->name instanceof PhpParser\Node\Name && strtolower($stmt->name->parts[0]) === 'function_exists') {
2917
            return true;
2918
        }
2919
2920
        return false;
2921
    }
2922
2923
    /**
2924
     * @param   PhpParser\Node\Expr\FuncCall    $stmt
2925
     *
2926
     * @return  bool
2927
     */
2928
    protected static function hasIntCheck(PhpParser\Node\Expr\FuncCall $stmt)
2929
    {
2930
        if ($stmt->name instanceof PhpParser\Node\Name &&
2931
            ($stmt->name->parts === ['is_int'] ||
2932
                $stmt->name->parts === ['is_integer'] ||
2933
                $stmt->name->parts === ['is_long'])
2934
        ) {
2935
            return true;
2936
        }
2937
2938
        return false;
2939
    }
2940
2941
    /**
2942
     * @param   PhpParser\Node\Expr\FuncCall    $stmt
2943
     *
2944
     * @return  bool
2945
     */
2946
    protected static function hasFloatCheck(PhpParser\Node\Expr\FuncCall $stmt)
2947
    {
2948
        if ($stmt->name instanceof PhpParser\Node\Name &&
2949
            ($stmt->name->parts === ['is_float'] ||
2950
                $stmt->name->parts === ['is_real'] ||
2951
                $stmt->name->parts === ['is_double'])
2952
        ) {
2953
            return true;
2954
        }
2955
2956
        return false;
2957
    }
2958
2959
    /**
2960
     * @param   PhpParser\Node\Expr\FuncCall    $stmt
2961
     *
2962
     * @return  bool
2963
     */
2964
    protected static function hasResourceCheck(PhpParser\Node\Expr\FuncCall $stmt)
2965
    {
2966
        if ($stmt->name instanceof PhpParser\Node\Name && $stmt->name->parts === ['is_resource']) {
2967
            return true;
2968
        }
2969
2970
        return false;
2971
    }
2972
2973
    /**
2974
     * @param   PhpParser\Node\Expr\FuncCall    $stmt
2975
     *
2976
     * @return  bool
2977
     */
2978
    protected static function hasScalarCheck(PhpParser\Node\Expr\FuncCall $stmt)
2979
    {
2980
        if ($stmt->name instanceof PhpParser\Node\Name && $stmt->name->parts === ['is_scalar']) {
2981
            return true;
2982
        }
2983
2984
        return false;
2985
    }
2986
2987
    /**
2988
     * @param   PhpParser\Node\Expr\FuncCall    $stmt
2989
     *
2990
     * @return  bool
2991
     */
2992
    protected static function hasCallableCheck(PhpParser\Node\Expr\FuncCall $stmt)
2993
    {
2994
        if ($stmt->name instanceof PhpParser\Node\Name && $stmt->name->parts === ['is_callable']) {
2995
            return true;
2996
        }
2997
2998
        return false;
2999
    }
3000
3001
    /**
3002
     * @param   PhpParser\Node\Expr\FuncCall    $stmt
3003
     *
3004
     * @return  bool
3005
     */
3006
    protected static function hasInArrayCheck(PhpParser\Node\Expr\FuncCall $stmt)
3007
    {
3008
        if ($stmt->name instanceof PhpParser\Node\Name
3009
            && $stmt->name->parts === ['in_array']
3010
            && isset($stmt->args[2])
3011
        ) {
3012
            $second_arg = $stmt->args[2]->value;
3013
3014
            if ($second_arg instanceof PhpParser\Node\Expr\ConstFetch
3015
                && strtolower($second_arg->name->parts[0]) === 'true'
3016
            ) {
3017
                return true;
3018
            }
3019
        }
3020
3021
        return false;
3022
    }
3023
3024
    /**
3025
     * @param   PhpParser\Node\Expr\FuncCall    $stmt
3026
     *
3027
     * @return  bool
3028
     */
3029
    protected static function hasNonEmptyCountCheck(PhpParser\Node\Expr\FuncCall $stmt)
3030
    {
3031
        if ($stmt->name instanceof PhpParser\Node\Name
3032
            && $stmt->name->parts === ['count']
3033
        ) {
3034
            return true;
3035
        }
3036
3037
        return false;
3038
    }
3039
3040
    /**
3041
     * @param   PhpParser\Node\Expr\FuncCall    $stmt
3042
     *
3043
     * @return  bool
3044
     */
3045
    protected static function hasArrayKeyExistsCheck(PhpParser\Node\Expr\FuncCall $stmt)
3046
    {
3047
        if ($stmt->name instanceof PhpParser\Node\Name && $stmt->name->parts === ['array_key_exists']) {
3048
            return true;
3049
        }
3050
3051
        return false;
3052
    }
3053
}
3054