Completed
Push — master ( e7b93c...96fb42 )
by Дмитрий
04:38 queued 01:52
created

Expression::passCastBoolean()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 18
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 18
ccs 0
cts 14
cp 0
rs 8.8571
cc 6
eloc 13
nc 6
nop 1
crap 42
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\Context;
12
use PhpParser\Node;
13
use PHPSA\Definition\ClassDefinition;
14
use PHPSA\Exception\RuntimeException;
15
use PHPSA\Variable;
16
use PHPSA\Compiler\Expression\AbstractExpressionCompiler;
17
18
class Expression
0 ignored issues
show
Complexity introduced by
This class has a complexity of 152 which exceeds the configured maximum of 50.

The class complexity is the sum of the complexity of all methods. A very high value is usually an indication that your class does not follow the single reponsibility principle and does more than one job.

Some resources for further reading:

You can also find more detailed suggestions for refactoring in the “Code” section of your repository.

Loading history...
19
{
20
    /**
21
     * @var Context
22
     */
23
    protected $context;
24
25
    /**
26
     * @param Context $context
27
     */
28 363
    public function __construct(Context $context)
29
    {
30 363
        $this->context = $context;
31 363
    }
32
33
    /**
34
     * @param $expr
35
     * @return ExpressionCompilerInterface|AbstractExpressionCompiler
36
     */
37 353
    protected function factory($expr)
38
    {
39 353
        switch (get_class($expr)) {
40
            /**
41
             * Call(s)
42
             */
43 353
            case Node\Expr\MethodCall::class:
44
                return new Expression\MethodCall();
45 353
            case Node\Expr\FuncCall::class:
46
                return new Expression\FunctionCall();
47 353
            case Node\Expr\StaticCall::class:
48
                return new Expression\StaticCall();
49
            /**
50
             * Operators
51
             */
52 353
            case Node\Expr\New_::class:
53
                return new Expression\Operators\NewOp();
54 353
            case Node\Expr\Instanceof_::class:
55
                return new Expression\Operators\InstanceOfOp();
56
            /**
57
             * Assign
58
             */
59 353
            case Node\Expr\AssignOp\Pow::class:
60
                return new Expression\AssignOp\Pow();
61 353
            case Node\Expr\AssignOp\Plus::class:
62
                return new Expression\AssignOp\Plus();
63 353
            case Node\Expr\AssignOp\Minus::class:
64
                return new Expression\AssignOp\Minus();
65 353
            case Node\Expr\AssignOp\Mod::class:
66
                return new Expression\AssignOp\Mod();
67 353
            case Node\Expr\AssignOp\BitwiseOr::class:
68
                return new Expression\AssignOp\BitwiseOr();
69 353
            case Node\Expr\AssignOp\BitwiseAnd::class:
70
                return new Expression\AssignOp\BitwiseAnd();
71
            /**
72
             * BinaryOp
73
             */
74 353
            case Node\Expr\BinaryOp\Identical::class:
75 28
                return new Expression\BinaryOp\Identical();
76 325
            case Node\Expr\BinaryOp\Concat::class:
77
                return new Expression\Operators\Contact();
78 325
            case Node\Expr\BinaryOp\NotIdentical::class:
79 14
                return new Expression\BinaryOp\NotIdentical();
80 311
            case Node\Expr\BinaryOp\Equal::class:
81 34
                return new Expression\BinaryOp\Equal();
82 277
            case Node\Expr\BinaryOp\NotEqual::class:
83 17
                return new Expression\BinaryOp\NotEqual();
84
            /**
85
             * @link http://php.net/manual/en/language.operators.increment.php
86
             */
87 260
            case Node\Expr\PostInc::class:
88 4
                return new Expression\Operators\PostInc();
89 256
            case Node\Expr\PostDec::class:
90 4
                return new Expression\Operators\PostDec();
91
            /**
92
             * Arithmetical
93
             */
94 252
            case Node\Expr\BinaryOp\Div::class:
95 37
                return new Expression\Operators\Arithmetical\Div();
96 215
            case Node\Expr\BinaryOp\Plus::class:
97 41
                return new Expression\Operators\Arithmetical\Plus();
98 174
            case Node\Expr\BinaryOp\Minus::class:
99 18
                return new Expression\Operators\Arithmetical\Minus();
100 156
            case Node\Expr\BinaryOp\Mul::class:
101 35
                return new Expression\Operators\Arithmetical\Mul();
102 121
            case Node\Expr\BinaryOp\Mod::class:
103 35
                return new Expression\Operators\Arithmetical\Mod();
104
            /**
105
             * Bitwise
106
             * @link http://php.net/manual/ru/language.operators.bitwise.php
107
             */
108 86
            case Node\Expr\BinaryOp\BitwiseOr::class:
109
                return new Expression\Operators\Bitwise\BitwiseOr();
110 86
            case Node\Expr\BinaryOp\BitwiseXor::class:
111
                return new Expression\Operators\Bitwise\BitwiseXor();
112 86
            case Node\Expr\BinaryOp\BitwiseAnd::class:
113
                return new Expression\Operators\Bitwise\BitwiseAnd();
114 86
            case Node\Expr\BinaryOp\ShiftRight::class:
115
                return new Expression\Operators\Bitwise\ShiftRight();
116 86
            case Node\Expr\BinaryOp\ShiftLeft::class:
117
                return new Expression\Operators\Bitwise\ShiftLeft();
118 86
            case Node\Expr\BitwiseNot::class:
119
                return new Expression\Operators\Bitwise\BitwiseNot();
120
            /**
121
             * Logical
122
             */
123 86
            case Node\Expr\BinaryOp\BooleanOr::class:
124 10
                return new Expression\Operators\Logical\BooleanOr();
125 76
            case Node\Expr\BinaryOp\BooleanAnd::class:
126 5
                return new Expression\Operators\Logical\BooleanAnd();
127 71
            case Node\Expr\BooleanNot::class:
128 5
                return new Expression\Operators\Logical\BooleanNot();
129
            /**
130
             * Comparison
131
             */
132 66
            case Node\Expr\BinaryOp\Greater::class:
133 12
                return new Expression\Operators\Comparison\Greater();
134 54
            case Node\Expr\BinaryOp\GreaterOrEqual::class:
135 12
                return new Expression\Operators\Comparison\GreaterOrEqual();
136 42
            case Node\Expr\BinaryOp\Smaller::class:
137 12
                return new Expression\Operators\Comparison\Smaller();
138 30
            case Node\Expr\BinaryOp\SmallerOrEqual::class:
139 12
                return new Expression\Operators\Comparison\SmallerOrEqual();
140
            /**
141
             * Another
142
             */
143 18
            case Node\Expr\UnaryMinus::class:
144 9
                return new Expression\Operators\UnaryMinus();
145 9
            case Node\Expr\UnaryPlus::class:
146 9
                return new Expression\Operators\UnaryPlus();
147
        }
148
149
        return false;
150
    }
151
152
    /**
153
     * @param object|string $expr
154
     * @return CompiledExpression
155
     */
156 363
    public function compile($expr)
0 ignored issues
show
Complexity introduced by
This operation has 672 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...
157
    {
158 363
        if (is_string($expr)) {
159
            return new CompiledExpression(CompiledExpression::STRING, $expr);
160
        }
161
162 363
        if (is_null($expr)) {
163
            return new CompiledExpression(CompiledExpression::NULL);
164
        }
165
166 363
        if (!is_object($expr)) {
167
            throw new InvalidArgumentException('$expr must be string/object/null');
168
        }
169
170 363
        $className = get_class($expr);
171
        switch ($className) {
172 363
            case 'PhpParser\Node\Expr\PropertyFetch':
173
                return $this->passPropertyFetch($expr);
174 363
            case 'PhpParser\Node\Stmt\Property':
175
                return $this->passProperty($expr);
176 363
            case 'PhpParser\Node\Expr\ClassConstFetch':
177
                return $this->passConstFetch($expr);
178 363
            case 'PhpParser\Node\Expr\Assign':
179
                return $this->passSymbol($expr);
180 363
            case 'PhpParser\Node\Expr\AssignRef':
181
                return $this->passSymbolByRef($expr);
182 363
            case 'PhpParser\Node\Expr\Variable':
183
                return $this->passExprVariable($expr);
184
            /**
185
             * Cast operators
186
             */
187 363
            case 'PhpParser\Node\Expr\Cast\Bool_':
188
                return $this->passCastBoolean($expr);
189 363
            case 'PhpParser\Node\Expr\Cast\Int_':
190
                return $this->passCastInt($expr);
191 363
            case 'PhpParser\Node\Expr\Cast\Double':
192
                return $this->passCastFloat($expr);
193 363
            case 'PhpParser\Node\Expr\Cast\String_':
194
                return $this->passCastString($expr);
195 363
            case 'PhpParser\Node\Expr\Cast\Unset_':
196
                return $this->passCastUnset($expr);
197
            /**
198
             * Expressions
199
             */
200 363
            case 'PhpParser\Node\Expr\Array_':
201 11
                return $this->getArray($expr);
202 362
            case 'PhpParser\Node\Expr\ConstFetch':
203
                return $this->constFetch($expr);
204 362
            case 'PhpParser\Node\Name':
205
                return $this->getNodeName($expr);
206 362
            case 'PhpParser\Node\Name\FullyQualified':
207
                return $this->getFullyQualifiedNodeName($expr);
208
            /**
209
             * Simple Scalar(s)
210
             */
211 362
            case 'PHPSA\Node\Scalar\Nil':
212 8
                return new CompiledExpression(CompiledExpression::NULL);
213 361
            case 'PhpParser\Node\Scalar\LNumber':
214 256
                return new CompiledExpression(CompiledExpression::INTEGER, $expr->value);
215 358
            case 'PhpParser\Node\Scalar\DNumber':
216 131
                return new CompiledExpression(CompiledExpression::DOUBLE, $expr->value);
217 357
            case 'PhpParser\Node\Scalar\String_':
218 8
                return new CompiledExpression(CompiledExpression::STRING, $expr->value);
219 355
            case 'PHPSA\Node\Scalar\Boolean':
220 60
                return new CompiledExpression(CompiledExpression::BOOLEAN, $expr->value);
221 353
            case 'PHPSA\Node\Scalar\Fake':
222 29
                return new CompiledExpression($expr->type, $expr->value);
223
        }
224
225 353
        $expressionCompiler = $this->factory($expr);
226 353
        if (!$expressionCompiler) {
227
            $this->context->debug("Expression compiler is not implemented for {$className}");
228
            return new CompiledExpression(CompiledExpression::UNIMPLEMENTED);
229
        }
230
231 353
        $result = $expressionCompiler->pass($expr, $this->context);
232 353
        if (!$result instanceof CompiledExpression) {
233
            throw new RuntimeException('Please return CompiledExpression from ' . get_class($expressionCompiler));
234
        }
235
236 353
        return $result;
237
    }
238
239
    /**
240
     * @todo Implement
241
     *
242
     * @param Node\Stmt\Property $st
243
     * @return CompiledExpression
244
     */
245
    public function passProperty(Node\Stmt\Property $st)
0 ignored issues
show
Unused Code introduced by
The parameter $st is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
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...
246
    {
247
        return new CompiledExpression();
248
    }
249
250
    /**
251
     * @param Node\Expr\Variable $expr
252
     * @return CompiledExpression
253
     */
254
    public function declareVariable(Node\Expr\Variable $expr)
255
    {
256
        $variable = $this->context->getSymbol($expr->name);
257
        if ($variable) {
258
            $variable->incGets();
259
            return new CompiledExpression($variable->getType(), $variable->getValue(), $variable);
260
        }
261
262
        $symbol = 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...
263
        $this->context->addVariable($symbol);
264
265
        return new CompiledExpression;
266
    }
267
268
    /**
269
     * @param Node\Name\FullyQualified $expr
270
     * @return CompiledExpression
271
     */
272
    public function getFullyQualifiedNodeName(Node\Name\FullyQualified $expr)
273
    {
274
        $this->context->debug('Unimplemented FullyQualified', $expr);
275
        
276
        return new CompiledExpression;
277
    }
278
279
    /**
280
     * @param Node\Name $expr
281
     * @return CompiledExpression
282
     */
283
    public function getNodeName(Node\Name $expr)
284
    {
285
        if ($expr->toString() === 'null') {
286
            return new CompiledExpression(CompiledExpression::NULL);
287
        }
288
289
        if (in_array($expr, ['parent'], true)) {
290
            /** @var ClassDefinition $scope */
291
            $scope = $this->context->scope;
292
            assert($scope instanceof ClassDefinition);
293
294
            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...
295
                $definition = $scope->getExtendsClassDefinition();
296
                if ($definition) {
297
                    return new CompiledExpression(CompiledExpression::OBJECT, $definition);
298
                }
299
            } else {
300
                $this->context->notice(
301
                    'no-parent',
302
                    'Cannot access parent:: when current class scope has no parent',
303
                    $expr
304
                );
305
            }
306
        }
307
308
        if (in_array($expr, ['self', 'static'], true)) {
309
            return CompiledExpression::fromZvalValue($this->context->scope);
310
        }
311
312
        if (defined($expr)) {
313
            return CompiledExpression::fromZvalValue(constant($expr));
314
        }
315
316
        return new CompiledExpression(CompiledExpression::STRING, $expr->toString());
317
    }
