Completed
Pull Request — master (#149)
by Enrico
04:04
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 155 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 745
    public function __construct(Context $context, EventManager $eventManager)
36
    {
37 745
        $this->context = $context;
38 745
        $this->eventManager = $eventManager;
39 745
    }
40
41
    /**
42
     * @param $expr
43
     * @return ExpressionCompilerInterface|AbstractExpressionCompiler
44
     */
45 727
    protected function factory($expr)
46
    {
47 727
        switch (get_class($expr)) {
48
            /**
49
             * Call(s)
50
             */
51 727
            case Node\Expr\MethodCall::class:
52
                return new Expression\MethodCall();
53 727
            case Node\Expr\FuncCall::class:
54 10
                return new Expression\FunctionCall();
55 719
            case Node\Expr\StaticCall::class:
56
                return new Expression\StaticCall();
57
            /**
58
             * Operators
59
             */
60 719
            case Node\Expr\New_::class:
61 2
                return new Expression\Operators\NewOp();
62 717
            case Node\Expr\Instanceof_::class:
63
                return new Expression\Operators\InstanceOfOp();
64
            /**
65
             * AssignOp
66
             */
67 717
            case Node\Expr\AssignOp\Pow::class:
68 21
                return new Expression\AssignOp\Pow();
69 696
            case Node\Expr\AssignOp\Plus::class:
70 17
                return new Expression\AssignOp\Plus();
71 679
            case Node\Expr\AssignOp\Minus::class:
72 20
                return new Expression\AssignOp\Minus();
73 659
            case Node\Expr\AssignOp\Mul::class:
74 20
                return new Expression\AssignOp\Mul();
75 639
            case Node\Expr\AssignOp\Div::class:
76 18
                return new Expression\AssignOp\Div();
77 621
            case Node\Expr\AssignOp\Mod::class:
78 11
                return new Expression\AssignOp\Mod();
79 610
            case Node\Expr\AssignOp\BitwiseOr::class:
80 12
                return new Expression\AssignOp\BitwiseOr();
81 598
            case Node\Expr\AssignOp\BitwiseAnd::class:
82 12
                return new Expression\AssignOp\BitwiseAnd();
83 586
            case Node\Expr\AssignOp\BitwiseXor::class:
84 12
                return new Expression\AssignOp\BitwiseXor();
85 574
            case Node\Expr\AssignOp\Concat::class:
86 14
                return new Expression\AssignOp\Concat();
87 560
            case Node\Expr\AssignOp\ShiftLeft::class:
88 12
                return new Expression\AssignOp\ShiftLeft();
89 548
            case Node\Expr\AssignOp\ShiftRight::class:
90 12
                return new Expression\AssignOp\ShiftRight();
91
92
            /**
93
             * BinaryOp
94
             */
95 536
            case Node\Expr\BinaryOp\Identical::class:
96 28
                return new Expression\BinaryOp\Identical();
97 508
            case Node\Expr\BinaryOp\Concat::class:
98 1
                return new Expression\Operators\Concat();
99 507
            case Node\Expr\BinaryOp\NotIdentical::class:
100 14
                return new Expression\BinaryOp\NotIdentical();
101 493
            case Node\Expr\BinaryOp\Equal::class:
102 34
                return new Expression\BinaryOp\Equal();
103 459
            case Node\Expr\BinaryOp\NotEqual::class:
104 17
                return new Expression\BinaryOp\NotEqual();
105 442
            case Node\Expr\BinaryOp\Spaceship::class:
106 11
                return new Expression\BinaryOp\SpaceShip();
107
                
108
            /**
109
             * @link http://php.net/manual/en/language.operators.increment.php
110
             */
111 431
            case Node\Expr\PostInc::class:
112 8
                return new Expression\Operators\PostInc();
113 423
            case Node\Expr\PostDec::class:
114 8
                return new Expression\Operators\PostDec();
115 415
            case Node\Expr\PreInc::class:
116 8
                return new Expression\Operators\PreInc();
117 407
            case Node\Expr\PreDec::class:
118 8
                return new Expression\Operators\PreDec();
119
            /**
120
             * Arithmetical
121
             */
122 399
            case Node\Expr\BinaryOp\Div::class:
123 37
                return new Expression\Operators\Arithmetical\Div();
124 362
            case Node\Expr\BinaryOp\Plus::class:
125 45
                return new Expression\Operators\Arithmetical\Plus();
126 317
            case Node\Expr\BinaryOp\Minus::class:
127 18
                return new Expression\Operators\Arithmetical\Minus();
128 299
            case Node\Expr\BinaryOp\Mul::class:
129 35
                return new Expression\Operators\Arithmetical\Mul();
130 264
            case Node\Expr\BinaryOp\Mod::class:
131 35
                return new Expression\Operators\Arithmetical\Mod();
132 229
            case Node\Expr\BinaryOp\Pow::class:
133 21
                return new Expression\Operators\Arithmetical\Pow();
134
135
            /**
136
             * Bitwise
137
             * @link http://php.net/manual/ru/language.operators.bitwise.php
138
             */
139 208
            case Node\Expr\BinaryOp\BitwiseOr::class:
140 12
                return new Expression\Operators\Bitwise\BitwiseOr();
141 196
            case Node\Expr\BinaryOp\BitwiseXor::class:
142 12
                return new Expression\Operators\Bitwise\BitwiseXor();
143 184
            case Node\Expr\BinaryOp\BitwiseAnd::class:
144 12
                return new Expression\Operators\Bitwise\BitwiseAnd();
145 172
            case Node\Expr\BinaryOp\ShiftRight::class:
146 12
                return new Expression\Operators\Bitwise\ShiftRight();
147 160
            case Node\Expr\BinaryOp\ShiftLeft::class:
148 12
                return new Expression\Operators\Bitwise\ShiftLeft();
149 148
            case Node\Expr\BitwiseNot::class:
150 5
                return new Expression\Operators\Bitwise\BitwiseNot();
151
            /**
152
             * Logical
153
             */
154 143
            case Node\Expr\BinaryOp\BooleanOr::class:
155 10
                return new Expression\Operators\Logical\BooleanOr();
156 133
            case Node\Expr\BinaryOp\BooleanAnd::class:
157 5
                return new Expression\Operators\Logical\BooleanAnd();
158 128
            case Node\Expr\BooleanNot::class:
159 5
                return new Expression\Operators\Logical\BooleanNot();
160 123
            case Node\Expr\BinaryOp\LogicalAnd::class:
161 15
                return new Expression\Operators\Logical\LogicalAnd();
162 108
            case Node\Expr\BinaryOp\LogicalOr::class:
163 17
                return new Expression\Operators\Logical\LogicalOr();
164 91
            case Node\Expr\BinaryOp\LogicalXor::class:
165 17
                return new Expression\Operators\Logical\LogicalXor();
166
167
            /**
168
             * Comparison
169
             */
170 74
            case Node\Expr\BinaryOp\Greater::class:
171 12
                return new Expression\Operators\Comparison\Greater();
172 62
            case Node\Expr\BinaryOp\GreaterOrEqual::class:
173 12
                return new Expression\Operators\Comparison\GreaterOrEqual();
174 50
            case Node\Expr\BinaryOp\Smaller::class:
175 12
                return new Expression\Operators\Comparison\Smaller();
176 38
            case Node\Expr\BinaryOp\SmallerOrEqual::class:
177 12
                return new Expression\Operators\Comparison\SmallerOrEqual();
178
179
            /**
180
             * Casts
181
             */
182 26
            case Node\Expr\Cast\Array_::class:
183 1
                return new Expression\Casts\ArrayCast();
184 26
            case Node\Expr\Cast\Bool_::class:
185 1
                return new Expression\Casts\BoolCast();
186 26
            case Node\Expr\Cast\Int_::class:
187 1
                return new Expression\Casts\IntCast();
188 26
            case Node\Expr\Cast\Double::class:
189 1
                return new Expression\Casts\DoubleCast();
190 26
            case Node\Expr\Cast\Object_::class:
191 1
                return new Expression\Casts\ObjectCast();
192 26
            case Node\Expr\Cast\String_::class:
193 1
                return new Expression\Casts\StringCast();
194 26
            case Node\Expr\Cast\Unset_::class:
195 1
                return new Expression\Casts\UnsetCast();
196
197
198
            /**
199
             * Other
200
             */
201 25
            case Node\Expr\Closure::class:
202
                return new Expression\Closure();
203 25
            case Node\Expr\UnaryMinus::class:
204 9
                return new Expression\Operators\UnaryMinus();
205 16
            case Node\Expr\UnaryPlus::class:
206 9
                return new Expression\Operators\UnaryPlus();
207 7
            case Node\Expr\Exit_::class:
208 1
                return new Expression\ExitOp();
209 6
            case Node\Expr\Isset_::class:
210 3
                return new Expression\IssetOp();
211 3
            case Node\Expr\Print_::class:
212 1
                return new Expression\PrintOp();
213 2
        }
214
215 2
        return false;
216
    }
217
218
    /**
219
     * @param object|string $expr
220
     * @return CompiledExpression
221
     */
222 745
    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...
223
    {
224 745
        if (is_string($expr)) {
225 12
            return new CompiledExpression(CompiledExpression::STRING, $expr);
226
        }
227
228 745
        if (is_null($expr)) {
229 7
            return new CompiledExpression(CompiledExpression::NULL);
230
        }
231
232 745
        if (!is_object($expr)) {
233
            throw new InvalidArgumentException('$expr must be string/object/null');
234
        }
235
236 745
        $this->eventManager->fire(
237 745
            ExpressionBeforeCompile::EVENT_NAME,
238 745
            new ExpressionBeforeCompile(
239 745
                $expr,
240 745
                $this->context
241 745
            )
242 745
        );
243
244 745
        $className = get_class($expr);
245
        switch ($className) {
246 745
            case Node\Arg::class:
247
                /**
248
                 * @todo Better compile
249
                 */
250 2
                return $this->compile($expr->value);
251 745
            case Node\Expr\PropertyFetch::class:
252
                return $this->passPropertyFetch($expr);
253 745
            case Node\Stmt\Property::class:
254
                return $this->passProperty($expr);
255 745
            case Node\Expr\ClassConstFetch::class:
256
                return $this->passConstFetch($expr);
257 745
            case Node\Expr\Assign::class:
258 12
                return $this->passSymbol($expr);
259 745
            case Node\Expr\AssignRef::class:
260 1
                return $this->passSymbolByRef($expr);
261 745
            case Node\Expr\Variable::class:
262 8
                return $this->passExprVariable($expr);
263
264
            /**
265
             * Expressions
266
             */
267 745
            case Node\Expr\Array_::class:
268 26
                return $this->getArray($expr);
269 744
            case Node\Expr\ConstFetch::class:
270 6
                return $this->constFetch($expr);
271 744
            case Node\Name::class:
272 11
                return $this->getNodeName($expr);
273 744
            case Node\Name\FullyQualified::class:
274
                return $this->getFullyQualifiedNodeName($expr);
275
276
            /**
277
             * Simple Scalar(s)
278
             */
279 744
            case \PHPSA\Node\Scalar\Nil::class:
280 21
                return new CompiledExpression(CompiledExpression::NULL);
281 743
            case Node\Scalar\LNumber::class:
282 468
                return new CompiledExpression(CompiledExpression::INTEGER, $expr->value);
283 735
            case Node\Scalar\DNumber::class:
284 197
                return new CompiledExpression(CompiledExpression::DOUBLE, $expr->value);
285 734
            case Node\Scalar\String_::class:
286 31
                return new CompiledExpression(CompiledExpression::STRING, $expr->value);
287 729
            case \PHPSA\Node\Scalar\Boolean::class:
288 217
                return new CompiledExpression(CompiledExpression::BOOLEAN, $expr->value);
289 727
            case \PHPSA\Node\Scalar\Fake::class:
290 71
                return new CompiledExpression($expr->type, $expr->value);
291
        }
292
293 727
        $expressionCompiler = $this->factory($expr);
294 727
        if (!$expressionCompiler) {
295 2
            $this->context->debug("Expression compiler is not implemented for {$className}");
296 2
            return new CompiledExpression(CompiledExpression::UNIMPLEMENTED);
297
        }
298
299 726
        $result = $expressionCompiler->pass($expr, $this->context);
300 726
        if (!$result instanceof CompiledExpression) {
301
            throw new RuntimeException('Please return CompiledExpression from ' . get_class($expressionCompiler));
302
        }
303
304 726
        return $result;
305
    }
306
307
    /**
308
     * @todo Implement
309
     *
310
     * @param Node\Stmt\Property $st
311
     * @return CompiledExpression
312
     */
313
    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...
314
    {
315
        $docBlock = $st->getDocComment();
316
        if (!$docBlock) {
317
            $this->context->notice(
318
                'missing-docblock',
319
                sprintf('Missing docblock for $%s property', $st->props[0]->name),
320
                $st
321
            );
322
323
            return new CompiledExpression();
324
        }
325
326
        $phpdoc = new \phpDocumentor\Reflection\DocBlock($docBlock->getText());
327
328
        $varTags = $phpdoc->getTagsByName('var');
329
        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...
330
            /** @var \phpDocumentor\Reflection\DocBlock\Tag\VarTag $varTag */
331
            $varTag = current($varTags);
332
333
            $typeResolver = new \phpDocumentor\Reflection\TypeResolver();
334
335
            try {
336
                $type = $typeResolver->resolve($varTag->getType());
337
            } catch (\InvalidArgumentException $e) {
338
                return new CompiledExpression();
339
            }
340
341
            if ($type) {
342
                switch (get_class($type)) {
343
                    case \phpDocumentor\Reflection\Types\Object_::class:
344
                        return new CompiledExpression(
345
                            CompiledExpression::OBJECT
346
                        );
347
                    case \phpDocumentor\Reflection\Types\Integer::class:
348
                        return new CompiledExpression(
349
                            CompiledExpression::INTEGER
350
                        );
351
                    case \phpDocumentor\Reflection\Types\String_::class:
352
                        return new CompiledExpression(
353
                            CompiledExpression::STRING
354
                        );
355
                    case \phpDocumentor\Reflection\Types\Float_::class:
356
                        return new CompiledExpression(
357
                            CompiledExpression::DOUBLE
358
                        );
359
                    case \phpDocumentor\Reflection\Types\Null_::class:
360
                        return new CompiledExpression(
361
                            CompiledExpression::NULL
362
                        );
363
                    case \phpDocumentor\Reflection\Types\Boolean::class:
364
                        return new CompiledExpression(
365
                            CompiledExpression::BOOLEAN
366
                        );
367
                }
368
            }
369
        }
370
371
        return new CompiledExpression();
372
    }
