Completed
Push — master ( 28368b...f9f779 )
by Дмитрий
05:23 queued 02:40
created

Expression::passSymbolByRef()   B

Complexity

Conditions 5
Paths 7

Size

Total Lines 39
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 23
CRAP Score 5.1424

Importance

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