318
319
    /**
320
     * (bool) {$expr}
321
     *
322
     * @param Node\Expr\Cast\Bool_ $expr
323
     * @return CompiledExpression
324
     */
325
    protected function passCastBoolean(Node\Expr\Cast\Bool_ $expr)
326
    {
327
        $expression = new Expression($this->context);
328
        $compiledExpression = $expression->compile($expr->expr);
329
330
        switch ($compiledExpression->getType()) {
331
            case CompiledExpression::BOOLEAN:
332
                $this->context->notice('stupid-cast', "You are trying to cast 'boolean' to 'boolean'", $expr);
333
                return $compiledExpression;
334
            case CompiledExpression::DOUBLE:
335
            case CompiledExpression::INTEGER:
336
            case CompiledExpression::NUMBER:
337
            case CompiledExpression::STRING:
338
                return new CompiledExpression(CompiledExpression::BOOLEAN, (bool) $compiledExpression->getValue());
339
        }
340
341
        return new CompiledExpression();
342
    }
343
344
    /**
345
     * (int) {$expr}
346
     *
347
     * @param Node\Expr\Cast\Int_ $expr
348
     * @return CompiledExpression
349
     */
350
    protected function passCastInt(Node\Expr\Cast\Int_ $expr)
351
    {
352
        $expression = new Expression($this->context);
353
        $compiledExpression = $expression->compile($expr->expr);
354
355
        switch ($compiledExpression->getType()) {
356
            case CompiledExpression::INTEGER:
357
                $this->context->notice('stupid-cast', "You are trying to cast 'int' to 'int'", $expr);
358
                return $compiledExpression;
359
            case CompiledExpression::BOOLEAN:
360
            case CompiledExpression::DOUBLE:
361
            case CompiledExpression::NUMBER:
362
            case CompiledExpression::STRING:
363
                return new CompiledExpression(CompiledExpression::INTEGER, (int) $compiledExpression->getValue());
364
        }
365
366
        return new CompiledExpression();
367
    }