373
374
    /**
375
     * @param Node\Expr\Variable $expr
376
     * @return CompiledExpression
377
     */
378 1
    public function declareVariable(Node\Expr\Variable $expr)
379
    {
380 1
        $variable = $this->context->getSymbol($expr->name);
381 1
        if (!$variable) {
382
            $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...
383
            $this->context->addVariable($variable);
384
        }
385
386 1
        return new CompiledExpression($variable->getType(), $variable->getValue(), $variable);
387
    }
388
389
    /**
390
     * @param Node\Name\FullyQualified $expr
391
     * @return CompiledExpression
392
     */
393
    public function getFullyQualifiedNodeName(Node\Name\FullyQualified $expr)
394
    {
395
        $this->context->debug('Unimplemented FullyQualified', $expr);
396
397
        return new CompiledExpression;
398
    }
399
400
    /**
401
     * @param Node\Name $expr
402
     * @return CompiledExpression
403
     */
404 11
    public function getNodeName(Node\Name $expr)
405
    {
406 11
        $nodeString = $expr->toString();
407 11
        if ($nodeString === 'null') {
408 1
            return new CompiledExpression(CompiledExpression::NULL);
409
        }
410
411 10
        if (in_array($nodeString, ['parent'], true)) {
412
            /** @var ClassDefinition $scope */
413
            $scope = $this->context->scope;
414
            assert($scope instanceof ClassDefinition);
415
416
            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...
417
                $definition = $scope->getExtendsClassDefinition();
418
                if ($definition) {
419
                    return new CompiledExpression(CompiledExpression::OBJECT, $definition);
420
                }
421
            } else {
422
                $this->context->notice(
423
                    'no-parent',
424
                    'Cannot access parent:: when current class scope has no parent',
425
                    $expr
426
                );
427
            }
428
        }
