Completed
Push — master ( 099575...90ed95 )
by Дмитрий
02:15
created

Expression::passCastBoolean()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 18
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 18
ccs 0
cts 14
cp 0
rs 8.8571
cc 6
eloc 13
nc 6
nop 1
crap 42
1
<?php
2
/**
3
 * @author Patsura Dmitry https://github.com/ovr <[email protected]>
4
 */
5
6
namespace PHPSA\Compiler;
7
8
use InvalidArgumentException;
9
use PHPSA\Check;
10
use PHPSA\CompiledExpression;
11
use PHPSA\Context;
12
use PhpParser\Node;
13
use PHPSA\Definition\ClassDefinition;
14
use PHPSA\Exception\RuntimeException;
15
use PHPSA\Variable;
16
use PHPSA\Compiler\Expression\AbstractExpressionCompiler;
17
18
class Expression
0 ignored issues
show
Complexity introduced by
This class has a complexity of 142 which exceeds the configured maximum of 50.

The class complexity is the sum of the complexity of all methods. A very high value is usually an indication that your class does not follow the single reponsibility principle and does more than one job.

Some resources for further reading:

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

Loading history...
19
{
20
    /**
21
     * @var Context
22
     */
23
    protected $context;
24
25
    /**
26
     * @param Context $context
27
     */
28 363
    public function __construct(Context $context)
29
    {
30 363
        $this->context = $context;
31 363
    }
32
33
    /**
34
     * @param $expr
35
     * @return ExpressionCompilerInterface|AbstractExpressionCompiler
36
     */
37 353
    protected function factory($expr)
38
    {
39 353
        switch (get_class($expr)) {
40
            /**
41
             * Call(s)
42
             */
43 353
            case 'PhpParser\Node\Expr\MethodCall':
44
                return new Expression\MethodCall();
45 353
            case 'PhpParser\Node\Expr\FuncCall':
46
                return new Expression\FunctionCall();
47 353
            case 'PhpParser\Node\Expr\StaticCall':
48
                return new Expression\StaticCall();
49
            /**
50
             * Operators
51
             */
52 353
            case 'PhpParser\Node\Expr\New_':
53
                return new Expression\Operators\NewOp();
54 353
            case 'PhpParser\Node\Expr\Instanceof_':
55
                return new Expression\Operators\InstanceOfOp();
56 353
            case 'PhpParser\Node\Expr\BinaryOp\Identical':
57 28
                return new Expression\BinaryOp\Identical();
58 325
            case 'PhpParser\Node\Expr\BinaryOp\Concat':
59
                return new Expression\Operators\Contact();
60 325
            case 'PhpParser\Node\Expr\BinaryOp\NotIdentical':
61 14
                return new Expression\BinaryOp\NotIdentical();
62 311
            case 'PhpParser\Node\Expr\BinaryOp\Equal':
63 34
                return new Expression\BinaryOp\Equal();
64 277
            case 'PhpParser\Node\Expr\BinaryOp\NotEqual':
65 17
                return new Expression\BinaryOp\NotEqual();
66
            /**
67
             * @link http://php.net/manual/en/language.operators.increment.php
68
             */
69 260
            case 'PhpParser\Node\Expr\PostInc':
70 4
                return new Expression\Operators\PostInc();
71 256
            case 'PhpParser\Node\Expr\PostDec':
72 4
                return new Expression\Operators\PostDec();
73
            /**
74
             * Arithmetical
75
             */
76 252
            case 'PhpParser\Node\Expr\BinaryOp\Div':
77 37
                return new Expression\Operators\Arithmetical\Div();
78 215
            case 'PhpParser\Node\Expr\BinaryOp\Plus':
79 41
                return new Expression\Operators\Arithmetical\Plus();
80 174
            case 'PhpParser\Node\Expr\BinaryOp\Minus':
81 18
                return new Expression\Operators\Arithmetical\Minus();
82 156
            case 'PhpParser\Node\Expr\BinaryOp\Mul':
83 35
                return new Expression\Operators\Arithmetical\Mul();
84 121
            case 'PhpParser\Node\Expr\BinaryOp\Mod':
85 35
                return new Expression\Operators\Arithmetical\Mod();
86
            /**
87
             * Bitwise
88
             * @link http://php.net/manual/ru/language.operators.bitwise.php
89
             */
90 86
            case 'PhpParser\Node\Expr\BinaryOp\BitwiseOr':
91
                return new Expression\Operators\Bitwise\BitwiseOr();
92 86
            case 'PhpParser\Node\Expr\BinaryOp\BitwiseXor':
93
                return new Expression\Operators\Bitwise\BitwiseXor();
94 86
            case 'PhpParser\Node\Expr\BinaryOp\BitwiseAnd':
95
                return new Expression\Operators\Bitwise\BitwiseAnd();
96 86
            case 'PhpParser\Node\Expr\BinaryOp\ShiftRight':
97
                return new Expression\Operators\Bitwise\ShiftRight();
98 86
            case 'PhpParser\Node\Expr\BinaryOp\ShiftLeft':
99
                return new Expression\Operators\Bitwise\ShiftLeft();
100 86
            case 'PhpParser\Node\Expr\BitwiseNot':
101
                return new Expression\Operators\Bitwise\BitwiseNot();
102
            /**
103
             * Logical
104
             */
105 86
            case 'PhpParser\Node\Expr\BinaryOp\BooleanOr':
106 10
                return new Expression\Operators\Logical\BooleanOr();
107 76
            case 'PhpParser\Node\Expr\BinaryOp\BooleanAnd':
108 5
                return new Expression\Operators\Logical\BooleanAnd();
109 71
            case 'PhpParser\Node\Expr\BooleanNot':
110 5
                return new Expression\Operators\Logical\BooleanNot();
111
            /**
112
             * Comparison
113
             */
114 66
            case 'PhpParser\Node\Expr\BinaryOp\Greater':
115 12
                return new Expression\Operators\Comparison\Greater();
116 54
            case 'PhpParser\Node\Expr\BinaryOp\GreaterOrEqual':
117 12
                return new Expression\Operators\Comparison\GreaterOrEqual();
118 42
            case 'PhpParser\Node\Expr\BinaryOp\Smaller':
119 12
                return new Expression\Operators\Comparison\Smaller();
120 30
            case 'PhpParser\Node\Expr\BinaryOp\SmallerOrEqual':
121 12
                return new Expression\Operators\Comparison\SmallerOrEqual();
122
            /**
123
             * Another
124
             */
125 18
            case 'PhpParser\Node\Expr\UnaryMinus':
126 9
                return new Expression\Operators\UnaryMinus();
127 9
            case 'PhpParser\Node\Expr\UnaryPlus':
128 9
                return new Expression\Operators\UnaryPlus();
129
        }
130
131
        return false;
132
    }
133
134
    /**
135
     * @param object|string $expr
136
     * @return CompiledExpression
137
     */
138 363
    public function compile($expr)
0 ignored issues
show
Complexity introduced by
This operation has 640 execution paths which exceeds the configured maximum of 200.

A high number of execution paths generally suggests many nested conditional statements and make the code less readible. This can usually be fixed by splitting the method into several smaller methods.

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

Loading history...
139
    {
140 363
        if (is_string($expr)) {
141
            return new CompiledExpression(CompiledExpression::STRING, $expr);
142
        }
143
144 363
        if (is_null($expr)) {
145
            return new CompiledExpression(CompiledExpression::NULL);
146
        }
147
148 363
        if (!is_object($expr)) {
149
            throw new InvalidArgumentException('$expr must be string/object/null');
150
        }
151
152 363
        $className = get_class($expr);
153
        switch ($className) {
154 363
            case 'PhpParser\Node\Expr\PropertyFetch':
155
                return $this->passPropertyFetch($expr);
156 363
            case 'PhpParser\Node\Stmt\Property':
157
                return $this->passProperty($expr);
158 363
            case 'PhpParser\Node\Expr\ClassConstFetch':
159
                return $this->passConstFetch($expr);
160 363
            case 'PhpParser\Node\Expr\Assign':
161
                return $this->passSymbol($expr);
162 363
            case 'PhpParser\Node\Expr\AssignRef':
163
                return $this->passSymbolByRef($expr);
164 363
            case 'PhpParser\Node\Expr\Variable':
165
                return $this->passExprVariable($expr);
166
            /**
167
             * Cast operators
168
             */
169 363
            case 'PhpParser\Node\Expr\Cast\Bool_':
170
                return $this->passCastBoolean($expr);
171 363
            case 'PhpParser\Node\Expr\Cast\Int_':
172
                return $this->passCastInt($expr);
173 363
            case 'PhpParser\Node\Expr\Cast\Double':
174
                return $this->passCastFloat($expr);
175 363
            case 'PhpParser\Node\Expr\Cast\String_':
176
                return $this->passCastString($expr);
177 363
            case 'PhpParser\Node\Expr\Cast\Unset_':
178
                return $this->passCastUnset($expr);
179
            /**
180
             * Expressions
181
             */
182 363
            case 'PhpParser\Node\Expr\Array_':
183 11
                return $this->getArray($expr);
184 362
            case 'PhpParser\Node\Expr\ConstFetch':
185
                return $this->constFetch($expr);
186 362
            case 'PhpParser\Node\Name':
187
                return $this->getNodeName($expr);
188
            /**
189
             * Simple Scalar(s)
190
             */
191 362
            case 'PHPSA\Node\Scalar\Nil':
192 8
                return new CompiledExpression(CompiledExpression::NULL);
193 361
            case 'PhpParser\Node\Scalar\LNumber':
194 256
                return new CompiledExpression(CompiledExpression::INTEGER, $expr->value);
195 358
            case 'PhpParser\Node\Scalar\DNumber':
196 131
                return new CompiledExpression(CompiledExpression::DOUBLE, $expr->value);
197 357
            case 'PhpParser\Node\Scalar\String_':
198 8
                return new CompiledExpression(CompiledExpression::STRING, $expr->value);
199 355
            case 'PHPSA\Node\Scalar\Boolean':
200 60
                return new CompiledExpression(CompiledExpression::BOOLEAN, $expr->value);
201 353
            case 'PHPSA\Node\Scalar\Fake':
202 29
                return new CompiledExpression($expr->type, $expr->value);
203
        }
204
205 353
        $expressionCompiler = $this->factory($expr);
206 353
        if (!$expressionCompiler) {
207
            $this->context->debug("Expression compiler is not implemented for {$className}");
208
            return new CompiledExpression(CompiledExpression::UNIMPLEMENTED);
209
        }
210
211 353
        $result = $expressionCompiler->pass($expr, $this->context);
212 353
        if (!$result instanceof CompiledExpression) {
213
            throw new RuntimeException('Please return CompiledExpression from ' . get_class($expressionCompiler));
214
        }
215
216 353
        return $result;
217
    }
218
219
    /**
220
     * @todo Implement
221
     *
222
     * @param Node\Stmt\Property $st
223
     * @return CompiledExpression
224
     */
225
    public function passProperty(Node\Stmt\Property $st)
0 ignored issues
show
Unused Code introduced by
The parameter $st 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...
Comprehensibility introduced by
Avoid variables with short names like $st. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
226
    {
227
        return new CompiledExpression();
228
    }
229
230
    /**
231
     * @param Node\Expr\Variable $expr
232
     * @return CompiledExpression
233
     */
234
    public function declareVariable(Node\Expr\Variable $expr)
235
    {
236
        $variable = $this->context->getSymbol($expr->name);
237
        if ($variable) {
238
            $variable->incGets();
239
            return new CompiledExpression($variable->getType(), $variable->getValue(), $variable);
240
        }
241
242
        $symbol = new Variable($expr->name, null, CompiledExpression::UNKNOWN, $this->context->getCurrentBranch());
0 ignored issues
show
Bug introduced by
It seems like $expr->name can also be of type object<PhpParser\Node\Expr>; however, PHPSA\Variable::__construct() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
243
        $this->context->addVariable($symbol);
244
245
        return new CompiledExpression;
246
    }
247
248
    /**
249
     * @param Node\Name $expr
250
     * @return CompiledExpression
251
     */
252
    public function getNodeName(Node\Name $expr)
253
    {
254
        if ($expr->toString() === 'null') {
255
            return new CompiledExpression(CompiledExpression::NULL);
256
        }
257
258
        if (in_array($expr, ['parent'])) {
259
            /** @var ClassDefinition $scope */
260
            $scope = $this->context->scope;
261
            assert($scope instanceof ClassDefinition);
262
263
            if ($scope->getExtendsClass()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $scope->getExtendsClass() of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
264
                $definition = $scope->getExtendsClassDefinition();
265
                if ($definition) {
266
                    return new CompiledExpression(CompiledExpression::OBJECT, $definition);
267
                }
268
            } else {
269
                $this->context->notice(
270
                    'no-parent',
271
                    'Cannot access parent:: when current class scope has no parent',
272
                    $expr
273
                );
274
            }
275
        }
276
277
        if (in_array($expr, ['self', 'static'])) {
278
            return CompiledExpression::fromZvalValue($this->context->scope);
279
        }
280
281
        $this->context->debug('[Unknown] How to get Node\Name for ' . $expr);
282
        return new CompiledExpression();
283
    }
284
285
    /**
286
     * (bool) {$expr}
287
     *
288
     * @param Node\Expr\Cast\Bool_ $expr
289
     * @return CompiledExpression
290
     */
291
    protected function passCastBoolean(Node\Expr\Cast\Bool_ $expr)
292
    {
293
        $expression = new Expression($this->context);
294
        $compiledExpression = $expression->compile($expr->expr);
295
296
        switch ($compiledExpression->getType()) {
297
            case CompiledExpression::BOOLEAN:
298
                $this->context->notice('stupid-cast', "You are trying to cast 'boolean' to 'boolean'", $expr);
299
                return $compiledExpression;
300
            case CompiledExpression::DOUBLE:
301
            case CompiledExpression::INTEGER:
302
            case CompiledExpression::NUMBER:
303
            case CompiledExpression::STRING:
304
                return new CompiledExpression(CompiledExpression::BOOLEAN, (bool) $compiledExpression->getValue());
305
        }
306
307
        return new CompiledExpression();
308
    }
309
310
    /**
311
     * (int) {$expr}
312
     *
313
     * @param Node\Expr\Cast\Int_ $expr
314
     * @return CompiledExpression
315
     */
316
    protected function passCastInt(Node\Expr\Cast\Int_ $expr)
317
    {
318
        $expression = new Expression($this->context);
319
        $compiledExpression = $expression->compile($expr->expr);
320
321
        switch ($compiledExpression->getType()) {
322
            case CompiledExpression::INTEGER:
323
                $this->context->notice('stupid-cast', "You are trying to cast 'int' to 'int'", $expr);
324
                return $compiledExpression;
325
            case CompiledExpression::BOOLEAN:
326
            case CompiledExpression::DOUBLE:
327
            case CompiledExpression::NUMBER:
328
            case CompiledExpression::STRING:
329
                return new CompiledExpression(CompiledExpression::INTEGER, (int) $compiledExpression->getValue());
330
        }
331
332
        return new CompiledExpression();
333
    }
334
335
    /**
336
     * (float) {$expr}
337
     *
338
     * @param Node\Expr\Cast\Double $expr
339
     * @return CompiledExpression
340
     */
341
    protected function passCastFloat(Node\Expr\Cast\Double $expr)
342
    {
343
        $expression = new Expression($this->context);
344
        $compiledExpression = $expression->compile($expr->expr);
345
346
        switch ($compiledExpression->getType()) {
347
            case CompiledExpression::DOUBLE:
348
                $this->context->notice('stupid-cast', "You are trying to cast 'float' to 'float'", $expr);
349
                return $compiledExpression;
350
            case CompiledExpression::BOOLEAN:
351
            case CompiledExpression::INTEGER:
352
            case CompiledExpression::NUMBER:
353
            case CompiledExpression::STRING:
354
                return new CompiledExpression(CompiledExpression::DOUBLE, (float) $compiledExpression->getValue());
355
        }
356
357
        return new CompiledExpression();
358
    }
359
360
    /**
361
     * (string) {$expr}
362
     *
363
     * @param Node\Expr\Cast\String_ $expr
364
     * @return CompiledExpression
365
     */
366
    protected function passCastString(Node\Expr\Cast\String_ $expr)
367
    {
368
        $expression = new Expression($this->context);
369
        $compiledExpression = $expression->compile($expr->expr);
370
371
        switch ($compiledExpression->getType()) {
372
            case CompiledExpression::STRING:
373
                $this->context->notice('stupid-cast', "You are trying to cast 'string' to 'string'", $expr);
374
                return $compiledExpression;
375
            case CompiledExpression::BOOLEAN:
376
            case CompiledExpression::INTEGER:
377
            case CompiledExpression::NUMBER:
378
            case CompiledExpression::DOUBLE:
379
                return new CompiledExpression(CompiledExpression::DOUBLE, (string) $compiledExpression->getValue());
380
        }
381
382
        return new CompiledExpression();
383
    }
384
385
    /**
386
     * (unset) {$expr}
387
     *
388
     * @param Node\Expr\Cast\Unset_ $expr
389
     * @return CompiledExpression
390
     */
391
    protected function passCastUnset(Node\Expr\Cast\Unset_ $expr)
392
    {
393
        $expression = new Expression($this->context);
394
        $compiledExpression = $expression->compile($expr->expr);
395
396
        switch ($compiledExpression->getType()) {
397
            case CompiledExpression::NULL:
398
                $this->context->notice('stupid-cast', "You are trying to cast 'unset' to 'null'", $expr);
399
                return $compiledExpression;
400
        }
401
402
        return new CompiledExpression(CompiledExpression::NULL, null);
403
    }
404
405
    /**
406
     * @param Node\Expr\PropertyFetch $expr
407
     * @return CompiledExpression
408
     */
409
    protected function passPropertyFetch(Node\Expr\PropertyFetch $expr)
410
    {
411
        $propertNameCE = $this->compile($expr->name);
412
413
        $scopeExpression = $this->compile($expr->var);
414
        if ($scopeExpression->isObject()) {
415
            $scopeExpressionValue = $scopeExpression->getValue();
416
            if ($scopeExpressionValue instanceof ClassDefinition) {
417
                $propertyName = $propertNameCE->isString() ? $propertNameCE->getValue() : false;
418
                if ($propertyName) {
419
                    if ($scopeExpressionValue->hasProperty($propertyName, true)) {
420
                        $property = $scopeExpressionValue->getProperty($propertyName, true);
421
                        return $this->compile($property);
422
                    } else {
423
                        $this->context->notice(
424
                            'undefined-property',
425
                            sprintf(
426
                                'Property %s does not exist in %s scope',
427
                                $propertyName,
428
                                $scopeExpressionValue->getName()
429
                            ),
430
                            $expr
431
                        );
432
                    }
433
                }
434
            }
435
        } elseif (!$scopeExpression->canBeObject()) {
436
            return new CompiledExpression(CompiledExpression::UNKNOWN);
437
        }
438
439
        $this->context->notice(
440
            'property-fetch-on-non-object',
441
            "It's not possible to fetch property on not object",
442
            $expr,
443
            Check::CHECK_BETA
444
        );
445
446
        return new CompiledExpression(CompiledExpression::UNKNOWN);
447
    }
448
449
    /**
450
     * @param Node\Expr\ClassConstFetch $expr
451
     * @return CompiledExpression
452
     */
453
    protected function passConstFetch(Node\Expr\ClassConstFetch $expr)
454
    {
455
        $leftCE = $this->compile($expr->class);
456
        if ($leftCE->isObject()) {
457
            $leftCEValue = $leftCE->getValue();
458
            if ($leftCEValue instanceof ClassDefinition) {
459
                if (!$leftCEValue->hasConst($expr->name)) {
460
                    $this->context->notice(
461
                        'undefined-const',
462
                        sprintf('Constant %s does not exist in %s scope', $expr->name, $expr->class),
463
                        $expr
464
                    );
465
                    return new CompiledExpression(CompiledExpression::UNKNOWN);
466
                }
467
468
                return new CompiledExpression();
469
            }
470
        }
471
472
        $this->context->debug('Unknown const fetch');
473
        return new CompiledExpression();
474
    }
475
476
    /**
477
     * @param Node\Expr\Assign $expr
478
     * @return CompiledExpression
479
     */
480
    protected function passSymbol(Node\Expr\Assign $expr)
481
    {
482
        $expression = new Expression($this->context);
483
        $compiledExpression = $expression->compile($expr->expr);
484
485
        if ($expr->var instanceof Node\Expr\List_) {
486
            $isCorrectType = false;
487
488
            switch ($compiledExpression->getType()) {
489
                case CompiledExpression::ARR:
490
                    $isCorrectType = true;
491
                    break;
492
            }
493
494
            if ($expr->var->vars) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $expr->var->vars of type PhpParser\Node\Expr[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
495
                foreach ($expr->var->vars as $key => $var) {
496
                    if ($var instanceof Node\Expr\Variable) {
497
                        $name = $expr->var->name;
0 ignored issues
show
Bug introduced by
The property name does not seem to exist in PhpParser\Node\Expr\List_.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
498
499
                        $symbol = $this->context->getSymbol($name);
500
                        if (!$symbol) {
501
                            $symbol = new Variable(
502
                                $name,
0 ignored issues
show
Bug introduced by
It seems like $name defined by $expr->var->name on line 497 can also be of type object<PhpParser\Node\Expr>; however, PHPSA\Variable::__construct() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
503
                                null,
504
                                CompiledExpression::UNKNOWN,
505
                                $this->context->getCurrentBranch()
506
                            );
507
                            $this->context->addVariable($symbol);
508
                        }
509
510
                        if (!$isCorrectType) {
511
                            $symbol->modify(CompiledExpression::NULL, null);
512
                        }
513
514
                        $symbol->incSets();
515
                    }
516
                }
517
            }
518
519
            return new CompiledExpression();
520
        }
521
522
        if ($expr->var instanceof Node\Expr\Variable) {
523
            $name = $expr->var->name;
524
525
            $symbol = $this->context->getSymbol($name);
526
            if ($symbol) {
527
                $symbol->modify($compiledExpression->getType(), $compiledExpression->getValue());
528
            } else {
529
                $symbol = new Variable(
530
                    $name,
0 ignored issues
show
Bug introduced by
It seems like $name defined by $expr->var->name on line 523 can also be of type object<PhpParser\Node\Expr>; however, PHPSA\Variable::__construct() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
531
                    $compiledExpression->getValue(),
532
                    $compiledExpression->getType(),
533
                    $this->context->getCurrentBranch()
534
                );
535
                $this->context->addVariable($symbol);
536
            }
537
538
            $symbol->incSets();
539
            return $compiledExpression;
540
        }
541
542
        if ($expr->var instanceof Node\Expr\PropertyFetch) {
543
            $compiledExpression = $this->compile($expr->var->var);
544
            if ($compiledExpression->getType() == CompiledExpression::OBJECT) {
545
                $objectDefinition = $compiledExpression->getValue();
546
                if ($objectDefinition instanceof ClassDefinition) {
547
                    if (is_string($expr->var->name)) {
548
                        if ($objectDefinition->hasProperty($expr->var->name)) {
549
                            return $this->compile($objectDefinition->getProperty($expr->var->name));
550
                        }
551
                    }
552
                }
553
            }
554
        }
555
556
        $this->context->debug('Unknown how to pass symbol');
557
        return new CompiledExpression();
558
    }
559
560
    /**
561
     * @param Node\Expr\AssignRef $expr
562
     * @return CompiledExpression
563
     */
564
    protected function passSymbolByRef(Node\Expr\AssignRef $expr)
565
    {
566
        if ($expr->var instanceof \PhpParser\Node\Expr\List_) {
567
            return new CompiledExpression();
568
        }
569
570
        if ($expr->var instanceof Node\Expr\Variable) {
571
            $name = $expr->var->name;
572
573
            $expression = new Expression($this->context);
574
            $compiledExpression = $expression->compile($expr->expr);
575
576
            $symbol = $this->context->getSymbol($name);
577
            if ($symbol) {
578
                $symbol->modify($compiledExpression->getType(), $compiledExpression->getValue());
579
580
                if ($expr->expr instanceof Node\Expr\Variable) {
581
                    $rightVarName = $expr->expr->name;
582
583
                    $rightSymbol = $this->context->getSymbol($rightVarName);
584
                    if ($rightSymbol) {
585
                        $rightSymbol->incUse();
586
                        $symbol->setReferencedTo($rightSymbol);
587
                    } else {
588
                        $this->context->debug('Cannot fetch variable by name: ' . $rightVarName);
589
                    }
590
                }
591
592
                $this->context->debug('Unknown how to pass referenced to symbol: ' . get_class($expr->expr));
593
            } else {
594
                $symbol = new Variable(
595
                    $name,
0 ignored issues
show
Bug introduced by
It seems like $name defined by $expr->var->name on line 571 can also be of type object<PhpParser\Node\Expr>; however, PHPSA\Variable::__construct() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
596
                    $compiledExpression->getValue(),
597
                    $compiledExpression->getType(),
598
                    $this->context->getCurrentBranch()
599
                );
600
                $this->context->addVariable($symbol);
601
            }
602
603
            $symbol->incSets();
604
            return $compiledExpression;
605
        }
606
607
        $this->context->debug('Unknown how to pass symbol by ref');
608
        return new CompiledExpression();
609
    }
610
611
    /**
612
     * @param Node\Expr\Variable $expr
613
     * @return CompiledExpression
614
     */
615
    protected function passExprVariable(Node\Expr\Variable $expr)
616
    {
617
        $variable = $this->context->getSymbol($expr->name);
618
        if ($variable) {
619
            $variable->incGets();
620
            return new CompiledExpression($variable->getType(), $variable->getValue(), $variable);
621
        }
622
623
        $this->context->notice(
624
            'undefined-variable',
625
            sprintf('You trying to use undefined variable $%s', $expr->name),
626
            $expr
627
        );
628
629
        return new CompiledExpression();
630
    }
631
632
    /**
633
     * Compile Array_ expression to CompiledExpression
634
     *
635
     * @param Node\Expr\Array_ $expr
636
     * @return CompiledExpression
637
     */
638 11
    protected function getArray(Node\Expr\Array_ $expr)
639
    {
640 11
        if ($expr->items === array()) {
641 8
            return new CompiledExpression(CompiledExpression::ARR, array());
642
        }
643
644 3
        $resultArray = array();
645
646 3
        foreach ($expr->items as $item) {
647 3
            $compiledValueResult = $this->compile($item->value);
648 3
            if ($item->key) {
649 1
                $compiledKeyResult = $this->compile($item->key);
650 1
                switch ($compiledKeyResult->getType()) {
651 1
                    case CompiledExpression::INTEGER:
652 1
                    case CompiledExpression::DOUBLE:
653 1
                    case CompiledExpression::BOOLEAN:
654 1
                    case CompiledExpression::NULL:
655 1
                    case CompiledExpression::STRING:
656 1
                        $resultArray[$compiledKeyResult->getValue()] = $compiledValueResult->getValue();
657 1
                        break;
658
                    default:
659
                        $this->context->debug("Type {$compiledKeyResult->getType()} is not supported for key value");
660
                        return new CompiledExpression(CompiledExpression::ARR);
661
                        break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
662 1
                }
663 1
            } else {
664 2
                $resultArray[] = $compiledValueResult->getValue();
665
            }
666 3
        }
667
668 3
        return new CompiledExpression(CompiledExpression::ARR, $resultArray);
669
    }
670
671
    /**
672
     * Convert const fetch expr to CompiledExpression
673
     *
674
     * @param Node\Expr\ConstFetch $expr
675
     * @return CompiledExpression
676
     */
677
    protected function constFetch(Node\Expr\ConstFetch $expr)
678
    {
679
        if ($expr->name instanceof Node\Name) {
680
            if ($expr->name->parts[0] === 'true') {
681
                return new CompiledExpression(CompiledExpression::BOOLEAN, true);
682
            }
683
684
            if ($expr->name->parts[0] === 'false') {
685
                return new CompiledExpression(CompiledExpression::BOOLEAN, false);
686
            }
687
        }
688
689
        /**
690
         * @todo Implement check
691
         */
692
693
        $expression = new Expression($this->context);
694
        $compiledExpr = $expression->compile($expr->name);
695
696
        return $compiledExpr;
697
    }
698
}
699