368
369
    /**
370
     * (float) {$expr}
371
     *
372
     * @param Node\Expr\Cast\Double $expr
373
     * @return CompiledExpression
374
     */
375
    protected function passCastFloat(Node\Expr\Cast\Double $expr)
376
    {
377
        $expression = new Expression($this->context);
378
        $compiledExpression = $expression->compile($expr->expr);
379
380
        switch ($compiledExpression->getType()) {
381
            case CompiledExpression::DOUBLE:
382
                $this->context->notice('stupid-cast', "You are trying to cast 'float' to 'float'", $expr);
383
                return $compiledExpression;
384
            case CompiledExpression::BOOLEAN:
385
            case CompiledExpression::INTEGER:
386
            case CompiledExpression::NUMBER:
387
            case CompiledExpression::STRING:
388
                return new CompiledExpression(CompiledExpression::DOUBLE, (float) $compiledExpression->getValue());
389
        }
390
391
        return new CompiledExpression();
392
    }
393
394
    /**
395
     * (string) {$expr}
396
     *
397
     * @param Node\Expr\Cast\String_ $expr
398
     * @return CompiledExpression
399
     */
400
    protected function passCastString(Node\Expr\Cast\String_ $expr)
401
    {
402
        $expression = new Expression($this->context);
403
        $compiledExpression = $expression->compile($expr->expr);
404
405
        switch ($compiledExpression->getType()) {
406
            case CompiledExpression::STRING:
407
                $this->context->notice('stupid-cast', "You are trying to cast 'string' to 'string'", $expr);
408
                return $compiledExpression;
409
            case CompiledExpression::BOOLEAN:
410
            case CompiledExpression::INTEGER:
411
            case CompiledExpression::NUMBER:
412
            case CompiledExpression::DOUBLE:
413
                return new CompiledExpression(CompiledExpression::DOUBLE, (string) $compiledExpression->getValue());
414
        }
415
416
        return new CompiledExpression();
417
    }