429
430 10
        if (in_array($nodeString, ['self', 'static'], true)) {
431
            return CompiledExpression::fromZvalValue($this->context->scope);
432
        }
433
434 10
        if (defined($nodeString)) {
435 1
            return CompiledExpression::fromZvalValue(constant($expr));
436
        }
437
438 10
        return new CompiledExpression(CompiledExpression::STRING, $expr->toString());
439
    }
440
441
    /**
442
     * @param Node\Expr\PropertyFetch $expr
443
     * @return CompiledExpression
444
     */
445
    protected function passPropertyFetch(Node\Expr\PropertyFetch $expr)
446
    {
447
        $propertNameCE = $this->compile($expr->name);
448
449
        $scopeExpression = $this->compile($expr->var);
450
        if ($scopeExpression->isObject()) {
451
            $scopeExpressionValue = $scopeExpression->getValue();
452
            if ($scopeExpressionValue instanceof ClassDefinition) {
453
                $propertyName = $propertNameCE->isString() ? $propertNameCE->getValue() : false;
454
                if ($propertyName) {
455
                    if ($scopeExpressionValue->hasProperty($propertyName, true)) {
456
                        $property = $scopeExpressionValue->getProperty($propertyName, true);
457
                        return $this->compile($property);
458
                    } else {
459
                        $this->context->notice(
460
                            'undefined-property',
461
                            sprintf(
462
                                'Property %s does not exist in %s scope',
463
                                $propertyName,
464
                                $scopeExpressionValue->getName()
465
                            ),
466
                            $expr
467
                        );
468
                    }
469
                }
470
            }
471
472
            return new CompiledExpression(CompiledExpression::UNKNOWN);
473
        } elseif (!$scopeExpression->canBeObject()) {
474
            return new CompiledExpression(CompiledExpression::UNKNOWN);
475
        }
476
477
        $this->context->notice(
478
            'property-fetch-on-non-object',
479
            "It's not possible to fetch property on not object",
480
            $expr,
481
            Check::CHECK_BETA
482
        );
483
484
        return new CompiledExpression(CompiledExpression::UNKNOWN);
485
    }
