Completed
Push — master ( 631126...00da34 )
by Дмитрий
03:46
created

Expression::getNodeName()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6
Metric Value
dl 0
loc 9
ccs 0
cts 5
cp 0
rs 9.6667
cc 2
eloc 5
nc 2
nop 1
crap 6
1
<?php
2
/**
3
 * @author Patsura Dmitry https://github.com/ovr <[email protected]>
4
 */
5
6
namespace PHPSA\Compiler;
7
8
use PHPSA\CompiledExpression;
9
use PHPSA\Context;
10
use PhpParser\Node;
11
use PHPSA\Definition\ClassDefinition;
12
use PHPSA\Exception\RuntimeException;
13
use PHPSA\Variable;
14
use PHPSA\Compiler\Expression\AbstractExpressionCompiler;
15
16
class Expression
0 ignored issues
show
Complexity introduced by
This class has a complexity of 132 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...
17
{
18
    /**
19
     * @var Context
20
     */
21
    protected $context;
22
23
    /**
24
     * @param Context $context
25
     */
26 363
    public function __construct(Context $context)
27
    {
28 363
        $this->context = $context;
29 363
    }
30
31
    /**
32
     * @param $expr
33
     * @return ExpressionCompilerInterface|AbstractExpressionCompiler
34
     */
35 353
    protected function factory($expr)
36
    {
37 353
        switch (get_class($expr)) {
38
            /**
39
             * Call(s)
40
             */
41 353
            case 'PhpParser\Node\Expr\MethodCall':
42
                return new Expression\MethodCall();
43 353
            case 'PhpParser\Node\Expr\FuncCall':
44
                return new Expression\FunctionCall();
45 353
            case 'PhpParser\Node\Expr\StaticCall':
46
                return new Expression\StaticCall();
47
            /**
48
             * Operators
49
             */
50 353
            case 'PhpParser\Node\Expr\New_':
51
                return new Expression\Operators\NewOp();
52 353
            case 'PhpParser\Node\Expr\Instanceof_':
53
                return new Expression\Operators\InstanceOfOp();
54 353
            case 'PhpParser\Node\Expr\BinaryOp\Identical':
55 28
                return new Expression\BinaryOp\Identical();
56 325
            case 'PhpParser\Node\Expr\BinaryOp\Concat':
57
                return new Expression\Operators\Contact();
58 325
            case 'PhpParser\Node\Expr\BinaryOp\NotIdentical':
59 14
                return new Expression\BinaryOp\NotIdentical();
60 311
            case 'PhpParser\Node\Expr\BinaryOp\Equal':
61 34
                return new Expression\BinaryOp\Equal();
62 277
            case 'PhpParser\Node\Expr\BinaryOp\NotEqual':
63 17
                return new Expression\BinaryOp\NotEqual();
64
            /**
65
             * @link http://php.net/manual/en/language.operators.increment.php
66
             */
67 260
            case 'PhpParser\Node\Expr\PostInc':
68 4
                return new Expression\Operators\PostInc();
69 256
            case 'PhpParser\Node\Expr\PostDec':
70 4
                return new Expression\Operators\PostDec();
71
            /**
72
             * Arithmetical
73
             */
74 252
            case 'PhpParser\Node\Expr\BinaryOp\Div':
75 37
                return new Expression\Operators\Arithmetical\Div();
76 215
            case 'PhpParser\Node\Expr\BinaryOp\Plus':
77 41
                return new Expression\Operators\Arithmetical\Plus();
78 174
            case 'PhpParser\Node\Expr\BinaryOp\Minus':
79 18
                return new Expression\Operators\Arithmetical\Minus();
80 156
            case 'PhpParser\Node\Expr\BinaryOp\Mul':
81 35
                return new Expression\Operators\Arithmetical\Mul();
82 121
            case 'PhpParser\Node\Expr\BinaryOp\Mod':
83 35
                return new Expression\Operators\Arithmetical\Mod();
84
            /**
85
             * Bitwise
86
             * @link http://php.net/manual/ru/language.operators.bitwise.php
87
             */
88 86
            case 'PhpParser\Node\Expr\BinaryOp\BitwiseOr':
89
                return new Expression\Operators\Bitwise\BitwiseOr();
90 86
            case 'PhpParser\Node\Expr\BinaryOp\BitwiseXor':
91
                return new Expression\Operators\Bitwise\BitwiseXor();
92 86
            case 'PhpParser\Node\Expr\BinaryOp\BitwiseAnd':
93
                return new Expression\Operators\Bitwise\BitwiseAnd();
94 86
            case 'PhpParser\Node\Expr\BinaryOp\ShiftRight':
95
                return new Expression\Operators\Bitwise\ShiftRight();
96 86
            case 'PhpParser\Node\Expr\BinaryOp\ShiftLeft':
97
                return new Expression\Operators\Bitwise\ShiftLeft();
98 86
            case 'PhpParser\Node\Expr\BitwiseNot':
99
                return new Expression\Operators\Bitwise\BitwiseNot();
100
            /**
101
             * Logical
102
             */
103 86
            case 'PhpParser\Node\Expr\BinaryOp\BooleanOr':
104 10
                return new Expression\Operators\Logical\BooleanOr();
105 76
            case 'PhpParser\Node\Expr\BinaryOp\BooleanAnd':
106 5
                return new Expression\Operators\Logical\BooleanAnd();
107 71
            case 'PhpParser\Node\Expr\BooleanNot':
108 5
                return new Expression\Operators\Logical\BooleanNot();
109
            /**
110
             * Comparison
111
             */
112 66
            case 'PhpParser\Node\Expr\BinaryOp\Greater':
113 12
                return new Expression\Operators\Comparison\Greater();
114 54
            case 'PhpParser\Node\Expr\BinaryOp\GreaterOrEqual':
115 12
                return new Expression\Operators\Comparison\GreaterOrEqual();
116 42
            case 'PhpParser\Node\Expr\BinaryOp\Smaller':
117 12
                return new Expression\Operators\Comparison\Smaller();
118 30
            case 'PhpParser\Node\Expr\BinaryOp\SmallerOrEqual':
119 12
                return new Expression\Operators\Comparison\SmallerOrEqual();
120
            /**
121
             * Another
122
             */
123 18
            case 'PhpParser\Node\Expr\UnaryMinus':
124 9
                return new Expression\Operators\UnaryMinus();
125 9
            case 'PhpParser\Node\Expr\UnaryPlus':
126 9
                return new Expression\Operators\UnaryPlus();
127
        }
128
129
        return false;
130
    }
131
132
    /**
133
     * @param $expr
134
     * @return CompiledExpression
135
     */
136 363
    public function compile($expr)
137
    {
138 363
        $className = get_class($expr);
139
        switch ($className) {
140 363
            case 'PhpParser\Node\Expr\PropertyFetch':
141
                return $this->passPropertyFetch($expr);
142 363
            case 'PhpParser\Node\Stmt\Property':
143
                return $this->passProperty($expr);
144 363
            case 'PhpParser\Node\Expr\ClassConstFetch':
145
                return $this->passConstFetch($expr);
146 363
            case 'PhpParser\Node\Expr\Assign':
147
                return $this->passSymbol($expr);
148 363
            case 'PhpParser\Node\Expr\AssignRef':
149
                return $this->passSymbolByRef($expr);
150 363
            case 'PhpParser\Node\Expr\Variable':
151
                return $this->passExprVariable($expr);
152
            /**
153
             * Cast operators
154
             */
155 363
            case 'PhpParser\Node\Expr\Cast\Bool_':
156
                return $this->passCastBoolean($expr);
157 363
            case 'PhpParser\Node\Expr\Cast\Int_':
158
                return $this->passCastInt($expr);
159 363
            case 'PhpParser\Node\Expr\Cast\Double':
160
                return $this->passCastFloat($expr);
161 363
            case 'PhpParser\Node\Expr\Cast\String_':
162
                return $this->passCastString($expr);
163 363
            case 'PhpParser\Node\Expr\Cast\Unset_':
164
                return $this->passCastUnset($expr);
165
            /**
166
             * Expressions
167
             */
168 363
            case 'PhpParser\Node\Expr\Array_':
169 11
                return $this->getArray($expr);
170 362
            case 'PhpParser\Node\Expr\ConstFetch':
171
                return $this->constFetch($expr);
172 362
            case 'PhpParser\Node\Name':
173
                return $this->getNodeName($expr);
174
            /**
175
             * Simple Scalar(s)
176
             */
177 362
            case 'PHPSA\Node\Scalar\Nil':
178 8
                return new CompiledExpression(CompiledExpression::NULL);
179 361
            case 'PhpParser\Node\Scalar\LNumber':
180 256
                return new CompiledExpression(CompiledExpression::INTEGER, $expr->value);
181 358
            case 'PhpParser\Node\Scalar\DNumber':
182 131
                return new CompiledExpression(CompiledExpression::DOUBLE, $expr->value);
183 357
            case 'PhpParser\Node\Scalar\String_':
184 8
                return new CompiledExpression(CompiledExpression::STRING, $expr->value);
185 355
            case 'PHPSA\Node\Scalar\Boolean':
186 60
                return new CompiledExpression(CompiledExpression::BOOLEAN, $expr->value);
187 353
            case 'PHPSA\Node\Scalar\Fake':
188 29
                return new CompiledExpression($expr->type, $expr->value);
189
        }
190
191 353
        $expressionCompiler = $this->factory($expr);
192 353
        if (!$expressionCompiler) {
193
            $this->context->debug("Expression compiler is not implemented for {$className}");
194
            return new CompiledExpression(CompiledExpression::UNIMPLEMENTED);
195
        }
196
197 353
        $result = $expressionCompiler->pass($expr, $this->context);
198 353
        if (!$result instanceof CompiledExpression) {
199
            throw new RuntimeException('Please return CompiledExpression from ' . get_class($expressionCompiler));
200
        }
201
202 353
        return $result;
203
    }
204
205
    /**
206
     * @todo Implement
207
     *
208
     * @param Node\Stmt\Property $st
209
     * @return CompiledExpression
210
     */
211
    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...
212
    {
213
        return new CompiledExpression();
214
    }
215
216
    /**
217
     * @param Node\Expr\Variable $expr
218
     * @return CompiledExpression
219
     */
220
    public function declareVariable(Node\Expr\Variable $expr)
221
    {
222
        $variable = $this->context->getSymbol($expr->name);
223
        if ($variable) {
224
            $variable->incGets();
225
            return new CompiledExpression($variable->getType(), $variable->getValue());
226
        }
227
228
        $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...
229
        $this->context->addVariable($symbol);
230
231
        return new CompiledExpression;
232
    }
233
234
    /**
235
     * @param Node\Name $expr
236
     * @return CompiledExpression
237
     */
238
    public function getNodeName(Node\Name $expr)
239
    {
240
        if ($expr->parts[0] === 'null') {
241
            return new CompiledExpression(CompiledExpression::NULL);
242
        }
243
244
        $this->context->debug('Unknown how to get node name');
245
        return new CompiledExpression();
246
    }
247
248
    /**
249
     * (bool) {$expr}
250
     *
251
     * @param Node\Expr\Cast\Bool_ $expr
252
     * @return CompiledExpression
253
     */
254
    protected function passCastBoolean(Node\Expr\Cast\Bool_ $expr)
255
    {
256
        $expression = new Expression($this->context);
257
        $compiledExpression = $expression->compile($expr->expr);
258
259
        switch ($compiledExpression->getType()) {
260
            case CompiledExpression::BOOLEAN:
261
                $this->context->notice('stupid-cast', "You are trying to cast 'boolean' to 'boolean'", $expr);
262
                return $compiledExpression;
263
            case CompiledExpression::DOUBLE:
264
            case CompiledExpression::INTEGER:
265
                return new CompiledExpression(CompiledExpression::BOOLEAN, (bool) $compiledExpression->getValue());
266
        }
267
268
        return new CompiledExpression();
269
    }
270
271
    /**
272
     * (int) {$expr}
273
     *
274
     * @param Node\Expr\Cast\Int_ $expr
275
     * @return CompiledExpression
276
     */
277
    protected function passCastInt(Node\Expr\Cast\Int_ $expr)
278
    {
279
        $expression = new Expression($this->context);
280
        $compiledExpression = $expression->compile($expr->expr);
281
282
        switch ($compiledExpression->getType()) {
283
            case CompiledExpression::INTEGER:
284
                $this->context->notice('stupid-cast', "You are trying to cast 'int' to 'int'", $expr);
285
                return $compiledExpression;
286
            case CompiledExpression::BOOLEAN:
287
            case CompiledExpression::DOUBLE:
288
            case CompiledExpression::STRING:
289
                return new CompiledExpression(CompiledExpression::INTEGER, (int) $compiledExpression->getValue());
290
        }
291
292
        return new CompiledExpression();
293
    }
294
295
    /**
296
     * (float) {$expr}
297
     *
298
     * @param Node\Expr\Cast\Double $expr
299
     * @return CompiledExpression
300
     */
301
    protected function passCastFloat(Node\Expr\Cast\Double $expr)
302
    {
303
        $expression = new Expression($this->context);
304
        $compiledExpression = $expression->compile($expr->expr);
305
306
        switch ($compiledExpression->getType()) {
307
            case CompiledExpression::DOUBLE:
308
                $this->context->notice('stupid-cast', "You are trying to cast 'float' to 'float'", $expr);
309
                return $compiledExpression;
310
            case CompiledExpression::BOOLEAN:
311
            case CompiledExpression::INTEGER:
312
            case CompiledExpression::STRING:
313
                return new CompiledExpression(CompiledExpression::DOUBLE, (float) $compiledExpression->getValue());
314
        }
315
316
        return new CompiledExpression();
317
    }
318
319
    /**
320
     * (string) {$expr}
321
     *
322
     * @param Node\Expr\Cast\String_ $expr
323
     * @return CompiledExpression
324
     */
325
    protected function passCastString(Node\Expr\Cast\String_ $expr)
326
    {
327
        $expression = new Expression($this->context);
328
        $compiledExpression = $expression->compile($expr->expr);
329
330
        switch ($compiledExpression->getType()) {
331
            case CompiledExpression::STRING:
332
                $this->context->notice('stupid-cast', "You are trying to cast 'string' to 'string'", $expr);
333
                return $compiledExpression;
334
            case CompiledExpression::BOOLEAN:
335
            case CompiledExpression::INTEGER:
336
            case CompiledExpression::DOUBLE:
337
                return new CompiledExpression(CompiledExpression::DOUBLE, (string) $compiledExpression->getValue());
338
        }
339
340
        return new CompiledExpression();
341
    }
342
343
    /**
344
     * (unset) {$expr}
345
     *
346
     * @param Node\Expr\Cast\Unset_ $expr
347
     * @return CompiledExpression
348
     */
349
    protected function passCastUnset(Node\Expr\Cast\Unset_ $expr)
350
    {
351
        $expression = new Expression($this->context);
352
        $compiledExpression = $expression->compile($expr->expr);
353
354
        switch ($compiledExpression->getType()) {
355
            case CompiledExpression::NULL:
356
                $this->context->notice('stupid-cast', "You are trying to cast 'unset' to 'null'", $expr);
357
                return $compiledExpression;
358
        }
359
360
        return new CompiledExpression(CompiledExpression::NULL, null);
361
    }
362
363
    /**
364
     * @param Node\Expr\PropertyFetch $expr
365
     * @return CompiledExpression
366
     */
367
    protected function passPropertyFetch(Node\Expr\PropertyFetch $expr)
368
    {
369
        $scopeExpression = $this->compile($expr->var);
370
        switch ($scopeExpression->getType()) {
371
            case CompiledExpression::OBJECT:
372
                $scopeExpressionValue = $scopeExpression->getValue();
373
                if ($scopeExpressionValue instanceof ClassDefinition) {
374
                    $propertyName = is_string($expr->name) ? $expr->name : false;
375
                    if ($expr->name instanceof Variable) {
376
                        /**
377
                         * @todo implement fetch from symbol table
378
                         */
379
                        //$methodName = $expr->name->name;
380
                    }
381
382
                    if ($propertyName) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $propertyName of type string|false is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false 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...
383
                        if ($scopeExpressionValue->hasProperty($propertyName, true)) {
384
                            $property = $scopeExpressionValue->getProperty($propertyName, true);
0 ignored issues
show
Unused Code introduced by
$property is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
385
                            return new CompiledExpression();
386
                        } else {
387
                            $this->context->notice(
388
                                'undefined-property',
389
                                sprintf(
390
                                    'Property %s does not exist in %s scope',
391
                                    $propertyName,
392
                                    $scopeExpressionValue->getName()
393
                                ),
394
                                $expr
395
                            );
396
                        }
397
                    }
398
                }
399
                break;
400
            default:
401
                $this->context->debug('You cannot fetch property from not object type');
402
                break;
403
        }
404
405
        $this->context->debug('Unknown property fetch');
406
        return new CompiledExpression();
407
    }