418
419
    /**
420
     * (unset) {$expr}
421
     *
422
     * @param Node\Expr\Cast\Unset_ $expr
423
     * @return CompiledExpression
424
     */
425
    protected function passCastUnset(Node\Expr\Cast\Unset_ $expr)
426
    {
427
        $expression = new Expression($this->context);
428
        $compiledExpression = $expression->compile($expr->expr);
429
430
        switch ($compiledExpression->getType()) {
431
            case CompiledExpression::NULL:
432
                $this->context->notice('stupid-cast', "You are trying to cast 'unset' to 'null'", $expr);
433
                return $compiledExpression;
434
        }
435
436
        return new CompiledExpression(CompiledExpression::NULL, null);
437
    }
438
439
    /**
440
     * @param Node\Expr\PropertyFetch $expr
441
     * @return CompiledExpression
442
     */
443
    protected function passPropertyFetch(Node\Expr\PropertyFetch $expr)
444
    {
445
        $propertNameCE = $this->compile($expr->name);
446
447
        $scopeExpression = $this->compile($expr->var);
448
        if ($scopeExpression->isObject()) {
449
            $scopeExpressionValue = $scopeExpression->getValue();
450
            if ($scopeExpressionValue instanceof ClassDefinition) {
451
                $propertyName = $propertNameCE->isString() ? $propertNameCE->getValue() : false;
452
                if ($propertyName) {
453
                    if ($scopeExpressionValue->hasProperty($propertyName, true)) {
454
                        $property = $scopeExpressionValue->getProperty($propertyName, true);
455
                        return $this->compile($property);
456
                    } else {
457
                        $this->context->notice(
458
                            'undefined-property',
459
                            sprintf(
460
                                'Property %s does not exist in %s scope',
461
                                $propertyName,
462
                                $scopeExpressionValue->getName()
463
                            ),
464
                            $expr
465
                        );
466
                    }
467
                }
468
            }
469
        } elseif (!$scopeExpression->canBeObject()) {
470
            return new CompiledExpression(CompiledExpression::UNKNOWN);
471
        }
472
473
        $this->context->notice(
474
            'property-fetch-on-non-object',
475
            "It's not possible to fetch property on not object",
476
            $expr,
477
            Check::CHECK_BETA
478
        );
479
480
        return new CompiledExpression(CompiledExpression::UNKNOWN);
481
    }