486
487
    /**
488
     * @param Node\Expr\ClassConstFetch $expr
489
     * @return CompiledExpression
490
     */
491
    protected function passConstFetch(Node\Expr\ClassConstFetch $expr)
492
    {
493
        $leftCE = $this->compile($expr->class);
494
        if ($leftCE->isObject()) {
495
            $leftCEValue = $leftCE->getValue();
496
            if ($leftCEValue instanceof ClassDefinition) {
497
                if (!$leftCEValue->hasConst($expr->name, true)) {
498
                    $this->context->notice(
499
                        'undefined-const',
500
                        sprintf('Constant %s does not exist in %s scope', $expr->name, $expr->class),
501
                        $expr
502
                    );
503
                    return new CompiledExpression(CompiledExpression::UNKNOWN);
504
                }
505
506
                return new CompiledExpression();
507
            }
508
        }
509
510
        $this->context->debug('Unknown const fetch', $expr);
511
        return new CompiledExpression();
512
    }
513
514
    /**
515
     * @param Node\Expr\Assign $expr
516
     * @return CompiledExpression
517
     */
518 12
    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...
519
    {
520 12
        $compiledExpression = $this->compile($expr->expr);
521
522 12
        if ($expr->var instanceof Node\Expr\List_) {
523
            $isCorrectType = false;
524
525
            switch ($compiledExpression->getType()) {
526
                case CompiledExpression::ARR:
527
                    $isCorrectType = true;
528
                    break;
529
            }
530
531
            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...
532
                foreach ($expr->var->vars as $key => $var) {
533
                    if ($var instanceof Node\Expr\Variable) {
534
                        $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...
535
536
                        $symbol = $this->context->getSymbol($name);
537
                        if (!$symbol) {
538
                            $symbol = new Variable(
539
                                $name,
0 ignored issues
show
Bug introduced by
It seems like $name defined by $expr->var->name on line 534 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...
540
                                null,
541
                                CompiledExpression::UNKNOWN,
542
                                $this->context->getCurrentBranch()
543
                            );
544
                            $this->context->addVariable($symbol);
545
                        }
546
547
                        if (!$isCorrectType) {
548
                            $symbol->modify(CompiledExpression::NULL, null);
549
                        }
550
551
                        $symbol->incSets();
552
                    }
553
                }
554
            }
555
556
            return new CompiledExpression();
557
        }
