Completed
Pull Request — master (#196)
by Enrico
05:40
created

Expression::getArray()   D

Complexity

Conditions 9
Paths 9

Size

Total Lines 31
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 24
CRAP Score 9

Importance

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