408
409
    /**
410
     * @param Node\Expr\ClassConstFetch $expr
411
     * @return CompiledExpression
412
     */
413
    protected function passConstFetch(Node\Expr\ClassConstFetch $expr)
414
    {
415
        if ($expr->class instanceof Node\Name) {
416
            $scope = $expr->class->parts[0];
417
418
            if ($scope == 'self' || $scope == 'this') {
419
                if ($this->context->scope === null) {
420
                    throw new RuntimeException("Current {$scope} scope is null");
421
                }
422
423
                if (!$this->context->scope->hasConst($expr->name)) {
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class PHPSA\Definition\ParentDefinition as the method hasConst() does only exist in the following sub-classes of PHPSA\Definition\ParentDefinition: PHPSA\Definition\ClassDefinition, PHPSA\Definition\RuntimeClassDefinition. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
424
                    $this->context->notice(
425
                        'undefined-const',
426
                        sprintf('Constant %s does not exist in %s scope', $expr->name, $scope),
427
                        $expr
428
                    );
429
                }
430
431
                return new CompiledExpression();
432
            }
433
        }
434
435
        $this->context->debug('Unknown const fetch');
436
        return new CompiledExpression();
437
    }
438
439
    /**
440
     * @param Node\Expr\Assign $expr
441
     * @return CompiledExpression
442
     */