558
559
560 12
        if ($expr->var instanceof Node\Expr\Variable) {
561 12
            $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...
562 12
            switch ($compiledExpressionName->getType()) {
563 12
                case CompiledExpression::STRING:
564 12
                    break;
565
                default:
566
                    $this->context->debug('Unexpected type of Variable name after compile');
567
                    return new CompiledExpression();
568 12
            }
569
570 12
            $symbol = $this->context->getSymbol($compiledExpressionName->getValue());
571 12
            if ($symbol) {
572 2
                $symbol->modify($compiledExpression->getType(), $compiledExpression->getValue());
573 2
                $this->context->modifyReferencedVariables(
574 2
                    $symbol,
575 2
                    $compiledExpression->getType(),
576 2
                    $compiledExpression->getValue()
577 2
                );
578 2
            } else {
579 12
                $symbol = new Variable(
580 12
                    $compiledExpressionName->getValue(),
581 12
                    $compiledExpression->getValue(),
582 12
                    $compiledExpression->getType(),
583 12
                    $this->context->getCurrentBranch()
584 12
                );
585 12
                $this->context->addVariable($symbol);
586
            }
587
588 12
            $symbol->incSets();
589 12
            return $compiledExpression;
590
        }
591
592 1
        if ($expr->var instanceof Node\Expr\PropertyFetch) {
593 1
            $compiledExpression = $this->compile($expr->var->var);
594 1
            if ($compiledExpression->getType() == CompiledExpression::OBJECT) {
595 1
                $objectDefinition = $compiledExpression->getValue();
596 1
                if ($objectDefinition instanceof ClassDefinition) {
597 1
                    if (is_string($expr->var->name)) {
598 1
                        if ($objectDefinition->hasProperty($expr->var->name)) {
599
                            return $this->compile($objectDefinition->getProperty($expr->var->name));
600
                        }
601 1
                    }
602 1
                }
603 1
            }
604 1
        }
