Completed
Push — master ( cfa785...3f4fca )
by Дмитрий
02:53
created

Expression::passCastInt()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 17
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

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