Completed
Pull Request — master (#149)
by Enrico
04:16
created

Expression::getNodeName()   C

Complexity

Conditions 7
Paths 11

Size

Total Lines 36
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 17.1096

Importance

Changes 0
Metric Value
cc 7
eloc 21
nc 11
nop 1
dl 0
loc 36
ccs 9
cts 22
cp 0.4091
crap 17.1096
rs 6.7272
c 0
b 0
f 0
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\Compiler\Event\ExpressionBeforeCompile;
12
use PHPSA\Context;
13
use PhpParser\Node;
14
use PHPSA\Definition\ClassDefinition;
15
use PHPSA\Exception\RuntimeException;
16
use PHPSA\Variable;
17
use PHPSA\Compiler\Expression\AbstractExpressionCompiler;
18
use Webiny\Component\EventManager\EventManager;
19
20
class Expression
0 ignored issues
show
Complexity introduced by
This class has a complexity of 158 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...
21
{
22
    /**
23
     * @var Context
24
     */
25
    protected $context;
26
27
    /**
28
     * @var EventManager
29
     */
30
    protected $eventManager;
31
32
    /**
33
     * @param Context $context
34
     */
35 775
    public function __construct(Context $context, EventManager $eventManager)
36
    {
37 775
        $this->context = $context;
38 775
        $this->eventManager = $eventManager;
39 775
    }
40
41
    /**
42
     * @param $expr
43
     * @return ExpressionCompilerInterface|AbstractExpressionCompiler
44
     */
45 757
    protected function factory($expr)
46
    {
47 757
        switch (get_class($expr)) {
48
            /**
49
             * Call(s)
50
             */
51 757
            case Node\Expr\MethodCall::class:
52
                return new Expression\MethodCall();
53 757
            case Node\Expr\FuncCall::class:
54 10
                return new Expression\FunctionCall();
55 749
            case Node\Expr\StaticCall::class:
56
                return new Expression\StaticCall();
57
            /**
58
             * Operators
59
             */
60 749
            case Node\Expr\New_::class:
61 2
                return new Expression\Operators\NewOp();
62 747
            case Node\Expr\Instanceof_::class:
63
                return new Expression\Operators\InstanceOfOp();
64
            /**
65
             * AssignOp
66
             */
67 747
            case Node\Expr\AssignOp\Pow::class:
68 21
                return new Expression\AssignOp\Pow();
69 726
            case Node\Expr\AssignOp\Plus::class:
70 17
                return new Expression\AssignOp\Plus();
71 709
            case Node\Expr\AssignOp\Minus::class:
72 20
                return new Expression\AssignOp\Minus();
73 689
            case Node\Expr\AssignOp\Mul::class:
74 20
                return new Expression\AssignOp\Mul();
75 669
            case Node\Expr\AssignOp\Div::class:
76 18
                return new Expression\AssignOp\Div();
77 651
            case Node\Expr\AssignOp\Mod::class:
78 11
                return new Expression\AssignOp\Mod();
79 640
            case Node\Expr\AssignOp\BitwiseOr::class:
80 12
                return new Expression\AssignOp\BitwiseOr();
81 628
            case Node\Expr\AssignOp\BitwiseAnd::class:
82 12
                return new Expression\AssignOp\BitwiseAnd();
83 616
            case Node\Expr\AssignOp\BitwiseXor::class:
84 12
                return new Expression\AssignOp\BitwiseXor();
85 604
            case Node\Expr\AssignOp\Concat::class:
86 14
                return new Expression\AssignOp\Concat();
87 590
            case Node\Expr\AssignOp\ShiftLeft::class:
88 12
                return new Expression\AssignOp\ShiftLeft();
89 578
            case Node\Expr\AssignOp\ShiftRight::class:
90 12
                return new Expression\AssignOp\ShiftRight();
91
92
            /**
93
             * BinaryOp
94
             */
95 566
            case Node\Expr\BinaryOp\Identical::class:
96 28
                return new Expression\BinaryOp\Identical();
97 538
            case Node\Expr\BinaryOp\Concat::class:
98 1
                return new Expression\Operators\Concat();
99 537
            case Node\Expr\BinaryOp\NotIdentical::class:
100 14
                return new Expression\BinaryOp\NotIdentical();
101 523
            case Node\Expr\BinaryOp\Equal::class:
102 34
                return new Expression\BinaryOp\Equal();
103 489
            case Node\Expr\BinaryOp\NotEqual::class:
104 17
                return new Expression\BinaryOp\NotEqual();
105 472
            case Node\Expr\BinaryOp\Spaceship::class:
106 11
                return new Expression\BinaryOp\SpaceShip();
107 461
            case Node\Expr\BinaryOp\Coalesce::class:
108 3
                return new Expression\BinaryOp\Coalesce();
109
                
110
            /**
111
             * @link http://php.net/manual/en/language.operators.increment.php
112
             */
113 458
            case Node\Expr\PostInc::class:
114 8
                return new Expression\Operators\PostInc();
115 450
            case Node\Expr\PostDec::class:
116 8
                return new Expression\Operators\PostDec();
117 442
            case Node\Expr\PreInc::class:
118 8
                return new Expression\Operators\PreInc();
119 434
            case Node\Expr\PreDec::class:
120 8
                return new Expression\Operators\PreDec();
121
            /**
122
             * Arithmetical
123
             */
124 426
            case Node\Expr\BinaryOp\Div::class:
125 37
                return new Expression\Operators\Arithmetical\Div();
126 389
            case Node\Expr\BinaryOp\Plus::class:
127 45
                return new Expression\Operators\Arithmetical\Plus();
128 344
            case Node\Expr\BinaryOp\Minus::class:
129 18
                return new Expression\Operators\Arithmetical\Minus();
130 326
            case Node\Expr\BinaryOp\Mul::class:
131 35
                return new Expression\Operators\Arithmetical\Mul();
132 291
            case Node\Expr\BinaryOp\Mod::class:
133 35
                return new Expression\Operators\Arithmetical\Mod();
134 256
            case Node\Expr\BinaryOp\Pow::class:
135 21
                return new Expression\Operators\Arithmetical\Pow();
136
137
            /**
138
             * Bitwise
139
             * @link http://php.net/manual/ru/language.operators.bitwise.php
140
             */
141 235
            case Node\Expr\BinaryOp\BitwiseOr::class:
142 12
                return new Expression\Operators\Bitwise\BitwiseOr();
143 223
            case Node\Expr\BinaryOp\BitwiseXor::class:
144 12
                return new Expression\Operators\Bitwise\BitwiseXor();
145 211
            case Node\Expr\BinaryOp\BitwiseAnd::class:
146 12
                return new Expression\Operators\Bitwise\BitwiseAnd();
147 199
            case Node\Expr\BinaryOp\ShiftRight::class:
148 12
                return new Expression\Operators\Bitwise\ShiftRight();
149 187
            case Node\Expr\BinaryOp\ShiftLeft::class:
150 12
                return new Expression\Operators\Bitwise\ShiftLeft();
151 175
            case Node\Expr\BitwiseNot::class:
152 5
                return new Expression\Operators\Bitwise\BitwiseNot();
153
            /**
154
             * Logical
155
             */
156 170
            case Node\Expr\BinaryOp\BooleanOr::class:
157 17
                return new Expression\Operators\Logical\BooleanOr();
158 153
            case Node\Expr\BinaryOp\BooleanAnd::class:
159 15
                return new Expression\Operators\Logical\BooleanAnd();
160 138
            case Node\Expr\BooleanNot::class:
161 9
                return new Expression\Operators\Logical\BooleanNot();
162 129
            case Node\Expr\BinaryOp\LogicalAnd::class:
163 15
                return new Expression\Operators\Logical\LogicalAnd();
164 114
            case Node\Expr\BinaryOp\LogicalOr::class:
165 17
                return new Expression\Operators\Logical\LogicalOr();
166 97
            case Node\Expr\BinaryOp\LogicalXor::class:
167 17
                return new Expression\Operators\Logical\LogicalXor();
168
169
            /**
170
             * Comparison
171
             */
172 80
            case Node\Expr\BinaryOp\Greater::class:
173 12
                return new Expression\Operators\Comparison\Greater();
174 68
            case Node\Expr\BinaryOp\GreaterOrEqual::class:
175 12
                return new Expression\Operators\Comparison\GreaterOrEqual();
176 56
            case Node\Expr\BinaryOp\Smaller::class:
177 12
                return new Expression\Operators\Comparison\Smaller();
178 44
            case Node\Expr\BinaryOp\SmallerOrEqual::class:
179 12
                return new Expression\Operators\Comparison\SmallerOrEqual();
180
181
            /**
182
             * Casts
183
             */
184 32
            case Node\Expr\Cast\Array_::class:
185 1
                return new Expression\Casts\ArrayCast();
186 32
            case Node\Expr\Cast\Bool_::class:
187 1
                return new Expression\Casts\BoolCast();
188 32
            case Node\Expr\Cast\Int_::class:
189 1
                return new Expression\Casts\IntCast();
190 32
            case Node\Expr\Cast\Double::class:
191 1
                return new Expression\Casts\DoubleCast();
192 32
            case Node\Expr\Cast\Object_::class:
193 1
                return new Expression\Casts\ObjectCast();
194 32
            case Node\Expr\Cast\String_::class:
195 1
                return new Expression\Casts\StringCast();
196 32
            case Node\Expr\Cast\Unset_::class:
197 1
                return new Expression\Casts\UnsetCast();
198
199
200
            /**
201
             * Other
202
             */
203 31
            case Node\Expr\Closure::class:
204
                return new Expression\Closure();
205 31
            case Node\Expr\UnaryMinus::class:
206 9
                return new Expression\Operators\UnaryMinus();
207 22
            case Node\Expr\UnaryPlus::class:
208 9
                return new Expression\Operators\UnaryPlus();
209 13
            case Node\Expr\Exit_::class:
210 1
                return new Expression\ExitOp();
211 12
            case Node\Expr\Isset_::class:
212 3
                return new Expression\IssetOp();
213 9
            case Node\Expr\Print_::class:
214 1
                return new Expression\PrintOp();
215 8
            case Node\Expr\Empty_::class:
216 5
                return new Expression\EmptyOp();
217 3
            case Node\Expr\Eval_::class:
218 2
                return new Expression\EvalOp();
219 1
        }
220
221 1
        return false;
222
    }
223
224
    /**
225
     * @param object|string $expr
226
     * @return CompiledExpression
227
     */
228 775
    public function compile($expr)
0 ignored issues
show
Complexity introduced by
This operation has 544 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...
229
    {
230 775
        if (is_string($expr)) {
231 13
            return new CompiledExpression(CompiledExpression::STRING, $expr);
232
        }
233
234 775
        if (is_null($expr)) {
235 7
            return new CompiledExpression(CompiledExpression::NULL);
236
        }
237
238 775
        if (!is_object($expr)) {
239
            throw new InvalidArgumentException('$expr must be string/object/null');
240
        }
241
242 775
        $this->eventManager->fire(
243 775
            ExpressionBeforeCompile::EVENT_NAME,
244 775
            new ExpressionBeforeCompile(
245 775
                $expr,
246 775
                $this->context
247 775
            )
248 775
        );
249
250 775
        $className = get_class($expr);
251
        switch ($className) {
252 775
            case Node\Arg::class:
253
                /**
254
                 * @todo Better compile
255
                 */
256 2
                return $this->compile($expr->value);
257 775
            case Node\Expr\PropertyFetch::class:
258
                return $this->passPropertyFetch($expr);
259 775
            case Node\Stmt\Property::class:
260
                return $this->passProperty($expr);
261 775
            case Node\Expr\ClassConstFetch::class:
262
                return $this->passConstFetch($expr);
263 775
            case Node\Expr\Assign::class:
264 13
                return $this->passSymbol($expr);
265 775
            case Node\Expr\AssignRef::class:
266 1
                return $this->passSymbolByRef($expr);
267 775
            case Node\Expr\Variable::class:
268 10
                return $this->passExprVariable($expr);
269
270
            /**
271
             * Expressions
272
             */
273 775
            case Node\Expr\Array_::class:
274 31
                return $this->getArray($expr);
275 774
            case Node\Expr\ConstFetch::class:
276 6
                return $this->constFetch($expr);
277 774
            case Node\Name::class:
278 11
                return $this->getNodeName($expr);
279 774
            case Node\Name\FullyQualified::class:
280
                return $this->getFullyQualifiedNodeName($expr);
281
282
            /**
283
             * Simple Scalar(s)
284
             */
285 774
            case \PHPSA\Node\Scalar\Nil::class:
286 25
                return new CompiledExpression(CompiledExpression::NULL);
287 773
            case Node\Scalar\LNumber::class:
288 475
                return new CompiledExpression(CompiledExpression::INTEGER, $expr->value);
289 765
            case Node\Scalar\DNumber::class:
290 200
                return new CompiledExpression(CompiledExpression::DOUBLE, $expr->value);
291 764
            case Node\Scalar\String_::class:
292 40
                return new CompiledExpression(CompiledExpression::STRING, $expr->value);
293 759
            case \PHPSA\Node\Scalar\Boolean::class:
294 229
                return new CompiledExpression(CompiledExpression::BOOLEAN, $expr->value);
295 757
            case \PHPSA\Node\Scalar\Fake::class:
296 71
                return new CompiledExpression($expr->type, $expr->value);
297
        }
298
299 757
        $expressionCompiler = $this->factory($expr);
300 757
        if (!$expressionCompiler) {
301 1
            $this->context->debug("Expression compiler is not implemented for {$className}");
302 1
            return new CompiledExpression(CompiledExpression::UNIMPLEMENTED);
303
        }
304
305 757
        $result = $expressionCompiler->pass($expr, $this->context);
306 757
        if (!$result instanceof CompiledExpression) {
307
            throw new RuntimeException('Please return CompiledExpression from ' . get_class($expressionCompiler));
308
        }
309
310 757
        return $result;
311
    }
312
313
    /**
314
     * @todo Implement
315
     *
316
     * @param Node\Stmt\Property $st
317
     * @return CompiledExpression
318
     */
319
    public function passProperty(Node\Stmt\Property $st)
0 ignored issues
show
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...
320
    {
321
        $docBlock = $st->getDocComment();
322
        if (!$docBlock) {
323
            $this->context->notice(
324
                'missing-docblock',
325
                sprintf('Missing docblock for $%s property', $st->props[0]->name),
326
                $st
327
            );
328
329
            return new CompiledExpression();
330
        }
331
332
        $phpdoc = new \phpDocumentor\Reflection\DocBlock($docBlock->getText());
333
334
        $varTags = $phpdoc->getTagsByName('var');
335
        if ($varTags) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $varTags of type phpDocumentor\Reflection\DocBlock\Tag[] 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...
336
            /** @var \phpDocumentor\Reflection\DocBlock\Tag\VarTag $varTag */
337
            $varTag = current($varTags);
338
339
            $typeResolver = new \phpDocumentor\Reflection\TypeResolver();
340
341
            try {
342
                $type = $typeResolver->resolve($varTag->getType());
343
            } catch (\InvalidArgumentException $e) {
344
                return new CompiledExpression();
345
            }
346
347
            if ($type) {
348
                switch (get_class($type)) {
349
                    case \phpDocumentor\Reflection\Types\Object_::class:
350
                        return new CompiledExpression(
351
                            CompiledExpression::OBJECT
352
                        );
353
                    case \phpDocumentor\Reflection\Types\Integer::class:
354
                        return new CompiledExpression(
355
                            CompiledExpression::INTEGER
356
                        );
357
                    case \phpDocumentor\Reflection\Types\String_::class:
358
                        return new CompiledExpression(
359
                            CompiledExpression::STRING
360
                        );
361
                    case \phpDocumentor\Reflection\Types\Float_::class:
362
                        return new CompiledExpression(
363
                            CompiledExpression::DOUBLE
364
                        );
365
                    case \phpDocumentor\Reflection\Types\Null_::class:
366
                        return new CompiledExpression(
367
                            CompiledExpression::NULL
368
                        );
369
                    case \phpDocumentor\Reflection\Types\Boolean::class:
370
                        return new CompiledExpression(
371
                            CompiledExpression::BOOLEAN
372
                        );
373
                }
374
            }
375
        }
376
377
        return new CompiledExpression();
378
    }
379
380
    /**
381
     * @param Node\Expr\Variable $expr
382
     * @return CompiledExpression
383
     */
384 1
    public function declareVariable(Node\Expr\Variable $expr)
385
    {
386 1
        $variable = $this->context->getSymbol($expr->name);
387 1
        if (!$variable) {
388 1
            $variable = 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...
389 1
            $this->context->addVariable($variable);
390 1
        }
391
392 1
        return new CompiledExpression($variable->getType(), $variable->getValue(), $variable);
393
    }
394
395
    /**
396
     * @param Node\Name\FullyQualified $expr
397
     * @return CompiledExpression
398
     */
399
    public function getFullyQualifiedNodeName(Node\Name\FullyQualified $expr)
400
    {
401
        $this->context->debug('Unimplemented FullyQualified', $expr);
402
403
        return new CompiledExpression;
404
    }
405
406
    /**
407
     * @param Node\Name $expr
408
     * @return CompiledExpression
409
     */
410 11
    public function getNodeName(Node\Name $expr)
411
    {
412 11
        $nodeString = $expr->toString();
413 11
        if ($nodeString === 'null') {
414 1
            return new CompiledExpression(CompiledExpression::NULL);
415
        }
416
417 10
        if (in_array($nodeString, ['parent'], true)) {
418
            /** @var ClassDefinition $scope */
419
            $scope = $this->context->scope;
420
            assert($scope instanceof ClassDefinition);
421
422
            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...
423
                $definition = $scope->getExtendsClassDefinition();
424
                if ($definition) {
425
                    return new CompiledExpression(CompiledExpression::OBJECT, $definition);
426
                }
427
            } else {
428
                $this->context->notice(
429
                    'no-parent',
430
                    'Cannot access parent:: when current class scope has no parent',
431
                    $expr
432
                );
433
            }
434
        }
435
436 10
        if (in_array($nodeString, ['self', 'static'], true)) {
437
            return CompiledExpression::fromZvalValue($this->context->scope);
438
        }
439
440 10
        if (defined($nodeString)) {
441 1
            return CompiledExpression::fromZvalValue(constant($expr));
442
        }
443
444 10
        return new CompiledExpression(CompiledExpression::STRING, $expr->toString());
445
    }