605
606 1
        $this->context->debug('Unknown how to pass symbol');
607 1
        return new CompiledExpression();
608
    }
609
610
    /**
611
     * @param Node\Expr\AssignRef $expr
612
     * @return CompiledExpression
613
     */
614 1
    protected function passSymbolByRef(Node\Expr\AssignRef $expr)
615
    {
616 1
        if ($expr->var instanceof Node\Expr\Variable) {
617 1
            $name = $expr->var->name;
618
619 1
            $compiledExpression = $this->compile($expr->expr);
620
621 1
            $symbol = $this->context->getSymbol($name);
622 1
            if ($symbol) {
623
                $symbol->modify($compiledExpression->getType(), $compiledExpression->getValue());
624
            } else {
625 1
                $symbol = new Variable(
626 1
                    $name,
0 ignored issues
show
Bug introduced by
It seems like $name defined by $expr->var->name on line 617 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...
627 1
                    $compiledExpression->getValue(),
628 1
                    $compiledExpression->getType(),
629 1
                    $this->context->getCurrentBranch()
630 1
                );
631 1
                $this->context->addVariable($symbol);
632
            }
633
634 1
            if ($expr->expr instanceof Node\Expr\Variable) {
635 1
                $rightVarName = $expr->expr->name;
636
637 1
                $rightSymbol = $this->context->getSymbol($rightVarName);
638 1
                if ($rightSymbol) {
639 1
                    $rightSymbol->incUse();
640 1
                    $symbol->setReferencedTo($rightSymbol);
641 1
                } else {
642
                    $this->context->debug('Cannot fetch variable by name: ' . $rightVarName);
643
                }
644 1
            }
645
646 1
            $symbol->incSets();
647 1
            return $compiledExpression;
648
        }
649
650
        $this->context->debug('Unknown how to pass symbol by ref');
651
        return new CompiledExpression();
652
    }