482
483
    /**
484
     * @param Node\Expr\ClassConstFetch $expr
485
     * @return CompiledExpression
486
     */
487
    protected function passConstFetch(Node\Expr\ClassConstFetch $expr)
488
    {
489
        $leftCE = $this->compile($expr->class);
490
        if ($leftCE->isObject()) {
491
            $leftCEValue = $leftCE->getValue();
492
            if ($leftCEValue instanceof ClassDefinition) {
493
                if (!$leftCEValue->hasConst($expr->name)) {
494
                    $this->context->notice(
495
                        'undefined-const',
496
                        sprintf('Constant %s does not exist in %s scope', $expr->name, $expr->class),
497
                        $expr
498
                    );
499
                    return new CompiledExpression(CompiledExpression::UNKNOWN);
500
                }
501
502
                return new CompiledExpression();
503
            }
504
        }
505
506
        $this->context->debug('Unknown const fetch', $expr);
507
        return new CompiledExpression();
508
    }
509
510
    /**
511
     * @param Node\Expr\Assign $expr
512
     * @return CompiledExpression
513
     */
514
    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...
515
    {
516
        $expression = new Expression($this->context);
517
        $compiledExpression = $expression->compile($expr->expr);
518
519
        if ($expr->var instanceof Node\Expr\List_) {
520
            $isCorrectType = false;
521
522
            switch ($compiledExpression->getType()) {
523
                case CompiledExpression::ARR:
524
                    $isCorrectType = true;
525
                    break;
526
            }
527
528
            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...
529
                foreach ($expr->var->vars as $key => $var) {
530
                    if ($var instanceof Node\Expr\Variable) {
531
                        if (!isset($expr->var->name)) {
532
                            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...
533
                        }
534
                        $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...
535
536
                        $symbol = $this->context->getSymbol($name);
537
                        if (!$symbol) {
538
                            $symbol = new Variable(
539
                                $name,
540
                                null,
541
                                CompiledExpression::UNKNOWN,
542
                                $this->context->getCurrentBranch()
543
                            );
544
                            $this->context->addVariable($symbol);
545
                        }
546
547
                        if (!$isCorrectType) {
548
                            $symbol->modify(CompiledExpression::NULL, null);
549
                        }
550
551
                        $symbol->incSets();
552
                    }
553
                }
554
            }
555
556
            return new CompiledExpression();
557
        }