443
    protected function passSymbol(Node\Expr\Assign $expr)
444
    {
445
        $expression = new Expression($this->context);
446
        $compiledExpression = $expression->compile($expr->expr);
447
448
        if ($expr->var instanceof Node\Expr\List_) {
449
            $isCorrectType = false;
450
451
            switch ($compiledExpression->getType()) {
452
                case CompiledExpression::ARR:
453
                    $isCorrectType = true;
454
                    break;
455
            }
456
457
            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...
458
                foreach ($expr->var->vars as $key => $var) {
459
                    if ($var instanceof Node\Expr\Variable) {
460
                        $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...
461
462
                        $symbol = $this->context->getSymbol($name);
463
                        if (!$symbol) {
464
                            $symbol = new Variable(
465
                                $name,
0 ignored issues
show
Bug introduced by
It seems like $name defined by $expr->var->name on line 460 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...
466
                                null,
467
                                CompiledExpression::UNKNOWN,
468
                                $this->context->getCurrentBranch()
469
                            );
470
                            $this->context->addVariable($symbol);
471
                        }
472
473
                        if (!$isCorrectType) {
474
                            $symbol->modify(CompiledExpression::NULL, null);
475
                        }
476
477
                        $symbol->incSets();
478
                    }
479
                }
480
            }
481
482
            return new CompiledExpression();
483
        }
