Completed
Push — master ( afef11...c67701 )
by Дмитрий
05:51 queued 03:12
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 161 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 844
    public function __construct(Context $context, EventManager $eventManager)
37
    {
38 844
        $this->context = $context;
39 844
        $this->eventManager = $eventManager;
40 844
    }
41
42
    /**
43
     * @param $expr
44
     * @return ExpressionCompilerInterface|AbstractExpressionCompiler
45
     */
46 817
    protected function factory($expr)
47
    {
48 817
        switch (get_class($expr)) {
49
            /**
50
             * Call(s)
51
             */
52 817
            case Node\Expr\MethodCall::class:
53
                return new Expression\MethodCall();
54 817
            case Node\Expr\FuncCall::class:
55 10
                return new Expression\FunctionCall();
56 809
            case Node\Expr\StaticCall::class:
57
                return new Expression\StaticCall();
58
            /**
59
             * Operators
60
             */
61 809
            case Node\Expr\New_::class:
62 2
                return new Expression\Operators\NewOp();
63 807
            case Node\Expr\Instanceof_::class:
64
                return new Expression\Operators\InstanceOfOp();
65
            /**
66
             * AssignOp
67
             */
68 807
            case Node\Expr\AssignOp\Pow::class:
69 21
                return new Expression\AssignOp\Pow();
70 786
            case Node\Expr\AssignOp\Plus::class:
71 17
                return new Expression\AssignOp\Plus();
72 769
            case Node\Expr\AssignOp\Minus::class:
73 20
                return new Expression\AssignOp\Minus();
74 749
            case Node\Expr\AssignOp\Mul::class:
75 20
                return new Expression\AssignOp\Mul();
76 729
            case Node\Expr\AssignOp\Div::class:
77 18
                return new Expression\AssignOp\Div();
78 711
            case Node\Expr\AssignOp\Mod::class:
79 11
                return new Expression\AssignOp\Mod();
80 700
            case Node\Expr\AssignOp\BitwiseOr::class:
81 12
                return new Expression\AssignOp\BitwiseOr();
82 688
            case Node\Expr\AssignOp\BitwiseAnd::class:
83 12
                return new Expression\AssignOp\BitwiseAnd();
84 676
            case Node\Expr\AssignOp\BitwiseXor::class:
85 12
                return new Expression\AssignOp\BitwiseXor();
86 664
            case Node\Expr\AssignOp\Concat::class:
87 14
                return new Expression\AssignOp\Concat();
88 650
            case Node\Expr\AssignOp\ShiftLeft::class:
89 12
                return new Expression\AssignOp\ShiftLeft();
90 638
            case Node\Expr\AssignOp\ShiftRight::class:
91 12
                return new Expression\AssignOp\ShiftRight();
92
93
            /**
94
             * BinaryOp
95
             */
96 626
            case Node\Expr\BinaryOp\Identical::class:
97 28
                return new Expression\BinaryOp\Identical();
98 598
            case Node\Expr\BinaryOp\Concat::class:
99 15
                return new Expression\Operators\Concat();
100 583
            case Node\Expr\BinaryOp\NotIdentical::class:
101 14
                return new Expression\BinaryOp\NotIdentical();
102 569
            case Node\Expr\BinaryOp\Equal::class:
103 41
                return new Expression\BinaryOp\Equal();
104 530
            case Node\Expr\BinaryOp\NotEqual::class:
105 17
                return new Expression\BinaryOp\NotEqual();
106 513
            case Node\Expr\BinaryOp\Spaceship::class:
107 11
                return new Expression\BinaryOp\SpaceShip();
108 502
            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 499
            case Node\Expr\PostInc::class:
115 8
                return new Expression\Operators\PostInc();
116 491
            case Node\Expr\PostDec::class:
117 8
                return new Expression\Operators\PostDec();
118 483
            case Node\Expr\PreInc::class:
119 8
                return new Expression\Operators\PreInc();
120 475
            case Node\Expr\PreDec::class:
121 8
                return new Expression\Operators\PreDec();
122
            /**
123
             * Arithmetical
124
             */
125 467
            case Node\Expr\BinaryOp\Div::class:
126 37
                return new Expression\Operators\Arithmetical\Div();
127 430
            case Node\Expr\BinaryOp\Plus::class:
128 45
                return new Expression\Operators\Arithmetical\Plus();
129 385
            case Node\Expr\BinaryOp\Minus::class:
130 24
                return new Expression\Operators\Arithmetical\Minus();
131 361
            case Node\Expr\BinaryOp\Mul::class:
132 35
                return new Expression\Operators\Arithmetical\Mul();
133 326
            case Node\Expr\BinaryOp\Mod::class:
134 35
                return new Expression\Operators\Arithmetical\Mod();
135 291
            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 270
            case Node\Expr\BinaryOp\BitwiseOr::class:
143 12
                return new Expression\Operators\Bitwise\BitwiseOr();
144 258
            case Node\Expr\BinaryOp\BitwiseXor::class:
145 12
                return new Expression\Operators\Bitwise\BitwiseXor();
146 246
            case Node\Expr\BinaryOp\BitwiseAnd::class:
147 12
                return new Expression\Operators\Bitwise\BitwiseAnd();
148 234
            case Node\Expr\BinaryOp\ShiftRight::class:
149 12
                return new Expression\Operators\Bitwise\ShiftRight();
150 222
            case Node\Expr\BinaryOp\ShiftLeft::class:
151 12
                return new Expression\Operators\Bitwise\ShiftLeft();
152 210
            case Node\Expr\BitwiseNot::class:
153 5
                return new Expression\Operators\Bitwise\BitwiseNot();
154
            /**
155
             * Logical
156
             */
157 205
            case Node\Expr\BinaryOp\BooleanOr::class:
158 17
                return new Expression\Operators\Logical\BooleanOr();
159 188
            case Node\Expr\BinaryOp\BooleanAnd::class:
160 15
                return new Expression\Operators\Logical\BooleanAnd();
161 173
            case Node\Expr\BooleanNot::class:
162 9
                return new Expression\Operators\Logical\BooleanNot();
163 164
            case Node\Expr\BinaryOp\LogicalAnd::class:
164 15
                return new Expression\Operators\Logical\LogicalAnd();
165 149
            case Node\Expr\BinaryOp\LogicalOr::class:
166 17
                return new Expression\Operators\Logical\LogicalOr();
167 132
            case Node\Expr\BinaryOp\LogicalXor::class:
168 17
                return new Expression\Operators\Logical\LogicalXor();
169
170
            /**
171
             * Comparison
172
             */
173 115
            case Node\Expr\BinaryOp\Greater::class:
174 12
                return new Expression\Operators\Comparison\Greater();
175 103
            case Node\Expr\BinaryOp\GreaterOrEqual::class:
176 12
                return new Expression\Operators\Comparison\GreaterOrEqual();
177 91
            case Node\Expr\BinaryOp\Smaller::class:
178 12
                return new Expression\Operators\Comparison\Smaller();
179 79
            case Node\Expr\BinaryOp\SmallerOrEqual::class:
180 12
                return new Expression\Operators\Comparison\SmallerOrEqual();
181
182
            /**
183
             * Casts
184
             */
185 67
            case Node\Expr\Cast\Array_::class:
186 3
                return new Expression\Casts\ArrayCast();
187 65
            case Node\Expr\Cast\Bool_::class:
188 8
                return new Expression\Casts\BoolCast();
189 58
            case Node\Expr\Cast\Int_::class:
190 8
                return new Expression\Casts\IntCast();
191 51
            case Node\Expr\Cast\Double::class:
192 8
                return new Expression\Casts\DoubleCast();
193 44
            case Node\Expr\Cast\Object_::class:
194 2
                return new Expression\Casts\ObjectCast();
195 43
            case Node\Expr\Cast\String_::class:
196 7
                return new Expression\Casts\StringCast();
197 37
            case Node\Expr\Cast\Unset_::class:
198 2
                return new Expression\Casts\UnsetCast();
199
200
201
            /**
202
             * Other
203
             */
204 35
            case Node\Expr\Closure::class:
205
                return new Expression\Closure();
206 35
            case Node\Expr\UnaryMinus::class:
207 9
                return new Expression\Operators\UnaryMinus();
208 26
            case Node\Expr\UnaryPlus::class:
209 9
                return new Expression\Operators\UnaryPlus();
210 17
            case Node\Expr\Exit_::class:
211 1
                return new Expression\ExitOp();
212 16
            case Node\Expr\Isset_::class:
213 3
                return new Expression\IssetOp();
214 13
            case Node\Expr\Print_::class:
215 2
                return new Expression\PrintOp();
216 12
            case Node\Expr\Empty_::class:
217 5
                return new Expression\EmptyOp();
218 7
            case Node\Expr\Eval_::class:
219 2
                return new Expression\EvalOp();
220 5
            case Node\Expr\ErrorSuppress::class:
221 2
                return new Expression\ErrorSuppress();
222 3
            case Node\Expr\Clone_::class:
223 1
                return new Expression\CloneOp();
224 2
            case Node\Expr\Ternary::class:
225 2
                return new Expression\Ternary();
226
        }
227
228
        return false;
229
    }
