ForAnalyzer::analyze()   F
last analyzed

Complexity

Conditions 48
Paths > 20000

Size

Total Lines 179

Duplication

Lines 26
Ratio 14.53 %

Importance

Changes 0
Metric Value
cc 48
nc 131401
nop 3
dl 26
loc 179
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\Internal\Analyzer\ScopeAnalyzer;
6
use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer;
7
use Psalm\Internal\Analyzer\StatementsAnalyzer;
8
use Psalm\Context;
9
use Psalm\Internal\Scope\LoopScope;
10
use Psalm\Type;
11
use function array_merge;
12
use function in_array;
13
use function array_intersect_key;
14
15
/**
16
 * @internal
17
 */
18
class ForAnalyzer
19
{
20
    /**
21
     * @param   StatementsAnalyzer           $statements_analyzer
22
     * @param   PhpParser\Node\Stmt\For_    $stmt
23
     * @param   Context                     $context
24
     *
25
     * @return  false|null
26
     */
27
    public static function analyze(
28
        StatementsAnalyzer $statements_analyzer,
29
        PhpParser\Node\Stmt\For_ $stmt,
30
        Context $context
31
    ) {
32
        $pre_assigned_var_ids = $context->assigned_var_ids;
33
        $context->assigned_var_ids = [];
34
35
        $init_var_types = [];
36
37
        foreach ($stmt->init as $init) {
38
            if (ExpressionAnalyzer::analyze($statements_analyzer, $init, $context) === false) {
39
                return false;
40
            }
41
42
            if ($init instanceof PhpParser\Node\Expr\Assign
43
                && $init->var instanceof PhpParser\Node\Expr\Variable
44
                && \is_string($init->var->name)
45
                && ($init_var_type = $statements_analyzer->node_data->getType($init->expr))
46
            ) {
47
                if ($init_var_type->isSingleIntLiteral()) {
48
                    $context->vars_in_scope['$' . $init->var->name] = Type::getInt();
49
                }
50
51
                $init_var_types[$init->var->name] = $init_var_type;
52
            }
53
        }
54
55
        $assigned_var_ids = $context->assigned_var_ids;
56
57
        $context->assigned_var_ids = array_merge(
58
            $pre_assigned_var_ids,
59
            $assigned_var_ids
60
        );
61
62
        $while_true = !$stmt->cond && !$stmt->init && !$stmt->loop;
63
64
        $pre_context = null;
65
66
        if ($while_true) {
67
            $pre_context = clone $context;
68
        }
69
70
        $for_context = clone $context;
71
72
        $for_context->inside_loop = true;
73
        $for_context->break_types[] = 'loop';
74
75
        $codebase = $statements_analyzer->getCodebase();
76
77
        if ($codebase->alter_code) {
78
            $for_context->branch_point = $for_context->branch_point ?: (int) $stmt->getAttribute('startFilePos');
79
        }
80
81
        $loop_scope = new LoopScope($for_context, $context);
82
83
        $loop_scope->protected_var_ids = array_merge(
84
            $assigned_var_ids,
85
            $context->protected_var_ids
86
        );
87
88
        LoopAnalyzer::analyze(
89
            $statements_analyzer,
90
            $stmt->stmts,
91
            $stmt->cond,
92
            $stmt->loop,
93
            $loop_scope,
94
            $inner_loop_context
95
        );
96
97
        if (!$inner_loop_context) {
98
            throw new \UnexpectedValueException('There should be an inner loop context');
99
        }
100
101
        $always_enters_loop = false;
102
103
        foreach ($stmt->cond as $cond) {
104
            if ($cond_type = $statements_analyzer->node_data->getType($cond)) {
105
                foreach ($cond_type->getAtomicTypes() as $iterator_type) {
106
                    $always_enters_loop = $iterator_type instanceof Type\Atomic\TTrue;
107
108
                    break;
109
                }
110
            }
111
112
            if (\count($stmt->init) === 1
113
                && \count($stmt->cond) === 1
114
                && $cond instanceof PhpParser\Node\Expr\BinaryOp
115
                && $cond->right instanceof PhpParser\Node\Scalar\LNumber
116
                && $cond->left instanceof PhpParser\Node\Expr\Variable
117
                && \is_string($cond->left->name)
118
                && isset($init_var_types[$cond->left->name])
119
                && $init_var_types[$cond->left->name]->isSingleIntLiteral()
120
            ) {
121
                $init_value = $init_var_types[$cond->left->name]->getSingleIntLiteral()->value;
122
                $cond_value = $cond->right->value;
123
124
                if ($cond instanceof PhpParser\Node\Expr\BinaryOp\Smaller && $init_value < $cond_value) {
125
                    $always_enters_loop = true;
126
                    break;
127
                }
128
129
                if ($cond instanceof PhpParser\Node\Expr\BinaryOp\SmallerOrEqual && $init_value <= $cond_value) {
130
                    $always_enters_loop = true;
131
                    break;
132
                }
133
134
                if ($cond instanceof PhpParser\Node\Expr\BinaryOp\Greater && $init_value > $cond_value) {
135
                    $always_enters_loop = true;
136
                    break;
137
                }
138
139
                if ($cond instanceof PhpParser\Node\Expr\BinaryOp\GreaterOrEqual && $init_value >= $cond_value) {
140
                    $always_enters_loop = true;
141
                    break;
142
                }
143
            }
144
        }
145
146
        if ($while_true) {
147
            $always_enters_loop = true;
148
        }
149
150
        $can_leave_loop = !$while_true
151
            || in_array(ScopeAnalyzer::ACTION_BREAK, $loop_scope->final_actions, true);
152
153 View Code Duplication
        if ($always_enters_loop && $can_leave_loop) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
154
            foreach ($inner_loop_context->vars_in_scope as $var_id => $type) {
155
                // if there are break statements in the loop it's not certain
156
                // that the loop has finished executing
157
                if (in_array(ScopeAnalyzer::ACTION_BREAK, $loop_scope->final_actions, true)
158
                    || in_array(ScopeAnalyzer::ACTION_CONTINUE, $loop_scope->final_actions, true)
159
                ) {
160
                    if (isset($loop_scope->possibly_defined_loop_parent_vars[$var_id])) {
161
                        $context->vars_in_scope[$var_id] = Type::combineUnionTypes(
162
                            $type,
163
                            $loop_scope->possibly_defined_loop_parent_vars[$var_id]
164
                        );
165
                    }
166
                } else {
167
                    $context->vars_in_scope[$var_id] = $type;
168
                }
169
            }
170
        }
171
172
        $for_context->loop_scope = null;
173
174 View Code Duplication
        if ($can_leave_loop) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
175
            $context->vars_possibly_in_scope = array_merge(
176
                $context->vars_possibly_in_scope,
177
                $for_context->vars_possibly_in_scope
178
            );
179
        } elseif ($pre_context) {
180
            $context->vars_possibly_in_scope = $pre_context->vars_possibly_in_scope;
181
        }
182
183
        $context->referenced_var_ids = array_intersect_key(
184
            $for_context->referenced_var_ids,
185
            $context->referenced_var_ids
186
        );
187
188
        if ($codebase->find_unused_variables) {
189
            foreach ($for_context->unreferenced_vars as $var_id => $locations) {
190
                if (isset($loop_scope->referenced_var_ids[$var_id])) {
191
                    $statements_analyzer->registerVariableUses($locations);
192
                } elseif (isset($context->unreferenced_vars[$var_id])) {
193
                    $context->unreferenced_vars[$var_id] += $locations;
194
                } else {
195
                    $context->unreferenced_vars[$var_id] = $locations;
196
                }
197
            }
198
        }
199
200
        if ($context->collect_exceptions) {
201
            $context->mergeExceptions($for_context);
202
        }
203
204
        return null;
205
    }
206
}
207