446
447
    /**
448
     * @param Node\Expr\PropertyFetch $expr
449
     * @return CompiledExpression
450
     */
451
    protected function passPropertyFetch(Node\Expr\PropertyFetch $expr)
452
    {
453
        $propertNameCE = $this->compile($expr->name);
454
455
        $scopeExpression = $this->compile($expr->var);
456
        if ($scopeExpression->isObject()) {
457
            $scopeExpressionValue = $scopeExpression->getValue();
458
            if ($scopeExpressionValue instanceof ClassDefinition) {
459
                $propertyName = $propertNameCE->isString() ? $propertNameCE->getValue() : false;
460
                if ($propertyName) {
461
                    if ($scopeExpressionValue->hasProperty($propertyName, true)) {
462
                        $property = $scopeExpressionValue->getProperty($propertyName, true);
463
                        return $this->compile($property);
464
                    } else {
465
                        $this->context->notice(
466
                            'undefined-property',
467
                            sprintf(
468
                                'Property %s does not exist in %s scope',
469
                                $propertyName,
470
                                $scopeExpressionValue->getName()
471
                            ),
472
                            $expr
473
                        );
474
                    }
475
                }
476
            }
477
478
            return new CompiledExpression(CompiledExpression::UNKNOWN);
479
        } elseif (!$scopeExpression->canBeObject()) {
480
            return new CompiledExpression(CompiledExpression::UNKNOWN);
481
        }
482
483
        $this->context->notice(
484
            'property-fetch-on-non-object',
485
            "It's not possible to fetch property on not object",
486
            $expr,
487
            Check::CHECK_BETA
488
        );
489
490
        return new CompiledExpression(CompiledExpression::UNKNOWN);
491
    }