230
231
    /**
232
     * @param object|string $expr
233
     * @throws InvalidArgumentException when $expr is not string/object/null
234
     * @throws RuntimeException when compiler class does not return a CompiledExpression
235
     * @return CompiledExpression
236
     */
237 844
    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...
238
    {
239 844
        if (is_string($expr)) {
240 13
            return new CompiledExpression(CompiledExpression::STRING, $expr);
241
        }
242
243 844
        if (is_null($expr)) {
244 8
            return new CompiledExpression(CompiledExpression::NULL);
245
        }
246
247 844
        if (!is_object($expr)) {
248
            throw new InvalidArgumentException('$expr must be string/object/null');
249
        }
250
251 844
        $this->eventManager->fire(
252 844
            ExpressionBeforeCompile::EVENT_NAME,
253 844
            new ExpressionBeforeCompile(
254 844
                $expr,
255 844
                $this->context
256 844
            )
257 844
        );
258
259 844
        $className = get_class($expr);
260
        switch ($className) {
261 844
            case Node\Arg::class:
262
                /**
263
                 * @todo Better compile
264
                 */
265 2
                return $this->compile($expr->value);
266 844
            case Node\Expr\PropertyFetch::class:
267
                return $this->passPropertyFetch($expr);
268 844
            case Node\Stmt\Property::class:
269
                return $this->passProperty($expr);
270 844
            case Node\Expr\ClassConstFetch::class:
271
                return $this->passConstFetch($expr);
272 844
            case Node\Expr\Assign::class:
273 26
                return $this->passSymbol($expr);
274 844
            case Node\Expr\AssignRef::class:
275 1
                return $this->passSymbolByRef($expr);
276 844
            case Node\Expr\Variable::class:
277 10
                return $this->passExprVariable($expr);
278
279
            /**
280
             * Expressions
281
             */
282 844
            case Node\Expr\Array_::class:
283 36
                return $this->getArray($expr);
284 843
            case Node\Expr\ConstFetch::class:
285 6
                return $this->constFetch($expr);
286 843
            case Node\Name::class:
287 24
                return $this->getNodeName($expr);
288 843
            case Node\Name\FullyQualified::class:
289
                return $this->getFullyQualifiedNodeName($expr);
290
291
            /**
292
             * Simple Scalar(s)
293
             */
294 843
            case \PHPSA\Node\Scalar\Nil::class:
295 25
                return new CompiledExpression(CompiledExpression::NULL);
296 842
            case Node\Scalar\LNumber::class:
297 508
                return new CompiledExpression(CompiledExpression::INTEGER, $expr->value);
298 825
            case Node\Scalar\DNumber::class:
299 212
                return new CompiledExpression(CompiledExpression::DOUBLE, $expr->value);
300 824
            case Node\Scalar\String_::class:
301 50
                return new CompiledExpression(CompiledExpression::STRING, $expr->value);
302 819
            case \PHPSA\Node\Scalar\Boolean::class:
303 241
                return new CompiledExpression(CompiledExpression::BOOLEAN, $expr->value);
304 817
            case \PHPSA\Node\Scalar\Fake::class:
305 79
                return new CompiledExpression($expr->type, $expr->value);
306
        }
307
308 817
        $expressionCompiler = $this->factory($expr);
309 817
        if (!$expressionCompiler) {
310
            $this->context->debug("Expression compiler is not implemented for {$className}");
311
            return new CompiledExpression(CompiledExpression::UNIMPLEMENTED);
312
        }
313
314 817
        $result = $expressionCompiler->pass($expr, $this->context);
315 817
        if (!$result instanceof CompiledExpression) {
316
            throw new RuntimeException('Please return CompiledExpression from ' . get_class($expressionCompiler));
317
        }
318
319 817
        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
     * @return CompiledExpression
392
     */
393 1
    public function declareVariable(Node\Expr\Variable $expr)
394
    {
395 1
        $variable = $this->context->getSymbol($expr->name);
396 1
        if (!$variable) {
397 1
            $variable = new Variable($expr->name, null, CompiledExpression::UNKNOWN, $this->context->getCurrentBranch());
0 ignored issues
show
Bug introduced by
It seems like $expr->name can also be of type object<PhpParser\Node\Expr>; however, PHPSA\Variable::__construct() does only seem to accept string, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
398 1
            $this->context->addVariable($variable);
399 1
        }
400
401 1
        return new CompiledExpression($variable->getType(), $variable->getValue(), $variable);
402
    }
403
404
    /**
405
     * @param Node\Name\FullyQualified $expr
406
     * @return CompiledExpression
407
     */
408
    public function getFullyQualifiedNodeName(Node\Name\FullyQualified $expr)
409
    {
410
        $this->context->debug('Unimplemented FullyQualified', $expr);
411
412
        return new CompiledExpression;
413
    }
414
415
    /**
416
     * @param Node\Name $expr
417
     * @return CompiledExpression
418
     */
419 24
    public function getNodeName(Node\Name $expr)
420
    {
421 24
        $nodeString = $expr->toString();
422 24
        if ($nodeString === 'null') {
423 1
            return new CompiledExpression(CompiledExpression::NULL);
424
        }
425
426 23
        if (in_array($nodeString, ['parent'], true)) {
427
            /** @var ClassDefinition $scope */
428
            $scope = $this->context->scope;
429
            assert($scope instanceof ClassDefinition);
430
431
            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...
432
                $definition = $scope->getExtendsClassDefinition();
433
                if ($definition) {
434
                    return new CompiledExpression(CompiledExpression::OBJECT, $definition);
435
                }
436
            } else {
437
                $this->context->notice(
438
                    'no-parent',
439
                    'Cannot access parent:: when current class scope has no parent',
440
                    $expr
441
                );
442
            }
443
        }
444
445 23
        if (in_array($nodeString, ['self', 'static'], true)) {
446
            return CompiledExpression::fromZvalValue($this->context->scope);
447
        }
448
449 23
        if (defined($nodeString)) {
450 1
            return CompiledExpression::fromZvalValue(constant($expr));
451
        }
452
453 23
        return new CompiledExpression(CompiledExpression::STRING, $expr->toString());
454
    }