558
559
        if ($expr->var instanceof Node\Expr\Variable) {
560
            $name = $expr->var->name;
561
562
            $symbol = $this->context->getSymbol($name);
563
            if ($symbol) {
564
                $symbol->modify($compiledExpression->getType(), $compiledExpression->getValue());
565
            } else {
566
                $symbol = new Variable(
567
                    $name,
568
                    $compiledExpression->getValue(),
569
                    $compiledExpression->getType(),
570
                    $this->context->getCurrentBranch()
571
                );
572
                $this->context->addVariable($symbol);
573
            }
574
575
            $symbol->incSets();
576
            return $compiledExpression;
577
        }
578
579
        if ($expr->var instanceof Node\Expr\PropertyFetch) {
580
            $compiledExpression = $this->compile($expr->var->var);
581
            if ($compiledExpression->getType() == CompiledExpression::OBJECT) {
582
                $objectDefinition = $compiledExpression->getValue();
583
                if ($objectDefinition instanceof ClassDefinition) {
584
                    if (is_string($expr->var->name)) {
585
                        if ($objectDefinition->hasProperty($expr->var->name)) {
586
                            return $this->compile($objectDefinition->getProperty($expr->var->name));
587
                        }
588
                    }
589
                }
590
            }
591
        }
592
593
        $this->context->debug('Unknown how to pass symbol');
594
        return new CompiledExpression();
595
    }
596
597
    /**
598
     * @param Node\Expr\AssignRef $expr
599
     * @return CompiledExpression
600
     */
601
    protected function passSymbolByRef(Node\Expr\AssignRef $expr)
