IfAnalyzer::analyzeElseIfBlock()   F
last analyzed

Complexity

Conditions 69
Paths > 20000

Size

Total Lines 475

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 69
nc 429496.7295
nop 7
dl 0
loc 475
rs 0
c 0
b 0
f 0

How to fix   Long Method    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\Block;
3
4
use PhpParser;
5
use Psalm\Codebase;
6
use Psalm\Internal\Analyzer\AlgebraAnalyzer;
7
use Psalm\Internal\Analyzer\ScopeAnalyzer;
8
use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer;
9
use Psalm\Internal\Analyzer\StatementsAnalyzer;
10
use Psalm\Internal\Analyzer\TypeAnalyzer;
11
use Psalm\Internal\Clause;
12
use Psalm\CodeLocation;
13
use Psalm\Context;
14
use Psalm\Issue\ConflictingReferenceConstraint;
15
use Psalm\Issue\DocblockTypeContradiction;
16
use Psalm\Issue\RedundantConditionGivenDocblockType;
17
use Psalm\Issue\TypeDoesNotContainType;
18
use Psalm\Issue\RedundantCondition;
19
use Psalm\IssueBuffer;
20
use Psalm\Internal\Scope\IfScope;
21
use Psalm\Type;
22
use Psalm\Type\Algebra;
23
use Psalm\Type\Reconciler;
24
use function array_merge;
25
use function array_map;
26
use function array_diff_key;
27
use function array_filter;
28
use function array_values;
29
use function array_keys;
30
use function array_reduce;
31
use function array_combine;
32
use function preg_match;
33
use function preg_quote;
34
use function array_unique;
35
use function count;
36
use function in_array;
37
use function array_intersect;
38
use function strpos;
39
use function substr;
40
use function array_intersect_key;
41
42
/**
43
 * @internal
44
 */