455
456
    /**
457
     * @param Node\Expr\PropertyFetch $expr
458
     * @return CompiledExpression
459
     */
460
    protected function passPropertyFetch(Node\Expr\PropertyFetch $expr)
461
    {
462
        $propertNameCE = $this->compile($expr->name);
463
464
        $scopeExpression = $this->compile($expr->var);
465
        if ($scopeExpression->isObject()) {
466
            $scopeExpressionValue = $scopeExpression->getValue();
467
            if ($scopeExpressionValue instanceof ClassDefinition) {
468
                $propertyName = $propertNameCE->isString() ? $propertNameCE->getValue() : false;
469
                if ($propertyName) {
470
                    if ($scopeExpressionValue->hasProperty($propertyName, true)) {
471
                        $property = $scopeExpressionValue->getProperty($propertyName, true);
472
                        return $this->compile($property);
473
                    } else {
474
                        $this->context->notice(
475
                            'undefined-property',
476
                            sprintf(
477
                                'Property %s does not exist in %s scope',
478
                                $propertyName,
479
                                $scopeExpressionValue->getName()
480
                            ),
481
                            $expr
482
                        );
483
                    }
484
                }
485
            }
486
487
            return new CompiledExpression(CompiledExpression::UNKNOWN);
488
        } elseif (!$scopeExpression->canBeObject()) {
489
            return new CompiledExpression(CompiledExpression::UNKNOWN);
490
        }
491
492
        $this->context->notice(
493
            'property-fetch-on-non-object',
494
            "It's not possible to fetch a property on a non-object",
495
            $expr,
496
            Check::CHECK_BETA
497
        );
498
499
        return new CompiledExpression(CompiledExpression::UNKNOWN);
500
    }
