Completed
Pull Request — master (#116)
by Enrico
04:25
created

Expression::passCastUnset()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

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