45
class IfAnalyzer
46
{
47
    /**
48
     * System of type substitution and deletion
49
     *
50
     * for example
51
     *
52
     * x: A|null
53
     *
54
     * if (x)
55
     *   (x: A)
56
     *   x = B  -- effects: remove A from the type of x, add B
57
     * else
58
     *   (x: null)
59
     *   x = C  -- effects: remove null from the type of x, add C
60
     *
61
     *
62
     * x: A|null
63
     *
64
     * if (!x)
65
     *   (x: null)
66
     *   throw new Exception -- effects: remove null from the type of x
67
     *
68
     * @param  StatementsAnalyzer       $statements_analyzer
69
     * @param  PhpParser\Node\Stmt\If_ $stmt
70
     * @param  Context                 $context
71
     *
72
     * @return null|false
73
     */
74
    public static function analyze(
75
        StatementsAnalyzer $statements_analyzer,
76
        PhpParser\Node\Stmt\If_ $stmt,
77
        Context $context
78
    ) {
79
        $codebase = $statements_analyzer->getCodebase();
80
81
        $if_scope = new IfScope();
82
83
        try {
84
            $if_conditional_scope = self::analyzeIfConditional(
85
                $statements_analyzer,
86
                $stmt->cond,
87
                $context,
88
                $codebase,
89
                $if_scope,
90
                $context->branch_point ?: (int) $stmt->getAttribute('startFilePos')
91
            );
92
93
            $if_context = $if_conditional_scope->if_context;
94
95
            $original_context = $if_conditional_scope->original_context;
96
            $cond_referenced_var_ids = $if_conditional_scope->cond_referenced_var_ids;
97
            $cond_assigned_var_ids = $if_conditional_scope->cond_assigned_var_ids;
98
        } catch (\Psalm\Exception\ScopeAnalysisException $e) {
99
            return false;
100
        }
101
102
        $mixed_var_ids = [];
103
104
        foreach ($if_context->vars_in_scope as $var_id => $type) {
105
            if ($type->hasMixed() && isset($context->vars_in_scope[$var_id])) {
106
                $mixed_var_ids[] = $var_id;
107
            }
108
        }
109
110
        $if_clauses = Algebra::getFormula(
111
            \spl_object_id($stmt->cond),
112
            $stmt->cond,
113
            $context->self,
114
            $statements_analyzer,
115
            $codebase
116
        );
117
118
        if (count($if_clauses) > 200) {
119
            $if_clauses = [];
120
        }
121
122
        $if_clauses = array_values(
123
            array_map(
124
                /**
125
                 * @return Clause
126
                 */
127
                function (Clause $c) use ($mixed_var_ids) {
128
                    $keys = array_keys($c->possibilities);
129
130
                    $mixed_var_ids = \array_diff($mixed_var_ids, $keys);
0 ignored issues
show
Bug introduced by
Consider using a different name than the imported variable $mixed_var_ids, or did you forget to import by reference?

It seems like you are assigning to a variable which was imported through a use statement which was not imported by reference.

For clarity, we suggest to use a different name or import by reference depending on whether you would like to have the change visibile in outer-scope.

Change not visible in outer-scope

$x = 1;
$callable = function() use ($x) {
    $x = 2; // Not visible in outer scope. If you would like this, how
            // about using a different variable name than $x?
};

$callable();
var_dump($x); // integer(1)

Change visible in outer-scope

$x = 1;
$callable = function() use (&$x) {
    $x = 2;
};

$callable();
var_dump($x); // integer(2)
Loading history...
131
132
                    foreach ($keys as $key) {
133
                        foreach ($mixed_var_ids as $mixed_var_id) {
134
                            if (preg_match('/^' . preg_quote($mixed_var_id, '/') . '(\[|-)/', $key)) {
135
                                return new Clause([], true);
136
                            }
137
                        }
138
                    }
139
140
                    return $c;
141
                },
142
                $if_clauses
143
            )
144
        );
145
146
        $entry_clauses = $context->clauses;
147
148
        // this will see whether any of the clauses in set A conflict with the clauses in set B
149
        AlgebraAnalyzer::checkForParadox(
150
            $context->clauses,
151
            $if_clauses,
152
            $statements_analyzer,
153
            $stmt->cond,
154
            $cond_assigned_var_ids
155
        );
156
157
        // if we have assignments in the if, we may have duplicate clauses
158
        if ($cond_assigned_var_ids) {
159
            $if_clauses = Algebra::simplifyCNF($if_clauses);
160
        }
161
162
        $if_context_clauses = array_merge($entry_clauses, $if_clauses);
163
164
        $if_context->clauses = Algebra::simplifyCNF($if_context_clauses);
165
166
        if ($if_context->reconciled_expression_clauses) {
167
            $reconciled_expression_clauses = $if_context->reconciled_expression_clauses;
168
169
            $if_context->clauses = array_values(
170
                array_filter(
171
                    $if_context->clauses,
172
                    function ($c) use ($reconciled_expression_clauses) {
173
                        return !in_array($c->getHash(), $reconciled_expression_clauses);
174
                    }
175
                )
176
            );
177
178
            if (count($if_context->clauses) === 1
179
                && $if_context->clauses[0]->wedge
180
                && !$if_context->clauses[0]->possibilities
181
            ) {
182
                $if_context->clauses = [];
183
                $if_context->reconciled_expression_clauses = [];
184
            }
185
        }
186
187
        // define this before we alter local claues after reconciliation
188
        $if_scope->reasonable_clauses = $if_context->clauses;
189
190
        try {
191
            $if_scope->negated_clauses = Algebra::negateFormula($if_clauses);
192
        } catch (\Psalm\Exception\ComplicatedExpressionException $e) {
193
            $if_scope->negated_clauses = [];
194
        }
195
196
        $if_scope->negated_types = Algebra::getTruthsFromFormula(
197
            Algebra::simplifyCNF(
198
                array_merge($context->clauses, $if_scope->negated_clauses)
199
            )
200
        );
201
202
        $active_if_types = [];
203
204
        $reconcilable_if_types = Algebra::getTruthsFromFormula(
205
            $if_context->clauses,
206
            \spl_object_id($stmt->cond),
207
            $cond_referenced_var_ids,
208
            $active_if_types
209
        );
210
211
        if (array_filter(
212
            $context->clauses,
213
            function ($clause) {
214
                return !!$clause->possibilities;
215
            }
216
        )) {
217
            $omit_keys = array_reduce(
218
                $context->clauses,
219
                /**
220
                 * @param array<string> $carry
221
                 * @return array<string>
222
                 */
223
                function ($carry, Clause $clause) {
224
                    return array_merge($carry, array_keys($clause->possibilities));
225
                },
226
                []
227
            );
228
229
            $omit_keys = array_combine($omit_keys, $omit_keys);
230
            $omit_keys = array_diff_key($omit_keys, Algebra::getTruthsFromFormula($context->clauses));
231
232
            $cond_referenced_var_ids = array_diff_key(
233
                $cond_referenced_var_ids,
234
                $omit_keys
235
            );
236
        }
237
238
        // if the if has an || in the conditional, we cannot easily reason about it
239
        if ($reconcilable_if_types) {
240
            $changed_var_ids = [];
241
242
            $if_vars_in_scope_reconciled =
243
                Reconciler::reconcileKeyedTypes(
244
                    $reconcilable_if_types,
245
                    $active_if_types,
246
                    $if_context->vars_in_scope,
247
                    $changed_var_ids,
248
                    $cond_referenced_var_ids,
249
                    $statements_analyzer,
250
                    $statements_analyzer->getTemplateTypeMap() ?: [],
251
                    $if_context->inside_loop,
252
                    $context->check_variables
253
                        ? new CodeLocation(
254
                            $statements_analyzer->getSource(),
255
                            $stmt->cond instanceof PhpParser\Node\Expr\BooleanNot
256
                                ? $stmt->cond->expr
257
                                : $stmt->cond,
258
                            $context->include_location
259
                        ) : null
260
                );
261
262
            $if_context->vars_in_scope = $if_vars_in_scope_reconciled;
263
264
            foreach ($reconcilable_if_types as $var_id => $_) {
265
                $if_context->vars_possibly_in_scope[$var_id] = true;
266
            }
267
268
            if ($changed_var_ids) {
269
                $if_context->clauses = Context::removeReconciledClauses($if_context->clauses, $changed_var_ids)[0];
270
            }
271
272
            $if_scope->if_cond_changed_var_ids = $changed_var_ids;
273
        }
274
275
        $old_if_context = clone $if_context;
276
        $context->vars_possibly_in_scope = array_merge(
277
            $if_context->vars_possibly_in_scope,
278
            $context->vars_possibly_in_scope
279
        );
280
281
        $context->referenced_var_ids = array_merge(
282
            $if_context->referenced_var_ids,
283
            $context->referenced_var_ids
284
        );
285
286
        $temp_else_context = clone $original_context;
287
288
        $changed_var_ids = [];
289
290
        if ($if_scope->negated_types) {
291
            $else_vars_reconciled = Reconciler::reconcileKeyedTypes(
292
                $if_scope->negated_types,
293
                [],
294
                $temp_else_context->vars_in_scope,
295
                $changed_var_ids,
296
                [],
297
                $statements_analyzer,
298
                $statements_analyzer->getTemplateTypeMap() ?: [],
299
                $context->inside_loop,
300
                $context->check_variables
301
                    ? new CodeLocation(
302
                        $statements_analyzer->getSource(),
303
                        $stmt->cond instanceof PhpParser\Node\Expr\BooleanNot
304
                            ? $stmt->cond->expr
305
                            : $stmt->cond,
306
                        $context->include_location
307
                    ) : null
308
            );
309
310
            $temp_else_context->vars_in_scope = $else_vars_reconciled;
311
        }
312
313
        // we calculate the vars redefined in a hypothetical else statement to determine
314
        // which vars of the if we can safely change
315
        $pre_assignment_else_redefined_vars = array_intersect_key(
316
            $temp_else_context->getRedefinedVars($context->vars_in_scope, true),
317
            $changed_var_ids
318
        );
319
320
        // this captures statements in the if conditional
321
        if ($codebase->find_unused_variables) {
322
            foreach ($if_context->unreferenced_vars as $var_id => $locations) {
323
                if (!isset($context->unreferenced_vars[$var_id])) {
324
                    if (isset($if_scope->new_unreferenced_vars[$var_id])) {
325
                        $if_scope->new_unreferenced_vars[$var_id] += $locations;
326
                    } else {
327
                        $if_scope->new_unreferenced_vars[$var_id] = $locations;
328
                    }
329
                } else {
330
                    $new_locations = array_diff_key(
331
                        $locations,
332
                        $context->unreferenced_vars[$var_id]
333
                    );
334
335
                    if ($new_locations) {
336
                        if (isset($if_scope->new_unreferenced_vars[$var_id])) {
337
                            $if_scope->new_unreferenced_vars[$var_id] += $locations;
338
                        } else {
339
                            $if_scope->new_unreferenced_vars[$var_id] = $locations;
340
                        }
341
                    }
342
                }
343
            }
344
        }
345
346
        // check the if
347
        if (self::analyzeIfBlock(
348
            $statements_analyzer,
349
            $stmt,
350
            $if_scope,
351
            $if_context,
352
            $old_if_context,
353
            $context,
354
            $pre_assignment_else_redefined_vars
355
        ) === false) {
356
            return false;
357
        }
358
359
        // check the else
360
        $else_context = clone $original_context;
361
362
        // check the elseifs
363
        foreach ($stmt->elseifs as $elseif) {
364
            if (self::analyzeElseIfBlock(
365
                $statements_analyzer,
366
                $elseif,
367
                $if_scope,
368
                $else_context,
369
                $context,
370
                $codebase,
371
                $else_context->branch_point ?: (int) $stmt->getAttribute('startFilePos')
372
            ) === false) {
373
                return false;
374
            }
375
        }
376
377
        if ($stmt->else) {
378
            if ($codebase->alter_code) {
379
                $else_context->branch_point =
380
                    $else_context->branch_point ?: (int) $stmt->getAttribute('startFilePos');
381
            }
382
        }
383
384
        if (self::analyzeElseBlock(
385
            $statements_analyzer,
386
            $stmt->else,
387
            $if_scope,
388
            $else_context,
389
            $context
390
        ) === false) {
391
            return false;
392
        }
393
394
        if ($context->loop_scope) {
395
            $context->loop_scope->final_actions = array_unique(
396
                array_merge(
397
                    $context->loop_scope->final_actions,
398
                    $if_scope->final_actions
399
                )
400
            );
401
        }
402
403
        $context->vars_possibly_in_scope = array_merge(
404
            $context->vars_possibly_in_scope,
405
            $if_scope->new_vars_possibly_in_scope
406
        );
407
408
        $context->possibly_assigned_var_ids = array_merge(
409
            $context->possibly_assigned_var_ids,
410
            $if_scope->possibly_assigned_var_ids ?: []
411
        );
412
413
        // vars can only be defined/redefined if there was an else (defined in every block)
414
        $context->assigned_var_ids = array_merge(
415
            $context->assigned_var_ids,
416
            $if_scope->assigned_var_ids ?: []
417
        );
418
419
        if ($if_scope->new_vars) {
420
            $context->vars_in_scope = array_merge($context->vars_in_scope, $if_scope->new_vars);
421
        }
422
423
        if ($if_scope->redefined_vars) {
424
            foreach ($if_scope->redefined_vars as $var_id => $type) {
425
                $context->vars_in_scope[$var_id] = $type;
426
                $if_scope->updated_vars[$var_id] = true;
427
428
                if ($if_scope->reasonable_clauses) {
429
                    $if_scope->reasonable_clauses = Context::filterClauses(
430
                        $var_id,
431
                        $if_scope->reasonable_clauses,
432
                        isset($context->vars_in_scope[$var_id])
433
                            ? $context->vars_in_scope[$var_id]
434
                            : null,
435
                        $statements_analyzer
436
                    );
437
                }
438
            }
439
        }
440
441
        if ($if_scope->possible_param_types) {
442
            foreach ($if_scope->possible_param_types as $var => $type) {
443
                $context->possible_param_types[$var] = $type;
444
            }
445
        }
446
447
        if ($if_scope->reasonable_clauses
448
            && (count($if_scope->reasonable_clauses) > 1 || !$if_scope->reasonable_clauses[0]->wedge)
449
        ) {
450
            $context->clauses = Algebra::simplifyCNF(
451
                array_merge(
452
                    $if_scope->reasonable_clauses,
453
                    $context->clauses
454
                )
455
            );
456
        }
457
458
        if ($if_scope->possibly_redefined_vars) {
459
            foreach ($if_scope->possibly_redefined_vars as $var_id => $type) {
460
                if (isset($context->vars_in_scope[$var_id])
461
                    && !$type->failed_reconciliation
462
                    && !isset($if_scope->updated_vars[$var_id])
463
                ) {
464
                    $combined_type = Type::combineUnionTypes(
465
                        $context->vars_in_scope[$var_id],
466
                        $type,
467
                        $codebase
468
                    );
469
470
                    if ($combined_type->equals($context->vars_in_scope[$var_id])) {
471
                        continue;
472
                    }
473
474
                    $context->removeDescendents($var_id, $combined_type);
475
                    $context->vars_in_scope[$var_id] = $combined_type;
476
                }
477
            }
478
        }
479
480
        if ($codebase->find_unused_variables) {
481
            foreach ($if_scope->new_unreferenced_vars as $var_id => $locations) {
482
                if (($stmt->else
483
                        && (isset($if_scope->assigned_var_ids[$var_id]) || isset($if_scope->new_vars[$var_id])))
484
                    || !isset($context->vars_in_scope[$var_id])
485
                ) {
486
                    $context->unreferenced_vars[$var_id] = $locations;
487
                } elseif (isset($if_scope->possibly_assigned_var_ids[$var_id])
488
                    || isset($if_context->possibly_assigned_var_ids[$var_id])
489
                ) {
490
                    if (!isset($context->unreferenced_vars[$var_id])) {
491
                        $context->unreferenced_vars[$var_id] = $locations;
492
                    } else {
493
                        $context->unreferenced_vars[$var_id] += $locations;
494
                    }
495
                } else {
496
                    $statements_analyzer->registerVariableUses($locations);
497
                }
498
            }
499
500
            $context->possibly_assigned_var_ids += $if_scope->possibly_assigned_var_ids;
501
        }
502
503
        if (!in_array(ScopeAnalyzer::ACTION_NONE, $if_scope->final_actions, true)) {
504
            $context->has_returned = true;
505
        }
506
507
        return null;
508
    }
509
510
    /**
511
     * @return \Psalm\Internal\Scope\IfConditionalScope
512
     */
513
    public static function analyzeIfConditional(
514
        StatementsAnalyzer $statements_analyzer,
515
        PhpParser\Node\Expr $cond,
516
        Context $outer_context,
517
        Codebase $codebase,
518
        IfScope $if_scope,
519
        ?int $branch_point
520
    ) {
521
        $entry_clauses = [];
522
523
        // used when evaluating elseifs
524
        if ($if_scope->negated_clauses) {
525
            $entry_clauses = array_merge($outer_context->clauses, $if_scope->negated_clauses);
526
527
            $changed_var_ids = [];
528
529
            if ($if_scope->negated_types) {
530
                $vars_reconciled = Reconciler::reconcileKeyedTypes(
531
                    $if_scope->negated_types,
532
                    [],
533
                    $outer_context->vars_in_scope,
534
                    $changed_var_ids,
535
                    [],
536
                    $statements_analyzer,
537
                    [],
538
                    $outer_context->inside_loop,
539
                    new CodeLocation(
540
                        $statements_analyzer->getSource(),
541
                        $cond instanceof PhpParser\Node\Expr\BooleanNot
542
                            ? $cond->expr
543
                            : $cond,
544
                        $outer_context->include_location,
545
                        false
546
                    )
547
                );
548
549
                if ($changed_var_ids) {
550
                    $outer_context = clone $outer_context;
551
                    $outer_context->vars_in_scope = $vars_reconciled;
552
553
                    $entry_clauses = array_values(
554
                        array_filter(
555
                            $entry_clauses,
556
                            /** @return bool */
557
                            function (Clause $c) use ($changed_var_ids) {
558
                                return count($c->possibilities) > 1
559
                                    || $c->wedge
560
                                    || !isset($changed_var_ids[array_keys($c->possibilities)[0]]);
561
                            }
562
                        )
563
                    );
564
                }
565
            }
566
        }
567
568
        // get the first expression in the if, which should be evaluated on its own
569
        // this allows us to update the context of $matches in
570
        // if (!preg_match('/a/', 'aa', $matches)) {
571
        //   exit
572
        // }
573
        // echo $matches[0];
574
        $externally_applied_if_cond_expr = self::getDefinitelyEvaluatedExpressionAfterIf($cond);
575
576
        $internally_applied_if_cond_expr = self::getDefinitelyEvaluatedExpressionInsideIf($cond);
577
578
        $was_inside_conditional = $outer_context->inside_conditional;
579
580
        $outer_context->inside_conditional = true;
581
582
        $pre_condition_vars_in_scope = $outer_context->vars_in_scope;
583
584
        $referenced_var_ids = $outer_context->referenced_var_ids;
585
        $outer_context->referenced_var_ids = [];
586
587
        $pre_assigned_var_ids = $outer_context->assigned_var_ids;
588
        $outer_context->assigned_var_ids = [];
589
590
        $if_context = null;
591
592
        if ($internally_applied_if_cond_expr !== $externally_applied_if_cond_expr) {
593
            $if_context = clone $outer_context;
594
        }
595
596
        if ($externally_applied_if_cond_expr) {
597
            if (ExpressionAnalyzer::analyze(
598
                $statements_analyzer,
599
                $externally_applied_if_cond_expr,
600
                $outer_context
601
            ) === false) {
602
                throw new \Psalm\Exception\ScopeAnalysisException();
603
            }
604
        }
605
606
        $first_cond_assigned_var_ids = $outer_context->assigned_var_ids;
607
        $outer_context->assigned_var_ids = array_merge(
608
            $pre_assigned_var_ids,
609
            $first_cond_assigned_var_ids
610
        );
611
612
        $first_cond_referenced_var_ids = $outer_context->referenced_var_ids;
613
        $outer_context->referenced_var_ids = array_merge(
614
            $referenced_var_ids,
615
            $first_cond_referenced_var_ids
616
        );
617
618
        if (!$was_inside_conditional) {
619
            $outer_context->inside_conditional = false;
620
        }
621
622
        if (!$if_context) {
623
            $if_context = clone $outer_context;
624
        }
625
626
        $if_conditional_context = clone $if_context;
627
        $if_conditional_context->if_context = $if_context;
628
        $if_conditional_context->if_scope = $if_scope;
629
630
        if ($codebase->alter_code) {
631
            $if_context->branch_point = $branch_point;
632
        }
633
634
        // we need to clone the current context so our ongoing updates
635
        // to $outer_context don't mess with elseif/else blocks
636
        $original_context = clone $outer_context;
637
638
        if ($internally_applied_if_cond_expr !== $cond
639
            || $externally_applied_if_cond_expr !== $cond
640
        ) {
641
            $assigned_var_ids = $outer_context->assigned_var_ids;
642
            $if_conditional_context->assigned_var_ids = [];
643
644
            $referenced_var_ids = $outer_context->referenced_var_ids;
645
            $if_conditional_context->referenced_var_ids = [];
646
647
            $if_conditional_context->inside_conditional = true;
648
649
            if (ExpressionAnalyzer::analyze($statements_analyzer, $cond, $if_conditional_context) === false) {
650
                throw new \Psalm\Exception\ScopeAnalysisException();
651
            }
652
653
            $if_conditional_context->inside_conditional = false;
654
655
            /** @var array<string, bool> */
656
            $more_cond_referenced_var_ids = $if_conditional_context->referenced_var_ids;
657
            $if_conditional_context->referenced_var_ids = array_merge(
658
                $more_cond_referenced_var_ids,
659
                $referenced_var_ids
660
            );
661
662
            $cond_referenced_var_ids = array_merge(
663
                $first_cond_referenced_var_ids,
664
                $more_cond_referenced_var_ids
665
            );
666
667
            /** @var array<string, bool> */
668
            $more_cond_assigned_var_ids = $if_conditional_context->assigned_var_ids;
669
            $if_conditional_context->assigned_var_ids = array_merge(
670
                $more_cond_assigned_var_ids,
671
                $assigned_var_ids
672
            );
673
674
            $cond_assigned_var_ids = array_merge(
675
                $first_cond_assigned_var_ids,
676
                $more_cond_assigned_var_ids
677
            );
678
        } else {
679
            $cond_referenced_var_ids = $first_cond_referenced_var_ids;
680
681
            $cond_assigned_var_ids = $first_cond_assigned_var_ids;
682
        }
683
684
        $newish_var_ids = array_map(
685
            /**
686
             * @param Type\Union $_
687
             *
688
             * @return true
689
             */
690
            function (Type\Union $_) {
0 ignored issues
show
Unused Code introduced by
The parameter $_ is not used and could be removed.

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

Loading history...
691
                return true;
692
            },
693
            array_diff_key(
694
                $if_conditional_context->vars_in_scope,
695
                $pre_condition_vars_in_scope,
696
                $cond_referenced_var_ids,
697
                $cond_assigned_var_ids
698
            )
699
        );
700
701
        $cond_type = $statements_analyzer->node_data->getType($cond);
702
703
        if ($cond_type !== null) {
704
            if ($cond_type->isFalse()) {
705
                if ($cond_type->from_docblock) {
706
                    if (IssueBuffer::accepts(
707
                        new DocblockTypeContradiction(
708
                            'if (false) is impossible',
709
                            new CodeLocation($statements_analyzer, $cond)
710
                        ),
711
                        $statements_analyzer->getSuppressedIssues()
712
                    )) {
713
                        // fall through
714
                    }
715
                } else {
716
                    if (IssueBuffer::accepts(
717
                        new TypeDoesNotContainType(
718
                            'if (false) is impossible',
719
                            new CodeLocation($statements_analyzer, $cond)
720
                        ),
721
                        $statements_analyzer->getSuppressedIssues()
722
                    )) {
723
                        // fall through
724
                    }
725
                }
726
            } elseif ($cond_type->isTrue()) {
727
                if ($cond_type->from_docblock) {
728
                    if (IssueBuffer::accepts(
729
                        new RedundantConditionGivenDocblockType(
730
                            'if (true) is redundant',
731
                            new CodeLocation($statements_analyzer, $cond)
732
                        ),
733
                        $statements_analyzer->getSuppressedIssues()
734
                    )) {
735
                        // fall through
736
                    }
737
                } else {
738
                    if (IssueBuffer::accepts(
739
                        new RedundantCondition(
740
                            'if (true) is redundant',
741
                            new CodeLocation($statements_analyzer, $cond)
742
                        ),
743
                        $statements_analyzer->getSuppressedIssues()
744
                    )) {
745
                        // fall through
746
                    }
747
                }
748
            }
749
        }
750
751
        // get all the var ids that were referened in the conditional, but not assigned in it
752
        $cond_referenced_var_ids = array_diff_key($cond_referenced_var_ids, $cond_assigned_var_ids);
753
754
        $cond_referenced_var_ids = array_merge($newish_var_ids, $cond_referenced_var_ids);
755
756
        return new \Psalm\Internal\Scope\IfConditionalScope(
757
            $if_context,
758
            $original_context,
759
            $cond_referenced_var_ids,
760
            $cond_assigned_var_ids,
761
            $entry_clauses
762
        );
763
    }
764
765
    /**
766
     * @param  StatementsAnalyzer        $statements_analyzer
767
     * @param  PhpParser\Node\Stmt\If_  $stmt
768
     * @param  IfScope                  $if_scope
769
     * @param  Context                  $if_context
770
     * @param  Context                  $old_if_context
771
     * @param  Context                  $outer_context
772
     * @param  array<string,Type\Union> $pre_assignment_else_redefined_vars
773
     *
774
     * @return false|null
775
     */
776
    protected static function analyzeIfBlock(
777
        StatementsAnalyzer $statements_analyzer,
778
        PhpParser\Node\Stmt\If_ $stmt,
779
        IfScope $if_scope,
780
        Context $if_context,
781
        Context $old_if_context,
782
        Context $outer_context,
783
        array $pre_assignment_else_redefined_vars
784
    ) {
785
        $codebase = $statements_analyzer->getCodebase();
786
787
        $if_context->parent_context = $outer_context;
788
789
        $assigned_var_ids = $if_context->assigned_var_ids;
790
        $possibly_assigned_var_ids = $if_context->possibly_assigned_var_ids;
791
        $if_context->assigned_var_ids = [];
792
        $if_context->possibly_assigned_var_ids = [];
793
794
        if ($statements_analyzer->analyze(
795
            $stmt->stmts,
796
            $if_context
797
        ) === false
798
        ) {
799
            return false;
800
        }
801
802
        $final_actions = ScopeAnalyzer::getFinalControlActions(
803
            $stmt->stmts,
804
            $statements_analyzer->node_data,
805
            $codebase->config->exit_functions,
806
            $outer_context->break_types
807
        );
808
809
        $has_ending_statements = $final_actions === [ScopeAnalyzer::ACTION_END];
810
811
        $has_leaving_statements = $has_ending_statements
812
            || (count($final_actions) && !in_array(ScopeAnalyzer::ACTION_NONE, $final_actions, true));
813
814
        $has_break_statement = $final_actions === [ScopeAnalyzer::ACTION_BREAK];
815
        $has_continue_statement = $final_actions === [ScopeAnalyzer::ACTION_CONTINUE];
816
        $has_leave_switch_statement = $final_actions === [ScopeAnalyzer::ACTION_LEAVE_SWITCH];
817
818
        $if_scope->final_actions = $final_actions;
819
820
        /** @var array<string, bool> */
821
        $new_assigned_var_ids = $if_context->assigned_var_ids;
822
        /** @var array<string, bool> */
823
        $new_possibly_assigned_var_ids = $if_context->possibly_assigned_var_ids;
824
825
        $if_context->assigned_var_ids = array_merge($assigned_var_ids, $new_assigned_var_ids);
826
        $if_context->possibly_assigned_var_ids = array_merge(
827
            $possibly_assigned_var_ids,
828
            $new_possibly_assigned_var_ids
829
        );
830
831
        if ($if_context->byref_constraints !== null) {
832
            foreach ($if_context->byref_constraints as $var_id => $byref_constraint) {
833
                if ($outer_context->byref_constraints !== null
834
                    && isset($outer_context->byref_constraints[$var_id])
835
                    && $byref_constraint->type
836
                    && ($outer_constraint_type = $outer_context->byref_constraints[$var_id]->type)
837
                    && !TypeAnalyzer::isContainedBy(
838
                        $codebase,
839
                        $byref_constraint->type,
840
                        $outer_constraint_type
841
                    )
842
                ) {
843
                    if (IssueBuffer::accepts(
844
                        new ConflictingReferenceConstraint(
845
                            'There is more than one pass-by-reference constraint on ' . $var_id,
846
                            new CodeLocation($statements_analyzer, $stmt, $outer_context->include_location, true)
847
                        ),
848
                        $statements_analyzer->getSuppressedIssues()
849
                    )) {
850
                        // fall through
851
                    }
852
                } else {
853
                    $outer_context->byref_constraints[$var_id] = $byref_constraint;
854
                }
855
            }
856
        }
857
858
        if ($codebase->find_unused_variables) {
859
            $outer_context->referenced_var_ids = array_merge(
860
                $outer_context->referenced_var_ids,
861
                $if_context->referenced_var_ids
862
            );
863
        }
864
865
        $mic_drop = false;
866
867
        if (!$has_leaving_statements) {
868
            $if_scope->new_vars = array_diff_key($if_context->vars_in_scope, $outer_context->vars_in_scope);
869
870
            $if_scope->redefined_vars = $if_context->getRedefinedVars($outer_context->vars_in_scope);
871
            $if_scope->possibly_redefined_vars = $if_scope->redefined_vars;
872
            $if_scope->assigned_var_ids = $new_assigned_var_ids;
873
            $if_scope->possibly_assigned_var_ids = $new_possibly_assigned_var_ids;
874
875
            $changed_var_ids = $new_assigned_var_ids;
876
877
            // if the variable was only set in the conditional, it's not possibly redefined
878
            foreach ($if_scope->possibly_redefined_vars as $var_id => $_) {
879
                if (!isset($new_possibly_assigned_var_ids[$var_id])
880
                    && isset($if_scope->if_cond_changed_var_ids[$var_id])
881
                ) {
882
                    unset($if_scope->possibly_redefined_vars[$var_id]);
883
                }
884
            }
885
886
            if ($if_scope->reasonable_clauses) {
887
                // remove all reasonable clauses that would be negated by the if stmts
888
                foreach ($changed_var_ids as $var_id => $_) {
889
                    $if_scope->reasonable_clauses = Context::filterClauses(
890
                        $var_id,
891
                        $if_scope->reasonable_clauses,
892
                        isset($if_context->vars_in_scope[$var_id]) ? $if_context->vars_in_scope[$var_id] : null,
893
                        $statements_analyzer
894
                    );
895
                }
896
            }
897
        } else {
898
            if (!$has_break_statement) {
899
                $if_scope->reasonable_clauses = [];
900
            }
901
        }
902
903
        if ($has_leaving_statements && !$has_break_statement && !$stmt->else && !$stmt->elseifs) {
904
            if ($if_scope->negated_types) {
905
                $changed_var_ids = [];
906
907
                $outer_context_vars_reconciled = Reconciler::reconcileKeyedTypes(
908
                    $if_scope->negated_types,
909
                    [],
910
                    $outer_context->vars_in_scope,
911
                    $changed_var_ids,
912
                    [],
913
                    $statements_analyzer,
914
                    $statements_analyzer->getTemplateTypeMap() ?: [],
915
                    $outer_context->inside_loop,
916
                    new CodeLocation(
917
                        $statements_analyzer->getSource(),
918
                        $stmt->cond instanceof PhpParser\Node\Expr\BooleanNot
919
                            ? $stmt->cond->expr
920
                            : $stmt->cond,
921
                        $outer_context->include_location,
922
                        false
923
                    )
924
                );
925
926
                foreach ($changed_var_ids as $changed_var_id => $_) {
927
                    $outer_context->removeVarFromConflictingClauses($changed_var_id);
928
                }
929
930
                $changed_var_ids += $new_assigned_var_ids;
931
932
                foreach ($changed_var_ids as $var_id => $_) {
933
                    $if_scope->negated_clauses = Context::filterClauses(
934
                        $var_id,
935
                        $if_scope->negated_clauses
936
                    );
937
                }
938
939
                $outer_context->vars_in_scope = $outer_context_vars_reconciled;
940
                $mic_drop = true;
941
            }
942
943
            $outer_context->clauses = Algebra::simplifyCNF(
944
                array_merge($outer_context->clauses, $if_scope->negated_clauses)
945
            );
946
        }
947
948
        // update the parent context as necessary, but only if we can safely reason about type negation.
949
        // We only update vars that changed both at the start of the if block and then again by an assignment
950
        // in the if statement.
951
        if ($if_scope->negated_types && !$mic_drop) {
952
            $vars_to_update = array_intersect(
953
                array_keys($pre_assignment_else_redefined_vars),
954
                array_keys($if_scope->negated_types)
955
            );
956
957
            $extra_vars_to_update = [];
958
959
            // if there's an object-like array in there, we also need to update the root array variable
960
            foreach ($vars_to_update as $var_id) {
961
                $bracked_pos = strpos($var_id, '[');
962
                if ($bracked_pos !== false) {
963
                    $extra_vars_to_update[] = substr($var_id, 0, $bracked_pos);
964
                }
965
            }
966
967
            if ($extra_vars_to_update) {
968
                $vars_to_update = array_unique(array_merge($extra_vars_to_update, $vars_to_update));
969
            }
970
971
            //update $if_context vars to include the pre-assignment else vars
972
            if (!$stmt->else && !$has_leaving_statements) {
973
                foreach ($pre_assignment_else_redefined_vars as $var_id => $type) {
974
                    if (isset($if_context->vars_in_scope[$var_id])) {
975
                        $if_context->vars_in_scope[$var_id] = Type::combineUnionTypes(
976
                            $if_context->vars_in_scope[$var_id],
977
                            $type,
978
                            $codebase
979
                        );
980
                    }
981
                }
982
            }
983
984
            $outer_context->update(
985
                $old_if_context,
986
                $if_context,
987
                $has_leaving_statements,
988
                $vars_to_update,
989
                $if_scope->updated_vars
990
            );
991
        }
992
993
        if (!$has_ending_statements) {
994
            $vars_possibly_in_scope = array_diff_key(
995
                $if_context->vars_possibly_in_scope,
996
                $outer_context->vars_possibly_in_scope
997
            );
998
999
            if ($if_context->loop_scope) {
1000
                if (!$has_continue_statement && !$has_break_statement) {
1001
                    $if_scope->new_vars_possibly_in_scope = $vars_possibly_in_scope;
1002
                }
1003
1004
                $if_context->loop_scope->vars_possibly_in_scope = array_merge(
1005
                    $vars_possibly_in_scope,
1006
                    $if_context->loop_scope->vars_possibly_in_scope
1007
                );
1008
            } elseif (!$has_leaving_statements) {
1009
                $if_scope->new_vars_possibly_in_scope = $vars_possibly_in_scope;
1010
            }
1011
1012
            if ($codebase->find_unused_variables && (!$has_leaving_statements || $has_leave_switch_statement)) {
1013
                foreach ($if_context->unreferenced_vars as $var_id => $locations) {
1014
                    if (!isset($outer_context->unreferenced_vars[$var_id])) {
1015
                        if (isset($if_scope->new_unreferenced_vars[$var_id])) {
1016
                            $if_scope->new_unreferenced_vars[$var_id] += $locations;
1017
                        } else {
1018
                            $if_scope->new_unreferenced_vars[$var_id] = $locations;
1019
                        }
1020
                    } else {
1021
                        $new_locations = array_diff_key(
1022
                            $locations,
1023
                            $outer_context->unreferenced_vars[$var_id]
1024
                        );
1025
1026
                        if ($new_locations) {
1027
                            if (isset($if_scope->new_unreferenced_vars[$var_id])) {
1028
                                $if_scope->new_unreferenced_vars[$var_id] += $locations;
1029
                            } else {
1030
                                $if_scope->new_unreferenced_vars[$var_id] = $locations;
1031
                            }
1032
                        }
1033
                    }
1034
                }
1035
            }
1036
        }
1037
1038
        if ($outer_context->collect_exceptions) {
1039
            $outer_context->mergeExceptions($if_context);
1040
        }
1041
    }
1042
1043
    /**
1044
     * @param  StatementsAnalyzer           $statements_analyzer
1045
     * @param  PhpParser\Node\Stmt\ElseIf_ $elseif
1046
     * @param  IfScope                     $if_scope
1047
     * @param  Context                     $elseif_context
0 ignored issues
show
Documentation introduced by
There is no parameter named $elseif_context. Did you maybe mean $elseif?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

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

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

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

Loading history...
1048
     * @param  Context                     $outer_context
1049
     *
1050
     * @return false|null
1051
     */
1052
    protected static function analyzeElseIfBlock(
1053
        StatementsAnalyzer $statements_analyzer,
1054
        PhpParser\Node\Stmt\ElseIf_ $elseif,
1055
        IfScope $if_scope,
1056
        Context $else_context,
1057
        Context $outer_context,
1058
        Codebase $codebase,
1059
        ?int $branch_point
1060
    ) {
1061
        $pre_conditional_context = clone $else_context;
1062
1063
        try {
1064
            $if_conditional_scope = self::analyzeIfConditional(
1065
                $statements_analyzer,
1066
                $elseif->cond,
1067
                $else_context,
1068
                $codebase,
1069
                $if_scope,
1070
                $branch_point
1071
            );
1072
1073
            $elseif_context = $if_conditional_scope->if_context;
1074
            $cond_referenced_var_ids = $if_conditional_scope->cond_referenced_var_ids;
1075
            $cond_assigned_var_ids = $if_conditional_scope->cond_assigned_var_ids;
1076
            $entry_clauses = $if_conditional_scope->entry_clauses;
1077
        } catch (\Psalm\Exception\ScopeAnalysisException $e) {
1078
            return false;
1079
        }
1080
1081
        $mixed_var_ids = [];
1082
1083
        foreach ($elseif_context->vars_in_scope as $var_id => $type) {
1084
            if ($type->hasMixed()) {
1085
                $mixed_var_ids[] = $var_id;
1086
            }
1087
        }
1088
1089
        $elseif_clauses = Algebra::getFormula(
1090
            \spl_object_id($elseif->cond),
1091
            $elseif->cond,
1092
            $else_context->self,
1093
            $statements_analyzer,
1094
            $codebase
1095
        );
1096
1097
        $elseif_clauses = array_map(
1098
            /**
1099
             * @return Clause
1100
             */
1101
            function (Clause $c) use ($mixed_var_ids) {
1102
                $keys = array_keys($c->possibilities);
1103
1104
                $mixed_var_ids = \array_diff($mixed_var_ids, $keys);
0 ignored issues
show
Bug introduced by
Consider using a different name than the imported variable $mixed_var_ids, or did you forget to import by reference?

It seems like you are assigning to a variable which was imported through a use statement which was not imported by reference.

For clarity, we suggest to use a different name or import by reference depending on whether you would like to have the change visibile in outer-scope.

Change not visible in outer-scope

$x = 1;
$callable = function() use ($x) {
    $x = 2; // Not visible in outer scope. If you would like this, how
            // about using a different variable name than $x?
};

$callable();
var_dump($x); // integer(1)

Change visible in outer-scope

$x = 1;
$callable = function() use (&$x) {
    $x = 2;
};

$callable();
var_dump($x); // integer(2)
Loading history...
1105
1106
                foreach ($keys as $key) {
1107
                    foreach ($mixed_var_ids as $mixed_var_id) {
1108
                        if (preg_match('/^' . preg_quote($mixed_var_id, '/') . '(\[|-)/', $key)) {
1109
                            return new Clause([], true);
1110
                        }
1111
                    }
1112
                }
1113
1114
                return $c;
1115
            },
1116
            $elseif_clauses
1117
        );
1118
1119
        $entry_clauses = array_map(
1120
            /**
1121
             * @return Clause
1122
             */
1123
            function (Clause $c) use ($cond_assigned_var_ids) {
1124
                $keys = array_keys($c->possibilities);
1125
1126
                foreach ($keys as $key) {
1127
                    foreach ($cond_assigned_var_ids as $conditional_assigned_var_id => $_) {
1128
                        if (preg_match('/^' . preg_quote($conditional_assigned_var_id, '/') . '(\[|-|$)/', $key)) {
1129
                            return new Clause([], true);
1130
                        }
1131
                    }
1132
                }
1133
1134
                return $c;
1135
            },
1136
            $entry_clauses
1137
        );
1138
1139
        // this will see whether any of the clauses in set A conflict with the clauses in set B
1140
        AlgebraAnalyzer::checkForParadox(
1141
            $entry_clauses,
1142
            $elseif_clauses,
1143
            $statements_analyzer,
1144
            $elseif->cond,
1145
            $cond_assigned_var_ids
1146
        );
1147
1148
        $elseif_context_clauses = array_merge($entry_clauses, $elseif_clauses);
1149
1150
        if ($elseif_context->reconciled_expression_clauses) {
1151
            $reconciled_expression_clauses = $elseif_context->reconciled_expression_clauses;
1152
1153
            $elseif_context_clauses = array_values(
1154
                array_filter(
1155
                    $elseif_context_clauses,
1156
                    function ($c) use ($reconciled_expression_clauses) {
1157
                        return !in_array($c->getHash(), $reconciled_expression_clauses);
1158
                    }
1159
                )
1160
            );
1161
        }
1162
1163
        $elseif_context->clauses = Algebra::simplifyCNF($elseif_context_clauses);
1164
1165
        $active_elseif_types = [];
1166
1167
        try {
1168
            if (array_filter(
1169
                $entry_clauses,
1170
                function ($clause) {
1171
                    return !!$clause->possibilities;
1172
                }
1173
            )) {
1174
                $omit_keys = array_reduce(
1175
                    $entry_clauses,
1176
                    /**
1177
                     * @param array<string> $carry
1178
                     * @return array<string>
1179
                     */
1180
                    function ($carry, Clause $clause) {
1181
                        return array_merge($carry, array_keys($clause->possibilities));
1182
                    },
1183
                    []
1184
                );
1185
1186
                $omit_keys = array_combine($omit_keys, $omit_keys);
1187
                $omit_keys = array_diff_key($omit_keys, Algebra::getTruthsFromFormula($entry_clauses));
1188
1189
                $cond_referenced_var_ids = array_diff_key(
1190
                    $cond_referenced_var_ids,
1191
                    $omit_keys
1192
                );
1193
            }
1194
            $reconcilable_elseif_types = Algebra::getTruthsFromFormula(
1195
                $elseif_context->clauses,
1196
                \spl_object_id($elseif->cond),
1197
                $cond_referenced_var_ids,
1198
                $active_elseif_types
1199
            );
1200
            $negated_elseif_types = Algebra::getTruthsFromFormula(
1201
                Algebra::negateFormula($elseif_clauses)
1202
            );
1203
        } catch (\Psalm\Exception\ComplicatedExpressionException $e) {
1204
            $reconcilable_elseif_types = [];
1205
            $negated_elseif_types = [];
1206
        }
1207
1208
        $all_negated_vars = array_unique(
1209
            array_merge(
1210
                array_keys($negated_elseif_types),
1211
                array_keys($if_scope->negated_types)
1212
            )
1213
        );
1214
1215
        foreach ($all_negated_vars as $var_id) {
1216
            if (isset($negated_elseif_types[$var_id])) {
1217
                if (isset($if_scope->negated_types[$var_id])) {
1218
                    $if_scope->negated_types[$var_id] = array_merge(
1219
                        $if_scope->negated_types[$var_id],
1220
                        $negated_elseif_types[$var_id]
1221
                    );
1222
                } else {
1223
                    $if_scope->negated_types[$var_id] = $negated_elseif_types[$var_id];
1224
                }
1225
            }
1226
        }
1227
1228
        $changed_var_ids = [];
1229
1230
        // if the elseif has an || in the conditional, we cannot easily reason about it
1231
        if ($reconcilable_elseif_types) {
1232
            $elseif_vars_reconciled = Reconciler::reconcileKeyedTypes(
1233
                $reconcilable_elseif_types,
1234
                $active_elseif_types,
1235
                $elseif_context->vars_in_scope,
1236
                $changed_var_ids,
1237
                $cond_referenced_var_ids,
1238
                $statements_analyzer,
1239
                $statements_analyzer->getTemplateTypeMap() ?: [],
1240
                $elseif_context->inside_loop,
1241
                new CodeLocation(
1242
                    $statements_analyzer->getSource(),
1243
                    $elseif->cond instanceof PhpParser\Node\Expr\BooleanNot
1244
                        ? $elseif->cond->expr
1245
                        : $elseif->cond,
1246
                    $outer_context->include_location
1247
                )
1248
            );
1249
1250
            $elseif_context->vars_in_scope = $elseif_vars_reconciled;
1251
1252
            if ($changed_var_ids) {
1253
                $elseif_context->clauses = Context::removeReconciledClauses(
1254
                    $elseif_context->clauses,
1255
                    $changed_var_ids
1256
                )[0];
1257
            }
1258
        }
1259
1260
        $pre_stmts_assigned_var_ids = $elseif_context->assigned_var_ids;
1261
        $elseif_context->assigned_var_ids = [];
1262
        $pre_stmts_possibly_assigned_var_ids = $elseif_context->possibly_assigned_var_ids;
1263
        $elseif_context->possibly_assigned_var_ids = [];
1264
1265
        if ($statements_analyzer->analyze(
1266
            $elseif->stmts,
1267
            $elseif_context
1268
        ) === false
1269
        ) {
1270
            return false;
1271
        }
1272
1273
        /** @var array<string, bool> */
1274
        $new_stmts_assigned_var_ids = $elseif_context->assigned_var_ids;
1275
        $elseif_context->assigned_var_ids = $pre_stmts_assigned_var_ids + $new_stmts_assigned_var_ids;
1276
1277
        /** @var array<string, bool> */
1278
        $new_stmts_possibly_assigned_var_ids = $elseif_context->possibly_assigned_var_ids;
1279
        $elseif_context->possibly_assigned_var_ids =
1280
            $pre_stmts_possibly_assigned_var_ids + $new_stmts_possibly_assigned_var_ids;
1281
1282
        if ($elseif_context->byref_constraints !== null) {
1283
            foreach ($elseif_context->byref_constraints as $var_id => $byref_constraint) {
1284
                if ($outer_context->byref_constraints !== null
1285
                    && isset($outer_context->byref_constraints[$var_id])
1286
                    && ($outer_constraint_type = $outer_context->byref_constraints[$var_id]->type)
1287
                    && $byref_constraint->type
1288
                    && !TypeAnalyzer::isContainedBy(
1289
                        $codebase,
1290
                        $byref_constraint->type,
1291
                        $outer_constraint_type
1292
                    )
1293
                ) {
1294
                    if (IssueBuffer::accepts(
1295
                        new ConflictingReferenceConstraint(
1296
                            'There is more than one pass-by-reference constraint on ' . $var_id,
1297
                            new CodeLocation($statements_analyzer, $elseif, $outer_context->include_location, true)
1298
                        ),
1299
                        $statements_analyzer->getSuppressedIssues()
1300
                    )) {
1301
                        // fall through
1302
                    }
1303
                } else {
1304
                    $outer_context->byref_constraints[$var_id] = $byref_constraint;
1305
                }
1306
            }
1307
        }
1308
1309
        $final_actions = ScopeAnalyzer::getFinalControlActions(
1310
            $elseif->stmts,
1311
            $statements_analyzer->node_data,
1312
            $codebase->config->exit_functions,
1313
            $outer_context->break_types
1314
        );
1315
        // has a return/throw at end
1316
        $has_ending_statements = $final_actions === [ScopeAnalyzer::ACTION_END];
1317
        $has_leaving_statements = $has_ending_statements
1318
            || (count($final_actions) && !in_array(ScopeAnalyzer::ACTION_NONE, $final_actions, true));
1319
1320
        $has_break_statement = $final_actions === [ScopeAnalyzer::ACTION_BREAK];
1321
        $has_continue_statement = $final_actions === [ScopeAnalyzer::ACTION_CONTINUE];
1322
        $has_leave_switch_statement = $final_actions === [ScopeAnalyzer::ACTION_LEAVE_SWITCH];
1323
1324
        $if_scope->final_actions = array_merge($final_actions, $if_scope->final_actions);
1325
1326
        // update the parent context as necessary
1327
        $elseif_redefined_vars = $elseif_context->getRedefinedVars($outer_context->vars_in_scope);
1328
1329
        if (!$has_leaving_statements) {
1330
            if ($if_scope->new_vars === null) {
1331
                $if_scope->new_vars = array_diff_key($elseif_context->vars_in_scope, $outer_context->vars_in_scope);
1332
            } else {
1333
                foreach ($if_scope->new_vars as $new_var => $type) {
1334
                    if (!$elseif_context->hasVariable($new_var, $statements_analyzer)) {
1335
                        unset($if_scope->new_vars[$new_var]);
1336
                    } else {
1337
                        $if_scope->new_vars[$new_var] = Type::combineUnionTypes(
1338
                            $type,
1339
                            $elseif_context->vars_in_scope[$new_var],
1340
                            $codebase
1341
                        );
1342
                    }
1343
                }
1344
            }
1345
1346
            $possibly_redefined_vars = $elseif_redefined_vars;
1347
1348
            foreach ($possibly_redefined_vars as $var_id => $_) {
1349
                if (!isset($new_stmts_assigned_var_ids[$var_id])
1350
                    && isset($changed_var_ids[$var_id])
1351
                ) {
1352
                    unset($possibly_redefined_vars[$var_id]);
1353
                }
1354
            }
1355
1356
            $assigned_var_ids = array_merge($new_stmts_assigned_var_ids, $cond_assigned_var_ids);
1357
1358
            if ($if_scope->assigned_var_ids === null) {
1359
                $if_scope->assigned_var_ids = $assigned_var_ids;
1360
            } else {
1361
                $if_scope->assigned_var_ids = array_intersect_key($assigned_var_ids, $if_scope->assigned_var_ids);
1362
            }
1363
1364
            if ($if_scope->redefined_vars === null) {
1365
                $if_scope->redefined_vars = $elseif_redefined_vars;
1366
                $if_scope->possibly_redefined_vars = $possibly_redefined_vars;
1367
            } else {
1368
                foreach ($if_scope->redefined_vars as $redefined_var => $type) {
1369
                    if (!isset($elseif_redefined_vars[$redefined_var])) {
1370
                        unset($if_scope->redefined_vars[$redefined_var]);
1371
                    } else {
1372
                        $if_scope->redefined_vars[$redefined_var] = Type::combineUnionTypes(
1373
                            $elseif_redefined_vars[$redefined_var],
1374
                            $type,
1375
                            $codebase
1376
                        );
1377
1378
                        if (isset($outer_context->vars_in_scope[$redefined_var])
1379
                            && $if_scope->redefined_vars[$redefined_var]->equals(
1380
                                $outer_context->vars_in_scope[$redefined_var]
1381
                            )
1382
                        ) {
1383
                            unset($if_scope->redefined_vars[$redefined_var]);
1384
                        }
1385
                    }
1386
                }
1387
1388
                foreach ($possibly_redefined_vars as $var => $type) {
1389
                    if (isset($if_scope->possibly_redefined_vars[$var])) {
1390
                        $if_scope->possibly_redefined_vars[$var] = Type::combineUnionTypes(
1391
                            $type,
1392
                            $if_scope->possibly_redefined_vars[$var],
1393
                            $codebase
1394
                        );
1395
                    } else {
1396
                        $if_scope->possibly_redefined_vars[$var] = $type;
1397
                    }
1398
                }
1399
            }
1400
1401
            $reasonable_clause_count = count($if_scope->reasonable_clauses);
1402
1403
            if ($reasonable_clause_count && $reasonable_clause_count < 20000 && $elseif_clauses) {
1404
                $if_scope->reasonable_clauses = Algebra::combineOredClauses(
1405
                    $if_scope->reasonable_clauses,
1406
                    $elseif_clauses
1407
                );
1408
            } else {
1409
                $if_scope->reasonable_clauses = [];
1410
            }
1411
        } else {
1412
            $if_scope->reasonable_clauses = [];
1413
        }
1414
1415
        if ($negated_elseif_types) {
1416
            if ($has_leaving_statements) {
1417
                $changed_var_ids = [];
1418
1419
                $leaving_vars_reconciled = Reconciler::reconcileKeyedTypes(
1420
                    $negated_elseif_types,
1421
                    [],
1422
                    $pre_conditional_context->vars_in_scope,
1423
                    $changed_var_ids,
1424
                    [],
1425
                    $statements_analyzer,
1426
                    $statements_analyzer->getTemplateTypeMap() ?: [],
1427
                    $elseif_context->inside_loop,
1428
                    new CodeLocation($statements_analyzer->getSource(), $elseif, $outer_context->include_location)
1429
                );
1430
1431
                $implied_outer_context = clone $elseif_context;
1432
                $implied_outer_context->vars_in_scope = $leaving_vars_reconciled;
1433
1434
                $outer_context->update(
1435
                    $elseif_context,
1436
                    $implied_outer_context,
1437
                    false,
1438
                    array_keys($negated_elseif_types),
1439
                    $if_scope->updated_vars
1440
                );
1441
            }
1442
        }
1443
1444
        if (!$has_ending_statements) {
1445
            $vars_possibly_in_scope = array_diff_key(
1446
                $elseif_context->vars_possibly_in_scope,
1447
                $outer_context->vars_possibly_in_scope
1448
            );
1449
1450
            $possibly_assigned_var_ids = $new_stmts_possibly_assigned_var_ids;
1451
1452
            if ($has_leaving_statements && $elseif_context->loop_scope) {
1453
                if (!$has_continue_statement && !$has_break_statement) {
1454
                    $if_scope->new_vars_possibly_in_scope = array_merge(
1455
                        $vars_possibly_in_scope,
1456
                        $if_scope->new_vars_possibly_in_scope
1457
                    );
1458
                    $if_scope->possibly_assigned_var_ids = array_merge(
1459
                        $possibly_assigned_var_ids,
1460
                        $if_scope->possibly_assigned_var_ids
1461
                    );
1462
                }
1463
1464
                $elseif_context->loop_scope->vars_possibly_in_scope = array_merge(
1465
                    $vars_possibly_in_scope,
1466
                    $elseif_context->loop_scope->vars_possibly_in_scope
1467
                );
1468
            } elseif (!$has_leaving_statements) {
1469
                $if_scope->new_vars_possibly_in_scope = array_merge(
1470
                    $vars_possibly_in_scope,
1471
                    $if_scope->new_vars_possibly_in_scope
1472
                );
1473
                $if_scope->possibly_assigned_var_ids = array_merge(
1474
                    $possibly_assigned_var_ids,
1475
                    $if_scope->possibly_assigned_var_ids
1476
                );
1477
            }
1478
1479
            if ($codebase->find_unused_variables &&  (!$has_leaving_statements || $has_leave_switch_statement)) {
1480
                foreach ($elseif_context->unreferenced_vars as $var_id => $locations) {
1481
                    if (!isset($outer_context->unreferenced_vars[$var_id])) {
1482
                        if (isset($if_scope->new_unreferenced_vars[$var_id])) {
1483
                            $if_scope->new_unreferenced_vars[$var_id] += $locations;
1484
                        } else {
1485
                            $if_scope->new_unreferenced_vars[$var_id] = $locations;
1486
                        }
1487
                    } else {
1488
                        $new_locations = array_diff_key(
1489
                            $locations,
1490
                            $outer_context->unreferenced_vars[$var_id]
1491
                        );
1492
1493
                        if ($new_locations) {
1494
                            if (isset($if_scope->new_unreferenced_vars[$var_id])) {
1495
                                $if_scope->new_unreferenced_vars[$var_id] += $locations;
1496
                            } else {
1497
                                $if_scope->new_unreferenced_vars[$var_id] = $locations;
1498
                            }
1499
                        }
1500
                    }
1501
                }
1502
            }
1503
        }
1504
1505
        if ($codebase->find_unused_variables) {
1506
            $outer_context->referenced_var_ids = array_merge(
1507
                $outer_context->referenced_var_ids,
1508
                $elseif_context->referenced_var_ids
1509
            );
1510
        }
1511
1512
        if ($outer_context->collect_exceptions) {
1513
            $outer_context->mergeExceptions($elseif_context);
1514
        }
1515
1516
        try {
1517
            $if_scope->negated_clauses = Algebra::simplifyCNF(
1518
                array_merge(
1519
                    $if_scope->negated_clauses,
1520
                    Algebra::negateFormula($elseif_clauses)
1521
                )
1522
            );
1523
        } catch (\Psalm\Exception\ComplicatedExpressionException $e) {
1524
            $if_scope->negated_clauses = [];
1525
        }
1526
    }
1527
1528
    /**
1529
     * @param  StatementsAnalyzer         $statements_analyzer
1530
     * @param  PhpParser\Node\Stmt\Else_|null $else
1531
     * @param  IfScope                   $if_scope
1532
     * @param  Context                   $else_context
1533
     * @param  Context                   $outer_context
1534
     *
1535
     * @return false|null
1536
     */
1537
    protected static function analyzeElseBlock(
1538
        StatementsAnalyzer $statements_analyzer,
1539
        $else,
1540
        IfScope $if_scope,
1541
        Context $else_context,
1542
        Context $outer_context
1543
    ) {
1544
        $codebase = $statements_analyzer->getCodebase();
1545
1546
        if (!$else && !$if_scope->negated_clauses && !$else_context->clauses) {
1547
            $if_scope->final_actions = array_merge([ScopeAnalyzer::ACTION_NONE], $if_scope->final_actions);
1548
            $if_scope->assigned_var_ids = [];
1549
            $if_scope->new_vars = [];
1550
            $if_scope->redefined_vars = [];
1551
            $if_scope->reasonable_clauses = [];
1552
1553
            return;
1554
        }
1555
1556
        $else_context->clauses = Algebra::simplifyCNF(
1557
            array_merge(
1558
                $else_context->clauses,
1559
                $if_scope->negated_clauses
1560
            )
1561
        );
1562
1563
        $else_types = Algebra::getTruthsFromFormula($else_context->clauses);
1564
1565
        if (!$else && !$else_types) {
1566
            $if_scope->final_actions = array_merge([ScopeAnalyzer::ACTION_NONE], $if_scope->final_actions);
1567
            $if_scope->assigned_var_ids = [];
1568
            $if_scope->new_vars = [];
1569
            $if_scope->redefined_vars = [];
1570
            $if_scope->reasonable_clauses = [];
1571
1572
            return;
1573
        }
1574
1575
        $original_context = clone $else_context;
1576
1577
        if ($else_types) {
1578
            $changed_var_ids = [];
1579
1580
            $else_vars_reconciled = Reconciler::reconcileKeyedTypes(
1581
                $else_types,
1582
                [],
1583
                $else_context->vars_in_scope,
1584
                $changed_var_ids,
1585
                [],
1586
                $statements_analyzer,
1587
                [],
1588
                $else_context->inside_loop,
1589
                $else
1590
                    ? new CodeLocation($statements_analyzer->getSource(), $else, $outer_context->include_location)
1591
                    : null
1592
            );
1593
1594
            $else_context->vars_in_scope = $else_vars_reconciled;
1595
1596
            $else_context->clauses = Context::removeReconciledClauses($else_context->clauses, $changed_var_ids)[0];
1597
        }
1598
1599
        $old_else_context = clone $else_context;
1600
1601
        $pre_stmts_assigned_var_ids = $else_context->assigned_var_ids;
1602
        $else_context->assigned_var_ids = [];
1603
1604
        $pre_possibly_assigned_var_ids = $else_context->possibly_assigned_var_ids;
1605
        $else_context->possibly_assigned_var_ids = [];
1606
1607
        if ($else) {
1608
            if ($statements_analyzer->analyze(
1609
                $else->stmts,
1610
                $else_context
1611
            ) === false
1612
            ) {
1613
                return false;
1614
            }
1615
        }
1616
1617
        /** @var array<string, bool> */
1618
        $new_assigned_var_ids = $else_context->assigned_var_ids;
1619
        $else_context->assigned_var_ids = $pre_stmts_assigned_var_ids;
1620
1621
        /** @var array<string, bool> */
1622
        $new_possibly_assigned_var_ids = $else_context->possibly_assigned_var_ids;
1623
        $else_context->possibly_assigned_var_ids = $pre_possibly_assigned_var_ids + $new_possibly_assigned_var_ids;
1624
1625
        if ($else && $else_context->byref_constraints !== null) {
1626
            foreach ($else_context->byref_constraints as $var_id => $byref_constraint) {
1627
                if ($outer_context->byref_constraints !== null
1628
                    && isset($outer_context->byref_constraints[$var_id])
1629
                    && ($outer_constraint_type = $outer_context->byref_constraints[$var_id]->type)
1630
                    && $byref_constraint->type
1631
                    && !TypeAnalyzer::isContainedBy(
1632
                        $codebase,
1633
                        $byref_constraint->type,
1634
                        $outer_constraint_type
1635
                    )
1636
                ) {
1637
                    if (IssueBuffer::accepts(
1638
                        new ConflictingReferenceConstraint(
1639
                            'There is more than one pass-by-reference constraint on ' . $var_id,
1640
                            new CodeLocation($statements_analyzer, $else, $outer_context->include_location, true)
1641
                        ),
1642
                        $statements_analyzer->getSuppressedIssues()
1643
                    )) {
1644
                        // fall through
1645
                    }
1646
                } else {
1647
                    $outer_context->byref_constraints[$var_id] = $byref_constraint;
1648
                }
1649
            }
1650
        }
1651
1652
        if ($else && $codebase->find_unused_variables) {
1653
            $outer_context->referenced_var_ids = array_merge(
1654
                $outer_context->referenced_var_ids,
1655
                $else_context->referenced_var_ids
1656
            );
1657
        }
1658
1659
        $final_actions = $else
1660
            ? ScopeAnalyzer::getFinalControlActions(
1661
                $else->stmts,
1662
                $statements_analyzer->node_data,
1663
                $codebase->config->exit_functions,
1664
                $outer_context->break_types
1665
            )
1666
            : [ScopeAnalyzer::ACTION_NONE];
1667
        // has a return/throw at end
1668
        $has_ending_statements = $final_actions === [ScopeAnalyzer::ACTION_END];
1669
        $has_leaving_statements = $has_ending_statements
1670
            || (count($final_actions) && !in_array(ScopeAnalyzer::ACTION_NONE, $final_actions, true));
1671
1672
        $has_break_statement = $final_actions === [ScopeAnalyzer::ACTION_BREAK];
1673
        $has_continue_statement = $final_actions === [ScopeAnalyzer::ACTION_CONTINUE];
1674
        $has_leave_switch_statement = $final_actions === [ScopeAnalyzer::ACTION_LEAVE_SWITCH];
1675
1676
        $if_scope->final_actions = array_merge($final_actions, $if_scope->final_actions);
1677
1678
        $else_redefined_vars = $else_context->getRedefinedVars($original_context->vars_in_scope);
1679
1680
        // if it doesn't end in a return
1681
        if (!$has_leaving_statements) {
1682
            if ($if_scope->new_vars === null && $else) {
1683
                $if_scope->new_vars = array_diff_key($else_context->vars_in_scope, $outer_context->vars_in_scope);
1684
            } elseif ($if_scope->new_vars !== null) {
1685
                foreach ($if_scope->new_vars as $new_var => $type) {
1686
                    if (!$else_context->hasVariable($new_var)) {
1687
                        unset($if_scope->new_vars[$new_var]);
1688
                    } else {
1689
                        $if_scope->new_vars[$new_var] = Type::combineUnionTypes(
1690
                            $type,
1691
                            $else_context->vars_in_scope[$new_var],
1692
                            $codebase
1693
                        );
1694
                    }
1695
                }
1696
            }
1697
1698
            if ($if_scope->assigned_var_ids === null) {
1699
                $if_scope->assigned_var_ids = $new_assigned_var_ids;
1700
            } else {
1701
                $if_scope->assigned_var_ids = array_intersect_key($new_assigned_var_ids, $if_scope->assigned_var_ids);
1702
            }
1703
1704
            if ($if_scope->redefined_vars === null) {
1705
                $if_scope->redefined_vars = $else_redefined_vars;
1706
                $if_scope->possibly_redefined_vars = $if_scope->redefined_vars;
1707
            } else {
1708
                foreach ($if_scope->redefined_vars as $redefined_var => $type) {
1709
                    if (!isset($else_redefined_vars[$redefined_var])) {
1710
                        unset($if_scope->redefined_vars[$redefined_var]);
1711
                    } else {
1712
                        $if_scope->redefined_vars[$redefined_var] = Type::combineUnionTypes(
1713
                            $else_redefined_vars[$redefined_var],
1714
                            $type,
1715
                            $codebase
1716
                        );
1717
                    }
1718
                }
1719
1720
                foreach ($else_redefined_vars as $var => $type) {
1721
                    if (isset($if_scope->possibly_redefined_vars[$var])) {
1722
                        $if_scope->possibly_redefined_vars[$var] = Type::combineUnionTypes(
1723
                            $type,
1724
                            $if_scope->possibly_redefined_vars[$var],
1725
                            $codebase
1726
                        );
1727
                    } else {
1728
                        $if_scope->possibly_redefined_vars[$var] = $type;
1729
                    }
1730
                }
1731
            }
1732
1733
            $if_scope->reasonable_clauses = [];
1734
        }
1735
1736
        // update the parent context as necessary
1737
        if ($if_scope->negatable_if_types) {
1738
            $outer_context->update(
1739
                $old_else_context,
1740
                $else_context,
1741
                $has_leaving_statements,
1742
                array_keys($if_scope->negatable_if_types),
1743
                $if_scope->updated_vars
1744
            );
1745
        }
1746
1747
        if (!$has_ending_statements) {
1748
            $vars_possibly_in_scope = array_diff_key(
1749
                $else_context->vars_possibly_in_scope,
1750
                $outer_context->vars_possibly_in_scope
1751
            );
1752
1753
            $possibly_assigned_var_ids = $new_possibly_assigned_var_ids;
1754
1755
            if ($has_leaving_statements && $else_context->loop_scope) {
1756
                if (!$has_continue_statement && !$has_break_statement) {
1757
                    $if_scope->new_vars_possibly_in_scope = array_merge(
1758
                        $vars_possibly_in_scope,
1759
                        $if_scope->new_vars_possibly_in_scope
1760
                    );
1761
1762
                    $if_scope->possibly_assigned_var_ids = array_merge(
1763
                        $possibly_assigned_var_ids,
1764
                        $if_scope->possibly_assigned_var_ids
1765
                    );
1766
                }
1767
1768
                $else_context->loop_scope->vars_possibly_in_scope = array_merge(
1769
                    $vars_possibly_in_scope,
1770
                    $else_context->loop_scope->vars_possibly_in_scope
1771
                );
1772
            } elseif (!$has_leaving_statements) {
1773
                $if_scope->new_vars_possibly_in_scope = array_merge(
1774
                    $vars_possibly_in_scope,
1775
                    $if_scope->new_vars_possibly_in_scope
1776
                );
1777
1778
                $if_scope->possibly_assigned_var_ids = array_merge(
1779
                    $possibly_assigned_var_ids,
1780
                    $if_scope->possibly_assigned_var_ids
1781
                );
1782
            }
1783
1784
            if ($codebase->find_unused_variables && (!$has_leaving_statements || $has_leave_switch_statement)) {
1785
                foreach ($else_context->unreferenced_vars as $var_id => $locations) {
1786
                    if (!isset($outer_context->unreferenced_vars[$var_id])) {
1787
                        if (isset($if_scope->new_unreferenced_vars[$var_id])) {
1788
                            $if_scope->new_unreferenced_vars[$var_id] += $locations;
1789
                        } else {
1790
                            $if_scope->new_unreferenced_vars[$var_id] = $locations;
1791
                        }
1792
                    } else {
1793
                        $new_locations = array_diff_key(
1794
                            $locations,
1795
                            $outer_context->unreferenced_vars[$var_id]
1796
                        );
1797
1798
                        if ($new_locations) {
1799
                            if (isset($if_scope->new_unreferenced_vars[$var_id])) {
1800
                                $if_scope->new_unreferenced_vars[$var_id] += $locations;
1801
                            } else {
1802
                                $if_scope->new_unreferenced_vars[$var_id] = $locations;
1803
                            }
1804
                        }
1805
                    }
1806
                }
1807
            }
1808
        }
1809
1810
        if ($outer_context->collect_exceptions) {
1811
            $outer_context->mergeExceptions($else_context);
1812
        }
1813
    }
1814
1815
    /**
1816
     * Returns statements that are definitely evaluated before any statements after the end of the
1817
     * if/elseif/else blocks
1818
     *
1819
     * @param  PhpParser\Node\Expr $stmt
1820
     *
1821
     * @return PhpParser\Node\Expr|null
1822
     */
1823
    private static function getDefinitelyEvaluatedExpressionAfterIf(PhpParser\Node\Expr $stmt)
1824
    {
1825
        if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\Equal
1826
            || $stmt instanceof PhpParser\Node\Expr\BinaryOp\Identical
1827
        ) {
1828
            if ($stmt->left instanceof PhpParser\Node\Expr\ConstFetch
1829
                && $stmt->left->name->parts === ['true']
1830
            ) {
1831
                return self::getDefinitelyEvaluatedExpressionAfterIf($stmt->right);
1832
            }
1833
1834
            if ($stmt->right instanceof PhpParser\Node\Expr\ConstFetch
1835
                && $stmt->right->name->parts === ['true']
1836
            ) {
1837
                return self::getDefinitelyEvaluatedExpressionAfterIf($stmt->left);
1838
            }
1839
        }
1840
1841
        if ($stmt instanceof PhpParser\Node\Expr\BinaryOp) {
1842
            if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\BooleanAnd
1843
                || $stmt instanceof PhpParser\Node\Expr\BinaryOp\LogicalAnd
1844
                || $stmt instanceof PhpParser\Node\Expr\BinaryOp\LogicalXor
1845
            ) {
1846
                return self::getDefinitelyEvaluatedExpressionAfterIf($stmt->left);
1847
            }
1848
1849
            return $stmt;
1850
        }