501
502
    /**
503
     * @param Node\Expr\ClassConstFetch $expr
504
     * @return CompiledExpression
505
     */
506
    protected function passConstFetch(Node\Expr\ClassConstFetch $expr)
507
    {
508
        $leftCE = $this->compile($expr->class);
509
        if ($leftCE->isObject()) {
510
            $leftCEValue = $leftCE->getValue();
511
            if ($leftCEValue instanceof ClassDefinition) {
512
                if (!$leftCEValue->hasConst($expr->name, true)) {
513
                    $this->context->notice(
514
                        'undefined-const',
515
                        sprintf('Constant %s does not exist in %s scope', $expr->name, $expr->class),
516
                        $expr
517
                    );
518
                    return new CompiledExpression(CompiledExpression::UNKNOWN);
519
                }
520
521
                return new CompiledExpression();
522
            }
523
        }
524
525
        $this->context->debug('Unknown const fetch', $expr);
526
        return new CompiledExpression();
527
    }
528
529
    /**
530
     * @param Node\Expr\Assign $expr
531
     * @return CompiledExpression
532
     */
533 26
    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...
534
    {
535 26
        $compiledExpression = $this->compile($expr->expr);
536
537 26
        if ($expr->var instanceof Node\Expr\List_) {
538 1
            $isCorrectType = $compiledExpression->isArray();
539
540 1
            foreach ($expr->var->vars as $key => $var) {
541 1
                if (!$var instanceof Node\Expr\Variable) {
542 1
                    continue;
543
                }
544
545 1
                if ($var->name instanceof Node\Expr\Variable) {
546 1
                    $this->compileVariableDeclaration($this->compile($var->name), new CompiledExpression());
547 1
                    continue;
548
                }
549
550 1
                $symbol = $this->context->getSymbol($var->name);
551 1
                if (!$symbol) {
552 1
                    $symbol = new Variable(
553 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...
554 1
                        null,
555 1
                        CompiledExpression::UNKNOWN,
556 1
                        $this->context->getCurrentBranch()
557 1
                    );
558 1
                    $this->context->addVariable($symbol);
559 1
                }
560
561 1
                if (!$isCorrectType) {
562
                    $symbol->modify(CompiledExpression::NULL, null);
563
                }
564
565 1
                $symbol->incSets();
566 1
            }
567
568 1
            return new CompiledExpression();
569
        }
570
571 26
        if ($expr->var instanceof Node\Expr\Variable) {
572 26
            $this->compileVariableDeclaration($this->compile($expr->var->name), $compiledExpression);
573
574 26
            return $compiledExpression;
575
        }
576
577 2
        if ($expr->var instanceof Node\Expr\PropertyFetch) {
578 2
            $compiledExpression = $this->compile($expr->var->var);
579 2
            if ($compiledExpression->getType() == CompiledExpression::OBJECT) {
580 2
                $objectDefinition = $compiledExpression->getValue();
581 2
                if ($objectDefinition instanceof ClassDefinition) {
582 2
                    if (is_string($expr->var->name)) {
583 2
                        if ($objectDefinition->hasProperty($expr->var->name)) {
584
                            return $this->compile($objectDefinition->getProperty($expr->var->name));
585
                        }
586 2
                    }
587 2
                }
588 2
            }
589 2
        }
590
591 2
        $this->context->debug('Unknown how to pass symbol');
592 2
        return new CompiledExpression();
593
    }