653
654
    /**
655
     * @param Node\Expr\Variable $expr
656
     * @return CompiledExpression
657
     */
658 8
    protected function passExprVariable(Node\Expr\Variable $expr)
659
    {
660 8
        $variable = $this->context->getSymbol($expr->name);
661 8
        if ($variable) {
662 8
            $variable->incGets();
663 8
            return new CompiledExpression($variable->getType(), $variable->getValue(), $variable);
664
        }
665
666
        $this->context->notice(
667
            'undefined-variable',
668
            sprintf('You trying to use undefined variable $%s', $expr->name),
669
            $expr
670
        );
671
672
        return new CompiledExpression();
673
    }
674
675
    /**
676
     * Compile Array_ expression to CompiledExpression
677
     *
678
     * @param Node\Expr\Array_ $expr
679
     * @return CompiledExpression
680
     */
681 26
    protected function getArray(Node\Expr\Array_ $expr)
682
    {
683 26
        if ($expr->items === []) {
684 17
            return new CompiledExpression(CompiledExpression::ARR, []);
685
        }
686
687 10
        $resultArray = [];
688
689 10
        foreach ($expr->items as $item) {
690 10
            $compiledValueResult = $this->compile($item->value);
691 10
            if ($item->key) {
692 3
                $compiledKeyResult = $this->compile($item->key);
693 3
                switch ($compiledKeyResult->getType()) {
694 3
                    case CompiledExpression::INTEGER:
695 3
                    case CompiledExpression::DOUBLE:
696 3
                    case CompiledExpression::BOOLEAN:
697 3
                    case CompiledExpression::NULL:
698 3
                    case CompiledExpression::STRING:
699 3
                        $resultArray[$compiledKeyResult->getValue()] = $compiledValueResult->getValue();
700 3
                        break;
701 1
                    default:
702 1
                        $this->context->debug("Type {$compiledKeyResult->getType()} is not supported for key value");
703 1
                        return new CompiledExpression(CompiledExpression::ARR);
704
                        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...
705 3
                }
706 3
            } else {
707 7
                $resultArray[] = $compiledValueResult->getValue();
708
            }
709 10
        }
710
711 10
        return new CompiledExpression(CompiledExpression::ARR, $resultArray);
712
    }
713
714
    /**
715
     * Convert const fetch expr to CompiledExpression
716
     *
717
     * @param Node\Expr\ConstFetch $expr
718
     * @return CompiledExpression
719
     */
720 6
    protected function constFetch(Node\Expr\ConstFetch $expr)
721
    {
722 6
        if ($expr->name instanceof Node\Name) {
723 6
            if ($expr->name->parts[0] === 'true') {
724 5
                return new CompiledExpression(CompiledExpression::BOOLEAN, true);
725
            }
726
727 2
            if ($expr->name->parts[0] === 'false') {
728
                return new CompiledExpression(CompiledExpression::BOOLEAN, false);
729
            }
730 2
        }
731
732
        /**
733
         * @todo Implement check
734
         */
735 2
        return $this->compile($expr->name);
736
    }
737
}
738