484
485
        if ($expr->var instanceof Node\Expr\Variable) {
486
            $name = $expr->var->name;
487
488
            $symbol = $this->context->getSymbol($name);
489
            if ($symbol) {
490
                $symbol->modify($compiledExpression->getType(), $compiledExpression->getValue());
491
            } else {
492
                $symbol = new Variable(
493
                    $name,
0 ignored issues
show
Bug introduced by
It seems like $name defined by $expr->var->name on line 486 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...
494
                    $compiledExpression->getValue(),
495
                    $compiledExpression->getType(),
496
                    $this->context->getCurrentBranch()
497
                );
498
                $this->context->addVariable($symbol);
499
            }
500
501
            $symbol->incSets();
502
            return $compiledExpression;
503
        }
504
505
        if ($expr->var instanceof Node\Expr\PropertyFetch) {
506
            $compiledExpression = $this->compile($expr->var->var);
507
            if ($compiledExpression->getType() == CompiledExpression::OBJECT) {
508
                $objectDefinition = $compiledExpression->getValue();
509
                if ($objectDefinition instanceof ClassDefinition) {
510
                    if (is_string($expr->var->name)) {
511
                        if ($objectDefinition->hasProperty($expr->var->name)) {
512
                            return $this->compile($objectDefinition->getProperty($expr->var->name));
513
                        }
514
                    }
515
                }
516
            }
517
        }