492
493
    /**
494
     * @param Node\Expr\ClassConstFetch $expr
495
     * @return CompiledExpression
496
     */
497
    protected function passConstFetch(Node\Expr\ClassConstFetch $expr)
498
    {
499
        $leftCE = $this->compile($expr->class);
500
        if ($leftCE->isObject()) {
501
            $leftCEValue = $leftCE->getValue();
502
            if ($leftCEValue instanceof ClassDefinition) {
503
                if (!$leftCEValue->hasConst($expr->name, true)) {
504
                    $this->context->notice(
505
                        'undefined-const',
506
                        sprintf('Constant %s does not exist in %s scope', $expr->name, $expr->class),
507
                        $expr
508
                    );
509
                    return new CompiledExpression(CompiledExpression::UNKNOWN);
510
                }
511
512
                return new CompiledExpression();
513
            }
514
        }
515
516
        $this->context->debug('Unknown const fetch', $expr);
517
        return new CompiledExpression();
518
    }
519
520
    /**
521
     * @param Node\Expr\Assign $expr
522
     * @return CompiledExpression
523
     */
524 13
    protected function passSymbol(Node\Expr\Assign $expr)
0 ignored issues
show
Complexity introduced by
This operation has 240 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...
525
    {
526 13
        $compiledExpression = $this->compile($expr->expr);
527
528 13
        if ($expr->var instanceof Node\Expr\List_) {
529
            $isCorrectType = false;
530
531
            switch ($compiledExpression->getType()) {
532
                case CompiledExpression::ARR:
533
                    $isCorrectType = true;
534
                    break;
535
            }
536
537
            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...
538
                foreach ($expr->var->vars as $key => $var) {
539
                    if ($var instanceof Node\Expr\Variable) {
540
                        $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...
541
542
                        $symbol = $this->context->getSymbol($name);
543
                        if (!$symbol) {
544
                            $symbol = new Variable(
545
                                $name,
0 ignored issues
show
Bug introduced by
It seems like $name defined by $expr->var->name on line 540 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...
546
                                null,
547
                                CompiledExpression::UNKNOWN,
548
                                $this->context->getCurrentBranch()
549
                            );
550
                            $this->context->addVariable($symbol);
551
                        }
552
553
                        if (!$isCorrectType) {
554
                            $symbol->modify(CompiledExpression::NULL, null);
555
                        }
556
557
                        $symbol->incSets();
558
                    }
559
                }
560
            }
561
562
            return new CompiledExpression();
563
        }