594
595 26
    protected function compileVariableDeclaration(CompiledExpression $variableName, CompiledExpression $value)
596
    {
597 26
        switch ($variableName->getType()) {
598 26
            case CompiledExpression::STRING:
599 26
                break;
600
            default:
601
                $this->context->debug('Unexpected type of Variable name after compile');
602
                return new CompiledExpression();
603 26
        }
604
605 26
        $symbol = $this->context->getSymbol($variableName->getValue());
606 26
        if ($symbol) {
607 2
            $symbol->modify($value->getType(), $value->getValue());
608 2
            $this->context->modifyReferencedVariables(
609 2
                $symbol,
610 2
                $value->getType(),
611 2
                $value->getValue()
612 2
            );
613 2
        } else {
614 26
            $symbol = new Variable(
615 26
                $variableName->getValue(),
616 26
                $value->getValue(),
617 26
                $value->getType(),
618 26
                $this->context->getCurrentBranch()
619 26
            );
620 26
            $this->context->addVariable($symbol);
621
        }
622
623 26
        $symbol->incSets();
624 26
    }
625
626
    /**
627
     * @param Node\Expr\AssignRef $expr
628
     * @return CompiledExpression
629
     */
630 1
    protected function passSymbolByRef(Node\Expr\AssignRef $expr)