518
519
        $this->context->debug('Unknown how to pass symbol');
520
        return new CompiledExpression();
521
    }
522
523
    /**
524
     * @param Node\Expr\AssignRef $expr
525
     * @return CompiledExpression
526
     */
527
    protected function passSymbolByRef(Node\Expr\AssignRef $expr)
528
    {
529
        if ($expr->var instanceof \PhpParser\Node\Expr\List_) {
530
            return new CompiledExpression();
531
        }
532
533
        if ($expr->var instanceof Node\Expr\Variable) {
534
            $name = $expr->var->name;
535
536
            $expression = new Expression($this->context);
537
            $compiledExpression = $expression->compile($expr->expr);
538
539
            $symbol = $this->context->getSymbol($name);
540
            if ($symbol) {
541
                $symbol->modify($compiledExpression->getType(), $compiledExpression->getValue());
542
543
                if ($expr->expr instanceof Node\Expr\Variable) {
544
                    $rightVarName = $expr->expr->name;
545
546
                    $rightSymbol = $this->context->getSymbol($rightVarName);
547
                    if ($rightSymbol) {
548
                        $rightSymbol->incUse();
549
                        $symbol->setReferencedTo($rightSymbol);
550
                    } else {
551
                        $this->context->debug('Cannot fetch variable by name: ' . $rightVarName);
552
                    }
553
                }
554
555
                $this->context->debug('Unknown how to pass referenced to symbol: ' . get_class($expr->expr));
556
            } else {
557
                $symbol = new Variable(
558
                    $name,
0 ignored issues
show
Bug introduced by
It seems like $name defined by $expr->var->name on line 534 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...
559
                    $compiledExpression->getValue(),
560
                    $compiledExpression->getType(),
561
                    $this->context->getCurrentBranch()
562
                );
563
                $this->context->addVariable($symbol);
564
            }
565
566
            $symbol->incSets();
567
            return $compiledExpression;
568
        }
569
570
        $this->context->debug('Unknown how to pass symbol by ref');
571
        return new CompiledExpression();
572
    }