564
565
566 13
        if ($expr->var instanceof Node\Expr\Variable) {
567 13
            $compiledExpressionName = $this->compile($expr->var->name);
0 ignored issues
show
Comprehensibility Naming introduced by
The variable name $compiledExpressionName exceeds the maximum configured length of 20.

Very long variable names usually make code harder to read. It is therefore recommended not to make variable names too verbose.

Loading history...
568 13
            switch ($compiledExpressionName->getType()) {
569 13
                case CompiledExpression::STRING:
570 13
                    break;
571
                default:
572
                    $this->context->debug('Unexpected type of Variable name after compile');
573
                    return new CompiledExpression();
574 13
            }
575
576 13
            $symbol = $this->context->getSymbol($compiledExpressionName->getValue());
577 13
            if ($symbol) {
578 2
                $symbol->modify($compiledExpression->getType(), $compiledExpression->getValue());
579 2
                $this->context->modifyReferencedVariables(
580 2
                    $symbol,
581 2
                    $compiledExpression->getType(),
582 2
                    $compiledExpression->getValue()
583 2
                );
584 2
            } else {
585 13
                $symbol = new Variable(
586 13
                    $compiledExpressionName->getValue(),
587 13
                    $compiledExpression->getValue(),
588 13
                    $compiledExpression->getType(),
589 13
                    $this->context->getCurrentBranch()
590 13
                );
591 13
                $this->context->addVariable($symbol);
592
            }
593
594 13
            $symbol->incSets();
595 13
            return $compiledExpression;
596
        }