631
    {
632 1
        if ($expr->var instanceof Node\Expr\Variable) {
633 1
            $name = $expr->var->name;
634
635 1
            $compiledExpression = $this->compile($expr->expr);
636
637 1
            $symbol = $this->context->getSymbol($name);
638 1
            if ($symbol) {
639
                $symbol->modify($compiledExpression->getType(), $compiledExpression->getValue());
640
            } else {
641 1
                $symbol = new Variable(
642 1
                    $name,
0 ignored issues
show
Bug introduced by
It seems like $name defined by $expr->var->name on line 633 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...
643 1
                    $compiledExpression->getValue(),
644 1
                    $compiledExpression->getType(),
645 1
                    $this->context->getCurrentBranch()
646 1
                );
647 1
                $this->context->addVariable($symbol);
648
            }
649
650 1
            if ($expr->expr instanceof Node\Expr\Variable) {
651 1
                $rightVarName = $expr->expr->name;
652
653 1
                $rightSymbol = $this->context->getSymbol($rightVarName);
654 1
                if ($rightSymbol) {
655 1
                    $rightSymbol->incUse();
656 1
                    $symbol->setReferencedTo($rightSymbol);
657 1
                } else {
658
                    $this->context->debug('Cannot fetch variable by name: ' . $rightVarName);
659
                }
660 1
            }
661
662 1
            $symbol->incSets();
663 1
            return $compiledExpression;
664
        }
665
666
        $this->context->debug('Unknown how to pass symbol by ref');
667
        return new CompiledExpression();
668
    }
669
670
    /**
671
     * @param Node\Expr\Variable $expr
672
     * @return CompiledExpression
673
     */
674 10
    protected function passExprVariable(Node\Expr\Variable $expr)
675
    {
676 10
        $variable = $this->context->getSymbol($expr->name);
677 10
        if ($variable) {
678 10
            $variable->incGets();
679 10
            return new CompiledExpression($variable->getType(), $variable->getValue(), $variable);
680
        }
681
682 1
        $this->context->notice(
683 1
            'undefined-variable',
684 1
            sprintf('You are trying to use an undefined variable $%s', $expr->name),
685
            $expr
686 1
        );
687
688 1
        return new CompiledExpression();
689
    }
690
691
    /**
692
     * Compile Array_ expression to CompiledExpression
693
     *
694
     * @param Node\Expr\Array_ $expr
695
     * @return CompiledExpression
696
     */
697 36
    protected function getArray(Node\Expr\Array_ $expr)
698
    {
699 36
        if ($expr->items === []) {
700 27
            return new CompiledExpression(CompiledExpression::ARR, []);
701
        }
702
703 11
        $resultArray = [];
704
705 11
        foreach ($expr->items as $item) {
706 11
            $compiledValueResult = $this->compile($item->value);
707 11
            if ($item->key) {
708 3
                $compiledKeyResult = $this->compile($item->key);
709 3
                switch ($compiledKeyResult->getType()) {
710 3
                    case CompiledExpression::INTEGER:
711 3
                    case CompiledExpression::DOUBLE:
712 3
                    case CompiledExpression::BOOLEAN:
713 3
                    case CompiledExpression::NULL:
714 3
                    case CompiledExpression::STRING:
715 3
                        $resultArray[$compiledKeyResult->getValue()] = $compiledValueResult->getValue();
716 3
                        break;
717 1
                    default:
718 1
                        $this->context->debug("Type {$compiledKeyResult->getType()} is not supported for key value");
719 1
                        return new CompiledExpression(CompiledExpression::ARR);
720 3
                }
721 3
            } else {
722 8
                $resultArray[] = $compiledValueResult->getValue();
723
            }
724 11
        }
725
726 11
        return new CompiledExpression(CompiledExpression::ARR, $resultArray);
727
    }
728
729
    /**
730
     * Convert const fetch expr to CompiledExpression
731
     *
732
     * @param Node\Expr\ConstFetch $expr
733
     * @return CompiledExpression
734
     */
735 6
    protected function constFetch(Node\Expr\ConstFetch $expr)
736
    {
737 6
        if ($expr->name instanceof Node\Name) {
738 6
            if ($expr->name->parts[0] === 'true') {
739 5
                return new CompiledExpression(CompiledExpression::BOOLEAN, true);
740
            }
741
742 2
            if ($expr->name->parts[0] === 'false') {
743
                return new CompiledExpression(CompiledExpression::BOOLEAN, false);
744
            }
745 2
        }
746
747
        /**
748
         * @todo Implement check
749
         */
750 2
        return $this->compile($expr->name);
751
    }
752
}
753