573
574
    /**
575
     * @param Node\Expr\Variable $expr
576
     * @return CompiledExpression
577
     */
578
    protected function passExprVariable(Node\Expr\Variable $expr)
579
    {
580
        $variable = $this->context->getSymbol($expr->name);
581
        if ($variable) {
582
            $variable->incGets();
583
            return new CompiledExpression($variable->getType(), $variable->getValue());
584
        }
585
586
        $this->context->notice(
587
            'undefined-variable',
588
            sprintf('You trying to use undefined variable $%s', $expr->name),
589
            $expr
590
        );
591
592
        return new CompiledExpression();
593
    }
594
595
    /**
596
     * Compile Array_ expression to CompiledExpression
597
     *
598
     * @param Node\Expr\Array_ $expr
599
     * @return CompiledExpression
600
     */
601 11
    protected function getArray(Node\Expr\Array_ $expr)
602
    {
603 11
        if ($expr->items === array()) {
604 8
            return new CompiledExpression(CompiledExpression::ARR, array());
605
        }
606
607 3
        $resultArray = array();
608
609 3
        foreach ($expr->items as $item) {
610 3
            $compiledValueResult = $this->compile($item->value);
611 3
            if ($item->key) {
612 1
                $compiledKeyResult = $this->compile($item->key);
613 1
                switch ($compiledKeyResult->getType()) {
614 1
                    case CompiledExpression::INTEGER:
615 1
                    case CompiledExpression::DOUBLE:
616 1
                    case CompiledExpression::BOOLEAN:
617 1
                    case CompiledExpression::NULL:
618 1
                    case CompiledExpression::STRING:
619 1
                        $resultArray[$compiledKeyResult->getValue()] = $compiledValueResult->getValue();
620 1
                        break;
621
                    default:
622
                        $this->context->debug("Type {$compiledKeyResult->getType()} is not supported for key value");
623
                        return new CompiledExpression(CompiledExpression::ARR);
624
                        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...
625 1
                }
626 1
            } else {
627 2
                $resultArray[] = $compiledValueResult->getValue();
628
            }
629 3
        }
630
631 3
        return new CompiledExpression(CompiledExpression::ARR, $resultArray);
632
    }
633
634
    /**
635
     * Convert const fetch expr to CompiledExpression
636
     *
637
     * @param Node\Expr\ConstFetch $expr
638
     * @return CompiledExpression
639
     */
640
    protected function constFetch(Node\Expr\ConstFetch $expr)
641
    {
642
        if ($expr->name instanceof Node\Name) {
643
            if ($expr->name->parts[0] === 'true') {
644
                return new CompiledExpression(CompiledExpression::BOOLEAN, true);
645
            }
646
647
            if ($expr->name->parts[0] === 'false') {
648
                return new CompiledExpression(CompiledExpression::BOOLEAN, false);
649
            }
650
        }
651
652
        /**
653
         * @todo Implement check
654
         */
655
656
        $expression = new Expression($this->context);
657
        $compiledExpr = $expression->compile($expr->name);
658
659
        return $compiledExpr;
660
    }
661
}
662