TernaryAnalyzer::analyze()   F
last analyzed

Complexity

Conditions 32
Paths > 20000

Size

Total Lines 241

Duplication

Lines 74
Ratio 30.71 %

Importance

Changes 0
Metric Value
cc 32
nc 23221
nop 3
dl 74
loc 241
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\Expression;
3
4
use PhpParser;
5
use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer;
6
use \Psalm\Internal\Analyzer\Statements\Block\IfAnalyzer;
7
use Psalm\Internal\Analyzer\StatementsAnalyzer;
8
use Psalm\CodeLocation;
9
use Psalm\Context;
10
use Psalm\Type;
11
use Psalm\Type\Algebra;
12
use Psalm\Type\Reconciler;
13
use Psalm\Internal\Type\AssertionReconciler;
14
use function array_merge;
15
use function array_map;
16
use function array_filter;
17
use function array_values;
18
use function array_keys;
19
use function preg_match;
20
use function preg_quote;
21
use function array_intersect_key;
22
23
/**
24
 * @internal
25
 */
26
class TernaryAnalyzer
27
{
28
    public static function analyze(
29
        StatementsAnalyzer $statements_analyzer,
30
        PhpParser\Node\Expr\Ternary $stmt,
31
        Context $context
32
    ) : bool {
33
        $codebase = $statements_analyzer->getCodebase();
34
35
        $if_scope = new \Psalm\Internal\Scope\IfScope();
36
37
        try {
38
            $if_conditional_scope = IfAnalyzer::analyzeIfConditional(
39
                $statements_analyzer,
40
                $stmt->cond,
41
                $context,
42
                $codebase,
43
                $if_scope,
44
                $context->branch_point ?: (int) $stmt->getAttribute('startFilePos')
45
            );
46
47
            $if_context = $if_conditional_scope->if_context;
48
49
            $cond_referenced_var_ids = $if_conditional_scope->cond_referenced_var_ids;
50
        } catch (\Psalm\Exception\ScopeAnalysisException $e) {
51
            return false;
52
        }
53
54
        $codebase = $statements_analyzer->getCodebase();
55
56
        $if_clauses = \Psalm\Type\Algebra::getFormula(
57
            \spl_object_id($stmt->cond),
58
            $stmt->cond,
59
            $context->self,
60
            $statements_analyzer,
61
            $codebase
62
        );
63
64
        $mixed_var_ids = [];
65
66
        foreach ($context->vars_in_scope as $var_id => $type) {
67
            if ($type->hasMixed()) {
68
                $mixed_var_ids[] = $var_id;
69
            }
70
        }
71
72 View Code Duplication
        foreach ($context->vars_possibly_in_scope as $var_id => $_) {
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...
73
            if (!isset($context->vars_in_scope[$var_id])) {
74
                $mixed_var_ids[] = $var_id;
75
            }
76
        }
77
78
        $if_clauses = array_values(
79
            array_map(
80
                /**
81
                 * @return \Psalm\Internal\Clause
82
                 */
83 View Code Duplication
                function (\Psalm\Internal\Clause $c) use ($mixed_var_ids) {
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...
84
                    $keys = array_keys($c->possibilities);
85
86
                    $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...
87
88
                    foreach ($keys as $key) {
89
                        foreach ($mixed_var_ids as $mixed_var_id) {
90
                            if (preg_match('/^' . preg_quote($mixed_var_id, '/') . '(\[|-)/', $key)) {
91
                                return new \Psalm\Internal\Clause([], true);
92
                            }
93
                        }
94
                    }
95
96
                    return $c;
97
                },
98
                $if_clauses
99
            )
100
        );
101
102
        $ternary_clauses = array_merge($context->clauses, $if_clauses);
103
104
        if ($if_context->reconciled_expression_clauses) {
105
            $reconciled_expression_clauses = $if_context->reconciled_expression_clauses;
106
107
            $ternary_clauses = array_values(
108
                array_filter(
109
                    $ternary_clauses,
110
                    function ($c) use ($reconciled_expression_clauses) {
111
                        return !\in_array($c->getHash(), $reconciled_expression_clauses);
112
                    }
113
                )
114
            );
115
        }
116
117
        $ternary_clauses = Algebra::simplifyCNF($ternary_clauses);
118
119
        $negated_clauses = Algebra::negateFormula($if_clauses);
120
121
        $negated_if_types = Algebra::getTruthsFromFormula(
122
            Algebra::simplifyCNF(
123
                array_merge($context->clauses, $negated_clauses)
124
            )
125
        );
126
127
        $active_if_types = [];
128
129
        $reconcilable_if_types = Algebra::getTruthsFromFormula(
130
            $ternary_clauses,
131
            \spl_object_id($stmt->cond),
132
            $cond_referenced_var_ids,
133
            $active_if_types
134
        );
135
136
        $changed_var_ids = [];
137
138 View Code Duplication
        if ($reconcilable_if_types) {
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...
139
            $if_vars_in_scope_reconciled = Reconciler::reconcileKeyedTypes(
140
                $reconcilable_if_types,
141
                $active_if_types,
142
                $if_context->vars_in_scope,
143
                $changed_var_ids,
144
                $cond_referenced_var_ids,
145
                $statements_analyzer,
146
                $statements_analyzer->getTemplateTypeMap() ?: [],
147
                $if_context->inside_loop,
148
                new CodeLocation($statements_analyzer->getSource(), $stmt->cond)
149
            );
150
151
            $if_context->vars_in_scope = $if_vars_in_scope_reconciled;
152
        }
153
154
        $t_else_context = clone $context;
155
156
        if ($stmt->if) {
157
            if (ExpressionAnalyzer::analyze($statements_analyzer, $stmt->if, $if_context) === false) {
158
                return false;
159
            }
160
161 View Code Duplication
            foreach ($if_context->vars_in_scope as $var_id => $type) {
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...
162
                if (isset($context->vars_in_scope[$var_id])) {
163
                    $context->vars_in_scope[$var_id] = Type::combineUnionTypes($context->vars_in_scope[$var_id], $type);
164
                }
165
            }
166
167
            $context->referenced_var_ids = array_merge(
168
                $context->referenced_var_ids,
169
                $if_context->referenced_var_ids
170
            );
171
172
            $context->unreferenced_vars = array_intersect_key(
173
                $context->unreferenced_vars,
174
                $if_context->unreferenced_vars
175
            );
176
        }
177
178 View Code Duplication
        if ($negated_if_types) {
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...
179
            $t_else_vars_in_scope_reconciled = Reconciler::reconcileKeyedTypes(
180
                $negated_if_types,
181
                $negated_if_types,
182
                $t_else_context->vars_in_scope,
183
                $changed_var_ids,
184
                $cond_referenced_var_ids,
185
                $statements_analyzer,
186
                $statements_analyzer->getTemplateTypeMap() ?: [],
187
                $t_else_context->inside_loop,
188
                new CodeLocation($statements_analyzer->getSource(), $stmt->else)
189
            );
190
191
            $t_else_context->vars_in_scope = $t_else_vars_in_scope_reconciled;
192
        }
193
194
        if (ExpressionAnalyzer::analyze($statements_analyzer, $stmt->else, $t_else_context) === false) {
195
            return false;
196
        }
197
198
        foreach ($t_else_context->vars_in_scope as $var_id => $type) {
199
            if (isset($context->vars_in_scope[$var_id])) {
200
                $context->vars_in_scope[$var_id] = Type::combineUnionTypes(
201
                    $context->vars_in_scope[$var_id],
202
                    $type
203
                );
204
            } elseif (isset($if_context->vars_in_scope[$var_id])
205
                && isset($if_context->assigned_var_ids[$var_id])
206
            ) {
207
                $context->vars_in_scope[$var_id] = Type::combineUnionTypes(
208
                    $if_context->vars_in_scope[$var_id],
209
                    $type
210
                );
211
            }
212
        }
213
214
        $context->vars_possibly_in_scope = array_merge(
215
            $context->vars_possibly_in_scope,
216
            $if_context->vars_possibly_in_scope,
217
            $t_else_context->vars_possibly_in_scope
218
        );
219
220
        $context->referenced_var_ids = array_merge(
221
            $context->referenced_var_ids,
222
            $t_else_context->referenced_var_ids
223
        );
224
225
        $context->unreferenced_vars = array_intersect_key(
226
            $context->unreferenced_vars,
227
            $t_else_context->unreferenced_vars
228
        );
229
230
        foreach ($context->unreferenced_vars as $var_id => $_) {
231
            if (isset($t_else_context->unreferenced_vars[$var_id])) {
232
                $context->unreferenced_vars[$var_id] += $t_else_context->unreferenced_vars[$var_id];
233
            }
234
235
            if (isset($if_context->unreferenced_vars[$var_id])) {
236
                $context->unreferenced_vars[$var_id] += $if_context->unreferenced_vars[$var_id];
237
            }
238
        }
239
240
        $lhs_type = null;
241
242
        if ($stmt->if) {
243
            if ($stmt_if_type = $statements_analyzer->node_data->getType($stmt->if)) {
244
                $lhs_type = $stmt_if_type;
245
            }
246 View Code Duplication
        } elseif ($stmt_cond_type = $statements_analyzer->node_data->getType($stmt->cond)) {
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...
247
            $if_return_type_reconciled = AssertionReconciler::reconcile(
248
                '!falsy',
249
                clone $stmt_cond_type,
250
                '',
251
                $statements_analyzer,
252
                $context->inside_loop,
253
                [],
254
                new CodeLocation($statements_analyzer->getSource(), $stmt),
255
                $statements_analyzer->getSuppressedIssues()
256
            );
257
258
            $lhs_type = $if_return_type_reconciled;
259
        }
260
261 View Code Duplication
        if ($lhs_type && ($stmt_else_type = $statements_analyzer->node_data->getType($stmt->else))) {
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...
262
            $statements_analyzer->node_data->setType($stmt, Type::combineUnionTypes($lhs_type, $stmt_else_type));
263
        } else {
264
            $statements_analyzer->node_data->setType($stmt, Type::getMixed());
265
        }
266
267
        return true;
268
    }
269
}
270