1851
1852
        if ($stmt instanceof PhpParser\Node\Expr\BooleanNot) {
1853
            $inner_stmt = self::getDefinitelyEvaluatedExpressionInsideIf($stmt->expr);
1854
1855
            if ($inner_stmt !== $stmt->expr) {
1856
                return $inner_stmt;
1857
            }
1858
        }
1859
1860
        return $stmt;
1861
    }
1862
1863
    /**
1864
     * Returns statements that are definitely evaluated before any statements inside
1865
     * the if block
1866
     *
1867
     * @param  PhpParser\Node\Expr $stmt
1868
     *
1869
     * @return PhpParser\Node\Expr|null
1870
     */
1871
    private static function getDefinitelyEvaluatedExpressionInsideIf(PhpParser\Node\Expr $stmt)
1872
    {
1873
        if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\Equal
1874
            || $stmt instanceof PhpParser\Node\Expr\BinaryOp\Identical
1875
        ) {
1876
            if ($stmt->left instanceof PhpParser\Node\Expr\ConstFetch
1877
                && $stmt->left->name->parts === ['true']
1878
            ) {
1879
                return self::getDefinitelyEvaluatedExpressionInsideIf($stmt->right);
1880
            }
1881
1882
            if ($stmt->right instanceof PhpParser\Node\Expr\ConstFetch
1883
                && $stmt->right->name->parts === ['true']
1884
            ) {
1885
                return self::getDefinitelyEvaluatedExpressionInsideIf($stmt->left);
1886
            }
1887
        }
1888
1889
        if ($stmt instanceof PhpParser\Node\Expr\BinaryOp) {
1890
            if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\BooleanOr
1891
                || $stmt instanceof PhpParser\Node\Expr\BinaryOp\LogicalOr
1892
                || $stmt instanceof PhpParser\Node\Expr\BinaryOp\LogicalXor
1893
            ) {
1894
                return self::getDefinitelyEvaluatedExpressionInsideIf($stmt->left);
1895
            }
1896
1897
            return $stmt;
1898
        }
1899
1900
        if ($stmt instanceof PhpParser\Node\Expr\BooleanNot) {
1901
            $inner_stmt = self::getDefinitelyEvaluatedExpressionAfterIf($stmt->expr);
1902
1903
            if ($inner_stmt !== $stmt->expr) {
1904
                return $inner_stmt;
1905
            }
1906
        }
1907
1908
        return $stmt;
1909
    }
1910
}
1911