602
    {
603
        if ($expr->var instanceof \PhpParser\Node\Expr\List_) {
604
            return new CompiledExpression();
605
        }
606
607
        if ($expr->var instanceof Node\Expr\Variable) {
608
            $name = $expr->var->name;
609
610
            $expression = new Expression($this->context);
611
            $compiledExpression = $expression->compile($expr->expr);
612
613
            $symbol = $this->context->getSymbol($name);
614
            if ($symbol) {
615
                $symbol->modify($compiledExpression->getType(), $compiledExpression->getValue());
616
617
                if ($expr->expr instanceof Node\Expr\Variable) {
618
                    $rightVarName = $expr->expr->name;
619
620
                    $rightSymbol = $this->context->getSymbol($rightVarName);
621
                    if ($rightSymbol) {
622
                        $rightSymbol->incUse();
623
                        $symbol->setReferencedTo($rightSymbol);
624
                    } else {
625
                        $this->context->debug('Cannot fetch variable by name: ' . $rightVarName);
626
                    }
627
                }
628
629
                $this->context->debug('Unknown how to pass referenced to symbol: ' . get_class($expr->expr));
630
            } else {
631
                $symbol = new Variable(
632
                    $name,
0 ignored issues
show
Bug introduced by
It seems like $name defined by $expr->var->name on line 608 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...
633
                    $compiledExpression->getValue(),
634
                    $compiledExpression->getType(),
635
                    $this->context->getCurrentBranch()
636
                );
637
                $this->context->addVariable($symbol);
638
            }
639
640
            $symbol->incSets();
641
            return $compiledExpression;
642
        }
643
644
        $this->context->debug('Unknown how to pass symbol by ref');
645
        return new CompiledExpression();
646
    }
647
648
    /**
649
     * @param Node\Expr\Variable $expr
650
     * @return CompiledExpression
651
     */
652
    protected function passExprVariable(Node\Expr\Variable $expr)
653
    {
654
        $variable = $this->context->getSymbol($expr->name);
655
        if ($variable) {
656
            $variable->incGets();
657
            return new CompiledExpression($variable->getType(), $variable->getValue(), $variable);
658
        }
659
660
        $this->context->notice(
661
            'undefined-variable',
662
            sprintf('You trying to use undefined variable $%s', $expr->name),
663
            $expr
664
        );
665
666
        return new CompiledExpression();
667
    }
668
669
    /**
670
     * Compile Array_ expression to CompiledExpression
671
     *
672
     * @param Node\Expr\Array_ $expr
673
     * @return CompiledExpression
674
     */
675 11
    protected function getArray(Node\Expr\Array_ $expr)
676
    {
677 11
        if ($expr->items === array()) {
678 8
            return new CompiledExpression(CompiledExpression::ARR, array());
679
        }
680
681 3
        $resultArray = array();
682
683 3
        foreach ($expr->items as $item) {
684 3
            $compiledValueResult = $this->compile($item->value);
685 3
            if ($item->key) {
686 1
                $compiledKeyResult = $this->compile($item->key);
687 1
                switch ($compiledKeyResult->getType()) {
688 1
                    case CompiledExpression::INTEGER:
689 1
                    case CompiledExpression::DOUBLE:
690 1
                    case CompiledExpression::BOOLEAN:
691 1
                    case CompiledExpression::NULL:
692 1
                    case CompiledExpression::STRING:
693 1
                        $resultArray[$compiledKeyResult->getValue()] = $compiledValueResult->getValue();
694 1
                        break;
695
                    default:
696
                        $this->context->debug("Type {$compiledKeyResult->getType()} is not supported for key value");
697
                        return new CompiledExpression(CompiledExpression::ARR);
698
                        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...
699 1
                }
700 1
            } else {
701 2
                $resultArray[] = $compiledValueResult->getValue();
702
            }
703 3
        }
704
705 3
        return new CompiledExpression(CompiledExpression::ARR, $resultArray);
706
    }
707
708
    /**
709
     * Convert const fetch expr to CompiledExpression
710
     *
711
     * @param Node\Expr\ConstFetch $expr
712
     * @return CompiledExpression
713
     */
714
    protected function constFetch(Node\Expr\ConstFetch $expr)
715
    {
716
        if ($expr->name instanceof Node\Name) {
717
            if ($expr->name->parts[0] === 'true') {
718
                return new CompiledExpression(CompiledExpression::BOOLEAN, true);
719
            }
720
721
            if ($expr->name->parts[0] === 'false') {
722
                return new CompiledExpression(CompiledExpression::BOOLEAN, false);
723
            }
724
        }
725
726
        /**
727
         * @todo Implement check
728
         */
729
730
        $expression = new Expression($this->context);
731
        $compiledExpr = $expression->compile($expr->name);
732
733
        return $compiledExpr;
734
    }
735
}
736