597
598 1
        if ($expr->var instanceof Node\Expr\PropertyFetch) {
599 1
            $compiledExpression = $this->compile($expr->var->var);
600 1
            if ($compiledExpression->getType() == CompiledExpression::OBJECT) {
601 1
                $objectDefinition = $compiledExpression->getValue();
602 1
                if ($objectDefinition instanceof ClassDefinition) {
603 1
                    if (is_string($expr->var->name)) {
604 1
                        if ($objectDefinition->hasProperty($expr->var->name)) {
605
                            return $this->compile($objectDefinition->getProperty($expr->var->name));
606
                        }
607 1
                    }
608 1
                }
609 1
            }
610 1
        }
611
612 1
        $this->context->debug('Unknown how to pass symbol');
613 1
        return new CompiledExpression();
614
    }
615
616
    /**
617
     * @param Node\Expr\AssignRef $expr
618
     * @return CompiledExpression
619
     */
620 1
    protected function passSymbolByRef(Node\Expr\AssignRef $expr)
621
    {
622 1
        if ($expr->var instanceof Node\Expr\Variable) {
623 1
            $name = $expr->var->name;
624
625 1
            $compiledExpression = $this->compile($expr->expr);
626
627 1
            $symbol = $this->context->getSymbol($name);
628 1
            if ($symbol) {
629
                $symbol->modify($compiledExpression->getType(), $compiledExpression->getValue());
630
            } else {
631 1
                $symbol = new Variable(
632 1
                    $name,
0 ignored issues
show
Bug introduced by
It seems like $name defined by $expr->var->name on line 623 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...
633 1
                    $compiledExpression->getValue(),
634 1
                    $compiledExpression->getType(),
635 1
                    $this->context->getCurrentBranch()
636 1
                );
637 1
                $this->context->addVariable($symbol);
638
            }
639
640 1
            if ($expr->expr instanceof Node\Expr\Variable) {
641 1
                $rightVarName = $expr->expr->name;
642
643 1
                $rightSymbol = $this->context->getSymbol($rightVarName);
644 1
                if ($rightSymbol) {
645 1
                    $rightSymbol->incUse();
646 1
                    $symbol->setReferencedTo($rightSymbol);
647 1
                } else {
648
                    $this->context->debug('Cannot fetch variable by name: ' . $rightVarName);
649
                }
650 1
            }
651
652 1
            $symbol->incSets();
653 1
            return $compiledExpression;
654
        }
655
656
        $this->context->debug('Unknown how to pass symbol by ref');
657
        return new CompiledExpression();
658
    }
