SwitchCaseAnalyzer::handleNonReturningCase()   F
last analyzed

Complexity

Conditions 31
Paths 2923

Size

Total Lines 168

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 31
nc 2923
nop 11
dl 0
loc 168
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity    Many Parameters   

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:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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\CodeLocation;
11
use Psalm\Context;
12
use Psalm\Issue\ContinueOutsideLoop;
13
use Psalm\Issue\ParadoxicalCondition;
14
use Psalm\IssueBuffer;
15
use Psalm\Internal\Scope\CaseScope;
16
use Psalm\Internal\Scope\SwitchScope;
17
use Psalm\Type;
18
use Psalm\Type\Algebra;
19
use Psalm\Type\Reconciler;
20
use function count;
21
use function in_array;
22
use function array_merge;
23
use function is_string;
24
use function substr;
25
use function array_intersect_key;
26
use function array_diff_key;
27
28
/**
29
 * @internal
30
 */
31
class SwitchCaseAnalyzer
32
{
33
    /**
34
     * @param ?string $switch_var_id
0 ignored issues
show
Documentation introduced by
The doc-type ?string could not be parsed: Unknown type name "?string" 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...
35
     * @return null|false
36
     */
37
    public static function analyze(
38
        StatementsAnalyzer $statements_analyzer,
39
        Codebase $codebase,
40
        PhpParser\Node\Stmt\Switch_ $stmt,
41
        $switch_var_id,
42
        PhpParser\Node\Stmt\Case_ $case,
43
        Context $context,
44
        Context $original_context,
45
        string $case_exit_type,
46
        array $case_actions,
47
        bool $is_last,
48
        SwitchScope $switch_scope
49
    ) {
50
        // has a return/throw at end
51
        $has_ending_statements = $case_actions === [ScopeAnalyzer::ACTION_END];
52
        $has_leaving_statements = $has_ending_statements
53
            || (count($case_actions) && !in_array(ScopeAnalyzer::ACTION_NONE, $case_actions, true));
54
55
        $case_context = clone $original_context;
56
57
        if ($codebase->alter_code) {
58
            $case_context->branch_point = $case_context->branch_point ?: (int) $stmt->getAttribute('startFilePos');
59
        }
60
61
        $case_context->parent_context = $context;
62
        $case_scope = $case_context->case_scope = new CaseScope($case_context);
63
64
        $case_equality_expr = null;
65
66
        $old_node_data = $statements_analyzer->node_data;
67
68
        $fake_switch_condition = false;
69
70
        if ($switch_var_id && substr($switch_var_id, 0, 15) === '$__tmp_switch__') {
71
            $switch_condition = new PhpParser\Node\Expr\Variable(
72
                substr($switch_var_id, 1),
73
                $stmt->cond->getAttributes()
74
            );
75
76
            $fake_switch_condition = true;
77
        } else {
78
            $switch_condition = $stmt->cond;
79
        }
80
81
        if ($case->cond) {
82
            $was_inside_conditional = $case_context->inside_conditional;
83
            $case_context->inside_conditional = true;
84
85
            if (ExpressionAnalyzer::analyze($statements_analyzer, $case->cond, $case_context) === false) {
86
                /** @psalm-suppress PossiblyNullPropertyAssignmentValue */
87
                $case_scope->parent_context = null;
88
                $case_context->case_scope = null;
89
                $case_context->parent_context = null;
90
91
                return false;
92
            }
93
94
            if (!$was_inside_conditional) {
95
                $case_context->inside_conditional = false;
96
            }
97
98
            $statements_analyzer->node_data = clone $statements_analyzer->node_data;
99
100
            $traverser = new PhpParser\NodeTraverser;
101
            $traverser->addVisitor(
102
                new \Psalm\Internal\PhpVisitor\ConditionCloningVisitor(
103
                    $statements_analyzer->node_data
104
                )
105
            );
106
107
            /** @var PhpParser\Node\Expr */
108
            $switch_condition = $traverser->traverse([$switch_condition])[0];
109
110
            if ($fake_switch_condition) {
111
                $statements_analyzer->node_data->setType(
112
                    $switch_condition,
0 ignored issues
show
Documentation introduced by
$switch_condition is of type object<PhpParser\Node>, but the function expects a object<PhpParser\Node\Ex...rser\Node\Stmt\Return_>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
113
                    $case_context->vars_in_scope[$switch_var_id] ?? Type::getMixed()
114
                );
115
            }
116
117
            if ($switch_condition instanceof PhpParser\Node\Expr\Variable
118
                && is_string($switch_condition->name)
119
                && isset($context->vars_in_scope['$' . $switch_condition->name])
120
            ) {
121
                $switch_var_type = $context->vars_in_scope['$' . $switch_condition->name];
122
123
                $type_statements = [];
124
125
                foreach ($switch_var_type->getAtomicTypes() as $type) {
126
                    if ($type instanceof Type\Atomic\GetClassT) {
127
                        $type_statements[] = new PhpParser\Node\Expr\FuncCall(
128
                            new PhpParser\Node\Name(['get_class']),
129
                            [
130
                                new PhpParser\Node\Arg(
131
                                    new PhpParser\Node\Expr\Variable(substr($type->typeof, 1))
132
                                ),
133
                            ]
134
                        );
135
                    } elseif ($type instanceof Type\Atomic\GetTypeT) {
136
                        $type_statements[] = new PhpParser\Node\Expr\FuncCall(
137
                            new PhpParser\Node\Name(['gettype']),
138
                            [
139
                                new PhpParser\Node\Arg(
140
                                    new PhpParser\Node\Expr\Variable(substr($type->typeof, 1))
141
                                ),
142
                            ]
143
                        );
144
                    } else {
145
                        $type_statements = null;
146
                        break;
147
                    }
148
                }
149
150
                if ($type_statements && count($type_statements) === 1) {
151
                    $switch_condition = $type_statements[0];
152
153
                    if ($fake_switch_condition) {
154
                        $statements_analyzer->node_data->setType(
155
                            $switch_condition,
156
                            $case_context->vars_in_scope[$switch_var_id] ?? Type::getMixed()
157
                        );
158
                    }
159
                }
160
            }
161
162
            if (($switch_condition_type = $statements_analyzer->node_data->getType($switch_condition))
163
                && ($case_cond_type = $statements_analyzer->node_data->getType($case->cond))
164
                && (($switch_condition_type->isString() && $case_cond_type->isString())
165
                    || ($switch_condition_type->isInt() && $case_cond_type->isInt())
166
                    || ($switch_condition_type->isFloat() && $case_cond_type->isFloat())
167
                )
168
            ) {
169
                $case_equality_expr = new PhpParser\Node\Expr\BinaryOp\Identical(
170
                    $switch_condition,
171
                    $case->cond,
172
                    $case->cond->getAttributes()
173
                );
174
            } else {
175
                $case_equality_expr = new PhpParser\Node\Expr\BinaryOp\Equal(
176
                    $switch_condition,
177
                    $case->cond,
178
                    $case->cond->getAttributes()
179
                );
180
            }
181
        }
182
183
        $continue_case_equality_expr = false;
184
185
        if ($case->stmts) {
186
            $case_stmts = array_merge($switch_scope->leftover_statements, $case->stmts);
187
        } else {
188
            $continue_case_equality_expr = count($switch_scope->leftover_statements) === 1;
189
            $case_stmts = $switch_scope->leftover_statements;
190
        }
191
192
        if (!$has_leaving_statements && !$is_last) {
193
            if (!$case_equality_expr) {
194
                $case_equality_expr = new PhpParser\Node\Expr\FuncCall(
195
                    new PhpParser\Node\Name\FullyQualified(['rand']),
196
                    [
197
                        new PhpParser\Node\Arg(new PhpParser\Node\Scalar\LNumber(0)),
198
                        new PhpParser\Node\Arg(new PhpParser\Node\Scalar\LNumber(1)),
199
                    ],
200
                    $case->getAttributes()
201
                );
202
            }
203
204
            $switch_scope->leftover_case_equality_expr = $switch_scope->leftover_case_equality_expr
205
                ? new PhpParser\Node\Expr\BinaryOp\BooleanOr(
206
                    $switch_scope->leftover_case_equality_expr,
207
                    $case_equality_expr,
208
                    $case->cond ? $case->cond->getAttributes() : $case->getAttributes()
209
                )
210
                : $case_equality_expr;
211
212
            if ($continue_case_equality_expr
213
                && $switch_scope->leftover_statements[0] instanceof PhpParser\Node\Stmt\If_
214
            ) {
215
                $case_if_stmt = $switch_scope->leftover_statements[0];
216
                $case_if_stmt->cond = $switch_scope->leftover_case_equality_expr;
217
            } else {
218
                $case_if_stmt = new PhpParser\Node\Stmt\If_(
219
                    $switch_scope->leftover_case_equality_expr,
220
                    ['stmts' => $case_stmts]
221
                );
222
223
                $switch_scope->leftover_statements = [$case_if_stmt];
224
            }
225
226
            /** @psalm-suppress PossiblyNullPropertyAssignmentValue */
227
            $case_scope->parent_context = null;
228
            $case_context->case_scope = null;
229
            $case_context->parent_context = null;
230
231
            $statements_analyzer->node_data = $old_node_data;
232
233
            return;
234
        }
235
236
        if ($switch_scope->leftover_case_equality_expr) {
237
            $case_or_default_equality_expr = $case_equality_expr;
238
239
            if (!$case_or_default_equality_expr) {
240
                $case_or_default_equality_expr = new PhpParser\Node\Expr\FuncCall(
241
                    new PhpParser\Node\Name\FullyQualified(['rand']),
242
                    [
243
                        new PhpParser\Node\Arg(new PhpParser\Node\Scalar\LNumber(0)),
244
                        new PhpParser\Node\Arg(new PhpParser\Node\Scalar\LNumber(1)),
245
                    ],
246
                    $case->getAttributes()
247
                );
248
            }
249
250
            $case_equality_expr = new PhpParser\Node\Expr\BinaryOp\BooleanOr(
251
                $switch_scope->leftover_case_equality_expr,
252
                $case_or_default_equality_expr,
253
                $case_or_default_equality_expr->getAttributes()
254
            );
255
        }
256
257
        if ($case_equality_expr
258
            && $switch_condition instanceof PhpParser\Node\Expr\Variable
259
            && is_string($switch_condition->name)
260
            && isset($context->vars_in_scope['$' . $switch_condition->name])
261
        ) {
262
            $new_case_equality_expr = self::simplifyCaseEqualityExpression(
263
                $case_equality_expr,
264
                $switch_condition
265
            );
266
267
            if ($new_case_equality_expr) {
268
                ExpressionAnalyzer::analyze(
269
                    $statements_analyzer,
270
                    $new_case_equality_expr->args[1]->value,
271
                    $case_context
272
                );
273
274
                $case_equality_expr = $new_case_equality_expr;
275
            }
276
        }
277
278
        $case_context->break_types[] = 'switch';
279
280
        $switch_scope->leftover_statements = [];
281
        $switch_scope->leftover_case_equality_expr = null;
282
283
        $case_clauses = [];
284
285
        if ($case_equality_expr) {
286
            $case_clauses = Algebra::getFormula(
287
                \spl_object_id($case_equality_expr),
288
                $case_equality_expr,
289
                $context->self,
290
                $statements_analyzer,
291
                $codebase,
292
                false,
293
                false
294
            );
295
        }
296
297
        if ($switch_scope->negated_clauses && count($switch_scope->negated_clauses) < 50) {
298
            $entry_clauses = Algebra::simplifyCNF(
299
                array_merge(
300
                    $original_context->clauses,
301
                    $switch_scope->negated_clauses
302
                )
303
            );
304
        } else {
305
            $entry_clauses = $original_context->clauses;
306
        }
307
308
        if ($case_clauses && $case->cond) {
309
            // this will see whether any of the clauses in set A conflict with the clauses in set B
310
            AlgebraAnalyzer::checkForParadox(
311
                $entry_clauses,
312
                $case_clauses,
313
                $statements_analyzer,
314
                $case->cond,
315
                []
316
            );
317
318
            if (count($entry_clauses) + count($case_clauses) < 50) {
319
                $case_context->clauses = Algebra::simplifyCNF(array_merge($entry_clauses, $case_clauses));
320
            } else {
321
                $case_context->clauses = array_merge($entry_clauses, $case_clauses);
322
            }
323
        } else {
324
            $case_context->clauses = $entry_clauses;
325
        }
326
327
        $reconcilable_if_types = Algebra::getTruthsFromFormula($case_context->clauses);
328
329
        // if the if has an || in the conditional, we cannot easily reason about it
330
        if ($reconcilable_if_types) {
331
            $changed_var_ids = [];
332
333
            $suppressed_issues = $statements_analyzer->getSuppressedIssues();
334
335
            if (!in_array('RedundantCondition', $suppressed_issues, true)) {
336
                $statements_analyzer->addSuppressedIssues(['RedundantCondition']);
337
            }
338
339
            if (!in_array('RedundantConditionGivenDocblockType', $suppressed_issues, true)) {
340
                $statements_analyzer->addSuppressedIssues(['RedundantConditionGivenDocblockType']);
341
            }
342
343
            $case_vars_in_scope_reconciled =
344
                Reconciler::reconcileKeyedTypes(
345
                    $reconcilable_if_types,
346
                    [],
347
                    $case_context->vars_in_scope,
348
                    $changed_var_ids,
349
                    $case->cond && $switch_var_id ? [$switch_var_id => true] : [],
350
                    $statements_analyzer,
351
                    [],
352
                    $case_context->inside_loop,
353
                    new CodeLocation(
354
                        $statements_analyzer->getSource(),
355
                        $case->cond ? $case->cond : $case,
356
                        $context->include_location
357
                    )
358
                );
359
360
            if (!in_array('RedundantCondition', $suppressed_issues, true)) {
361
                $statements_analyzer->removeSuppressedIssues(['RedundantCondition']);
362
            }
363
364
            if (!in_array('RedundantConditionGivenDocblockType', $suppressed_issues, true)) {
365
                $statements_analyzer->removeSuppressedIssues(['RedundantConditionGivenDocblockType']);
366
            }
367
368
            $case_context->vars_in_scope = $case_vars_in_scope_reconciled;
369
            foreach ($reconcilable_if_types as $var_id => $_) {
370
                $case_context->vars_possibly_in_scope[$var_id] = true;
371
            }
372
373
            if ($changed_var_ids) {
374
                $case_context->clauses = Context::removeReconciledClauses($case_context->clauses, $changed_var_ids)[0];
375
            }
376
        }
377
378
        if ($case_clauses) {
379
            $switch_scope->negated_clauses = array_merge(
380
                $switch_scope->negated_clauses,
381
                Algebra::negateFormula($case_clauses)
382
            );
383
        }
384
385
        $pre_possibly_assigned_var_ids = $case_context->possibly_assigned_var_ids;
386
        $case_context->possibly_assigned_var_ids = [];
387
388
        $pre_assigned_var_ids = $case_context->assigned_var_ids;
389
        $case_context->assigned_var_ids = [];
390
391
        $statements_analyzer->analyze($case_stmts, $case_context);
392
393
        $traverser = new PhpParser\NodeTraverser;
394
        $traverser->addVisitor(
395
            new \Psalm\Internal\PhpVisitor\TypeMappingVisitor(
396
                $statements_analyzer->node_data,
397
                $old_node_data
398
            )
399
        );
400
401
        $traverser->traverse([$case]);
402
403
        $statements_analyzer->node_data = $old_node_data;
404
405
        /** @var array<string, bool> */
406
        $new_case_assigned_var_ids = $case_context->assigned_var_ids;
407
        $case_context->assigned_var_ids = $pre_assigned_var_ids + $new_case_assigned_var_ids;
408
409
        /** @var array<string, bool> */
410
        $new_case_possibly_assigned_var_ids = $case_context->possibly_assigned_var_ids;
411
        $case_context->possibly_assigned_var_ids =
412
            $pre_possibly_assigned_var_ids + $new_case_possibly_assigned_var_ids;
413
414
        $context->referenced_var_ids = array_merge(
415
            $context->referenced_var_ids,
416
            $case_context->referenced_var_ids
417
        );
418
419
        if ($case_exit_type !== 'return_throw') {
420
            if (self::handleNonReturningCase(
421
                $statements_analyzer,
422
                $switch_var_id,
423
                $case,
424
                $context,
425
                $case_context,
426
                $original_context,
427
                $new_case_assigned_var_ids,
428
                $new_case_possibly_assigned_var_ids,
429
                $case_exit_type,
430
                $switch_scope,
431
                $case_scope
432
            ) === false) {
433
                /** @psalm-suppress PossiblyNullPropertyAssignmentValue */
434
                $case_scope->parent_context = null;
435
                $case_context->case_scope = null;
436
                $case_context->parent_context = null;
437
438
                return false;
439
            }
440
        }
441
442
        // augment the information with data from break statements
443
        if ($case_scope->break_vars !== null) {
444
            if ($switch_scope->possibly_redefined_vars === null) {
445
                $switch_scope->possibly_redefined_vars = array_intersect_key(
446
                    $case_scope->break_vars,
447
                    $context->vars_in_scope
448
                );
449
            } else {
450
                foreach ($case_scope->break_vars as $var_id => $type) {
451
                    if (isset($context->vars_in_scope[$var_id])) {
452
                        if (!isset($switch_scope->possibly_redefined_vars[$var_id])) {
453
                            $switch_scope->possibly_redefined_vars[$var_id] = clone $type;
454
                        } else {
455
                            $switch_scope->possibly_redefined_vars[$var_id] = Type::combineUnionTypes(
456
                                clone $type,
457
                                $switch_scope->possibly_redefined_vars[$var_id]
458
                            );
459
                        }
460
                    }
461
                }
462
            }
463
464
            if ($switch_scope->new_vars_in_scope !== null) {
465
                foreach ($switch_scope->new_vars_in_scope as $var_id => $type) {
466
                    if (isset($case_scope->break_vars[$var_id])) {
467
                        if (!isset($case_context->vars_in_scope[$var_id])) {
468
                            unset($switch_scope->new_vars_in_scope[$var_id]);
469
                        } else {
470
                            $switch_scope->new_vars_in_scope[$var_id] = Type::combineUnionTypes(
471
                                clone $case_scope->break_vars[$var_id],
472
                                $type
473
                            );
474
                        }
475
                    } else {
476
                        unset($switch_scope->new_vars_in_scope[$var_id]);
477
                    }
478
                }
479
            }
480
481
            if ($switch_scope->redefined_vars !== null) {
482
                foreach ($switch_scope->redefined_vars as $var_id => $type) {
483
                    if (isset($case_scope->break_vars[$var_id])) {
484
                        $switch_scope->redefined_vars[$var_id] = Type::combineUnionTypes(
485
                            clone $case_scope->break_vars[$var_id],
486
                            $type
487
                        );
488
                    } else {
489
                        unset($switch_scope->redefined_vars[$var_id]);
490
                    }
491
                }
492
            }
493
        }
494
495
        /** @psalm-suppress PossiblyNullPropertyAssignmentValue */
496
        $case_scope->parent_context = null;
497
        $case_context->case_scope = null;
498
        $case_context->parent_context = null;
499
    }
500
501
    /**
502
     * @param ?string $switch_var_id
0 ignored issues
show
Documentation introduced by
The doc-type ?string could not be parsed: Unknown type name "?string" 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...
503
     * @param array<string, bool> $new_case_assigned_var_ids
504
     * @param array<string, bool> $new_case_possibly_assigned_var_ids
505
     * @return null|false
506
     */
507
    private static function handleNonReturningCase(
508
        StatementsAnalyzer $statements_analyzer,
509
        $switch_var_id,
510
        PhpParser\Node\Stmt\Case_ $case,
511
        Context $context,
512
        Context $case_context,
513
        Context $original_context,
514
        array $new_case_assigned_var_ids,
515
        array $new_case_possibly_assigned_var_ids,
516
        string $case_exit_type,
517
        SwitchScope $switch_scope,
518
        CaseScope $case_scope
519
    ) {
520
        if (!$case->cond
521
            && $switch_var_id
522
            && isset($case_context->vars_in_scope[$switch_var_id])
523
            && $case_context->vars_in_scope[$switch_var_id]->isEmpty()
524
        ) {
525
            if (IssueBuffer::accepts(
526
                new ParadoxicalCondition(
527
                    'All possible case statements have been met, default is impossible here',
528
                    new CodeLocation($statements_analyzer->getSource(), $case)
529
                ),
530
                $statements_analyzer->getSuppressedIssues()
531
            )) {
532
                return false;
533
            }
534
        }
535
536
        // if we're leaving this block, add vars to outer for loop scope
537
        if ($case_exit_type === 'continue') {
538
            if (!$context->loop_scope) {
539
                if (IssueBuffer::accepts(
540
                    new ContinueOutsideLoop(
541
                        'Continue called when not in loop',
542
                        new CodeLocation($statements_analyzer->getSource(), $case)
543
                    )
544
                )) {
545
                    return false;
546
                }
547
            }
548
        } else {
549
            $case_redefined_vars = $case_context->getRedefinedVars($original_context->vars_in_scope);
550
551
            if ($switch_scope->possibly_redefined_vars === null) {
552
                $switch_scope->possibly_redefined_vars = $case_redefined_vars;
553
            } else {
554
                foreach ($case_redefined_vars as $var_id => $type) {
555
                    if (!isset($switch_scope->possibly_redefined_vars[$var_id])) {
556
                        $switch_scope->possibly_redefined_vars[$var_id] = clone $type;
557
                    } else {
558
                        $switch_scope->possibly_redefined_vars[$var_id] = Type::combineUnionTypes(
559
                            clone $type,
560
                            $switch_scope->possibly_redefined_vars[$var_id]
561
                        );
562
                    }
563
                }
564
            }
565
566
            if ($switch_scope->redefined_vars === null) {
567
                $switch_scope->redefined_vars = $case_redefined_vars;
568
            } else {
569
                foreach ($switch_scope->redefined_vars as $var_id => $type) {
570
                    if (!isset($case_redefined_vars[$var_id])) {
571
                        unset($switch_scope->redefined_vars[$var_id]);
572
                    } else {
573
                        $switch_scope->redefined_vars[$var_id] = Type::combineUnionTypes(
574
                            $type,
575
                            clone $case_redefined_vars[$var_id]
576
                        );
577
                    }
578
                }
579
            }
580
581
            $context_new_vars = array_diff_key($case_context->vars_in_scope, $context->vars_in_scope);
582
583
            if ($switch_scope->new_vars_in_scope === null) {
584
                $switch_scope->new_vars_in_scope = $context_new_vars;
585
                $switch_scope->new_vars_possibly_in_scope = array_diff_key(
586
                    $case_context->vars_possibly_in_scope,
587
                    $context->vars_possibly_in_scope
588
                );
589
            } else {
590
                foreach ($switch_scope->new_vars_in_scope as $new_var => $type) {
591
                    if (!$case_context->hasVariable($new_var, $statements_analyzer)) {
592
                        unset($switch_scope->new_vars_in_scope[$new_var]);
593
                    } else {
594
                        $switch_scope->new_vars_in_scope[$new_var] =
595
                            Type::combineUnionTypes(clone $case_context->vars_in_scope[$new_var], $type);
596
                    }
597
                }
598
599
                $switch_scope->new_vars_possibly_in_scope = array_merge(
600
                    array_diff_key(
601
                        $case_context->vars_possibly_in_scope,
602
                        $context->vars_possibly_in_scope
603
                    ),
604
                    $switch_scope->new_vars_possibly_in_scope
605
                );
606
            }
607
        }
608
609
        if ($context->collect_exceptions) {
610
            $context->mergeExceptions($case_context);
611
        }
612
613
        $codebase = $statements_analyzer->getCodebase();
614
615
        if ($codebase->find_unused_variables) {
616
            $switch_scope->new_possibly_assigned_var_ids =
617
                $switch_scope->new_possibly_assigned_var_ids + $new_case_possibly_assigned_var_ids;
618
619
            if ($switch_scope->new_assigned_var_ids === null) {
620
                $switch_scope->new_assigned_var_ids = $new_case_assigned_var_ids;
621
            } else {
622
                $switch_scope->new_assigned_var_ids = array_intersect_key(
623
                    $switch_scope->new_assigned_var_ids,
624
                    $new_case_assigned_var_ids
625
                );
626
            }
627
628
            foreach ($case_context->unreferenced_vars as $var_id => $locations) {
629
                if (!isset($original_context->unreferenced_vars[$var_id])) {
630
                    if (isset($switch_scope->new_unreferenced_vars[$var_id])) {
631
                        $switch_scope->new_unreferenced_vars[$var_id] += $locations;
632
                    } else {
633
                        $switch_scope->new_unreferenced_vars[$var_id] = $locations;
634
                    }
635
                } else {
636
                    $new_locations = array_diff_key(
637
                        $locations,
638
                        $original_context->unreferenced_vars[$var_id]
639
                    );
640
641
                    if ($new_locations) {
642
                        if (isset($switch_scope->new_unreferenced_vars[$var_id])) {
643
                            $switch_scope->new_unreferenced_vars[$var_id] += $locations;
644
                        } else {
645
                            $switch_scope->new_unreferenced_vars[$var_id] = $locations;
646
                        }
647
                    }
648
                }
649
            }
650
651
            foreach ($case_scope->unreferenced_vars as $var_id => $locations) {
652
                if (!isset($original_context->unreferenced_vars[$var_id])) {
653
                    if (isset($switch_scope->new_unreferenced_vars[$var_id])) {
654
                        $switch_scope->new_unreferenced_vars[$var_id] += $locations;
655
                    } else {
656
                        $switch_scope->new_unreferenced_vars[$var_id] = $locations;
657
                    }
658
                } else {
659
                    $new_locations = array_diff_key(
660
                        $locations,
661
                        $original_context->unreferenced_vars[$var_id]
662
                    );
663
664
                    if ($new_locations) {
665
                        if (isset($switch_scope->new_unreferenced_vars[$var_id])) {
666
                            $switch_scope->new_unreferenced_vars[$var_id] += $locations;
667
                        } else {
668
                            $switch_scope->new_unreferenced_vars[$var_id] = $locations;
669
                        }
670
                    }
671
                }
672
            }
673
        }
674
    }
675
676
    private static function simplifyCaseEqualityExpression(
677
        PhpParser\Node\Expr $case_equality_expr,
678
        PhpParser\Node\Expr\Variable $var
679
    ) : ?PhpParser\Node\Expr\FuncCall {
680
        if ($case_equality_expr instanceof PhpParser\Node\Expr\BinaryOp\BooleanOr) {
681
            $nested_or_options = self::getOptionsFromNestedOr($case_equality_expr, $var);
682
683
            if ($nested_or_options) {
684
                return new PhpParser\Node\Expr\FuncCall(
685
                    new PhpParser\Node\Name\FullyQualified(['in_array']),
686
                    [
687
                        new PhpParser\Node\Arg(
688
                            $var
689
                        ),
690
                        new PhpParser\Node\Arg(
691
                            new PhpParser\Node\Expr\Array_(
692
                                $nested_or_options
693
                            )
694
                        ),
695
                        new PhpParser\Node\Arg(
696
                            new PhpParser\Node\Expr\ConstFetch(
697
                                new PhpParser\Node\Name\FullyQualified(['true'])
698
                            )
699
                        ),
700
                    ]
701
                );
702
            }
703
        }
704
705
        return null;
706
    }
707
708
    /**
709
     * @param array<PhpParser\Node\Expr\ArrayItem> $in_array_values
710
     * @return ?array<PhpParser\Node\Expr\ArrayItem>
0 ignored issues
show
Documentation introduced by
The doc-type ?array<PhpParser\Node\Expr\ArrayItem> could not be parsed: Unknown type name "?array" 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...
711
     */
712
    private static function getOptionsFromNestedOr(
713
        PhpParser\Node\Expr $case_equality_expr,
714
        PhpParser\Node\Expr\Variable $var,
715
        array $in_array_values = []
716
    ) : ?array {
717
        if ($case_equality_expr instanceof PhpParser\Node\Expr\BinaryOp\Identical
718
            && $case_equality_expr->left instanceof PhpParser\Node\Expr\Variable
719
            && $case_equality_expr->left->name === $var->name
720
        ) {
721
            $in_array_values[] = new PhpParser\Node\Expr\ArrayItem(
722
                $case_equality_expr->right
723
            );
724
725
            return $in_array_values;
726
        }
727
728
        if (!$case_equality_expr instanceof PhpParser\Node\Expr\BinaryOp\BooleanOr) {
729
            return null;
730
        }
731
732
        if (!$case_equality_expr->right instanceof PhpParser\Node\Expr\BinaryOp\Identical
733
            || !$case_equality_expr->right->left instanceof PhpParser\Node\Expr\Variable
734
            || $case_equality_expr->right->left->name !== $var->name
735
        ) {
736
            return null;
737
        }
738
739
        $in_array_values[] = new PhpParser\Node\Expr\ArrayItem($case_equality_expr->right->right);
740
741
        return self::getOptionsFromNestedOr(
742
            $case_equality_expr->left,
743
            $var,
744
            $in_array_values
745
        );
746
    }
747
}
748