659
660
    /**
661
     * @param Node\Expr\Variable $expr
662
     * @return CompiledExpression
663
     */
664 10
    protected function passExprVariable(Node\Expr\Variable $expr)
665
    {
666 10
        $variable = $this->context->getSymbol($expr->name);
667 10
        if ($variable) {
668 10
            $variable->incGets();
669 10
            return new CompiledExpression($variable->getType(), $variable->getValue(), $variable);
670
        }
671
672 1
        $this->context->notice(
673 1
            'undefined-variable',
674 1
            sprintf('You trying to use undefined variable $%s', $expr->name),
675
            $expr
676 1
        );
677
678 1
        return new CompiledExpression();
679
    }
680
681
    /**
682
     * Compile Array_ expression to CompiledExpression
683
     *
684
     * @param Node\Expr\Array_ $expr
685
     * @return CompiledExpression
686
     */
687 31
    protected function getArray(Node\Expr\Array_ $expr)
688
    {
689 31
        if ($expr->items === []) {
690 22
            return new CompiledExpression(CompiledExpression::ARR, []);
691
        }
692
693 10
        $resultArray = [];
694
695 10
        foreach ($expr->items as $item) {
696 10
            $compiledValueResult = $this->compile($item->value);
697 10
            if ($item->key) {
698 3
                $compiledKeyResult = $this->compile($item->key);
699 3
                switch ($compiledKeyResult->getType()) {
700 3
                    case CompiledExpression::INTEGER:
701 3
                    case CompiledExpression::DOUBLE:
702 3
                    case CompiledExpression::BOOLEAN:
703 3
                    case CompiledExpression::NULL:
704 3
                    case CompiledExpression::STRING:
705 3
                        $resultArray[$compiledKeyResult->getValue()] = $compiledValueResult->getValue();
706 3
                        break;
707 1
                    default:
708 1
                        $this->context->debug("Type {$compiledKeyResult->getType()} is not supported for key value");
709 1
                        return new CompiledExpression(CompiledExpression::ARR);
710
                        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...
711 3
                }
712 3
            } else {
713 7
                $resultArray[] = $compiledValueResult->getValue();
714
            }
715 10
        }
716
717 10
        return new CompiledExpression(CompiledExpression::ARR, $resultArray);
718
    }
719
720
    /**
721
     * Convert const fetch expr to CompiledExpression
722
     *
723
     * @param Node\Expr\ConstFetch $expr
724
     * @return CompiledExpression
725
     */
726 6
    protected function constFetch(Node\Expr\ConstFetch $expr)
727
    {
728 6
        if ($expr->name instanceof Node\Name) {
729 6
            if ($expr->name->parts[0] === 'true') {
730 5
                return new CompiledExpression(CompiledExpression::BOOLEAN, true);
731
            }
732
733 2
            if ($expr->name->parts[0] === 'false') {
734
                return new CompiledExpression(CompiledExpression::BOOLEAN, false);
735
            }
736 2
        }
737
738
        /**
739
         * @todo Implement check
740
         */
741 2
        return $this->compile($expr->name);
742
    }
743
}
744