Completed
Push — master ( dde835...fe5d7e )
by Дмитрий
02:46
created

Expression::passConstFetch()   B

Complexity

Conditions 6
Paths 5

Size

Total Lines 25
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42
Metric Value
dl 0
loc 25
ccs 0
cts 16
cp 0
rs 8.439
cc 6
eloc 14
nc 5
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 139 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 'PhpParser\Node\Expr\MethodCall':
44
                return new Expression\MethodCall();
45 353
            case 'PhpParser\Node\Expr\FuncCall':
46
                return new Expression\FunctionCall();
47 353
            case 'PhpParser\Node\Expr\StaticCall':
48
                return new Expression\StaticCall();
49
            /**
50
             * Operators
51
             */
52 353
            case 'PhpParser\Node\Expr\New_':
53
                return new Expression\Operators\NewOp();
54 353
            case 'PhpParser\Node\Expr\Instanceof_':
55
                return new Expression\Operators\InstanceOfOp();
56 353
            case 'PhpParser\Node\Expr\BinaryOp\Identical':
57 28
                return new Expression\BinaryOp\Identical();
58 325
            case 'PhpParser\Node\Expr\BinaryOp\Concat':
59
                return new Expression\Operators\Contact();
60 325
            case 'PhpParser\Node\Expr\BinaryOp\NotIdentical':
61 14
                return new Expression\BinaryOp\NotIdentical();
62 311
            case 'PhpParser\Node\Expr\BinaryOp\Equal':
63 34
                return new Expression\BinaryOp\Equal();
64 277
            case 'PhpParser\Node\Expr\BinaryOp\NotEqual':
65 17
                return new Expression\BinaryOp\NotEqual();
66
            /**
67
             * @link http://php.net/manual/en/language.operators.increment.php
68
             */
69 260
            case 'PhpParser\Node\Expr\PostInc':
70 4
                return new Expression\Operators\PostInc();
71 256
            case 'PhpParser\Node\Expr\PostDec':
72 4
                return new Expression\Operators\PostDec();
73
            /**
74
             * Arithmetical
75
             */
76 252
            case 'PhpParser\Node\Expr\BinaryOp\Div':
77 37
                return new Expression\Operators\Arithmetical\Div();
78 215
            case 'PhpParser\Node\Expr\BinaryOp\Plus':
79 41
                return new Expression\Operators\Arithmetical\Plus();
80 174
            case 'PhpParser\Node\Expr\BinaryOp\Minus':
81 18
                return new Expression\Operators\Arithmetical\Minus();
82 156
            case 'PhpParser\Node\Expr\BinaryOp\Mul':
83 35
                return new Expression\Operators\Arithmetical\Mul();
84 121
            case 'PhpParser\Node\Expr\BinaryOp\Mod':
85 35
                return new Expression\Operators\Arithmetical\Mod();
86
            /**
87
             * Bitwise
88
             * @link http://php.net/manual/ru/language.operators.bitwise.php
89
             */
90 86
            case 'PhpParser\Node\Expr\BinaryOp\BitwiseOr':
91
                return new Expression\Operators\Bitwise\BitwiseOr();
92 86
            case 'PhpParser\Node\Expr\BinaryOp\BitwiseXor':
93
                return new Expression\Operators\Bitwise\BitwiseXor();
94 86
            case 'PhpParser\Node\Expr\BinaryOp\BitwiseAnd':
95
                return new Expression\Operators\Bitwise\BitwiseAnd();
96 86
            case 'PhpParser\Node\Expr\BinaryOp\ShiftRight':
97
                return new Expression\Operators\Bitwise\ShiftRight();
98 86
            case 'PhpParser\Node\Expr\BinaryOp\ShiftLeft':
99
                return new Expression\Operators\Bitwise\ShiftLeft();
100 86
            case 'PhpParser\Node\Expr\BitwiseNot':
101
                return new Expression\Operators\Bitwise\BitwiseNot();
102
            /**
103
             * Logical
104
             */
105 86
            case 'PhpParser\Node\Expr\BinaryOp\BooleanOr':
106 10
                return new Expression\Operators\Logical\BooleanOr();
107 76
            case 'PhpParser\Node\Expr\BinaryOp\BooleanAnd':
108 5
                return new Expression\Operators\Logical\BooleanAnd();
109 71
            case 'PhpParser\Node\Expr\BooleanNot':
110 5
                return new Expression\Operators\Logical\BooleanNot();
111
            /**
112
             * Comparison
113
             */
114 66
            case 'PhpParser\Node\Expr\BinaryOp\Greater':
115 12
                return new Expression\Operators\Comparison\Greater();
116 54
            case 'PhpParser\Node\Expr\BinaryOp\GreaterOrEqual':
117 12
                return new Expression\Operators\Comparison\GreaterOrEqual();
118 42
            case 'PhpParser\Node\Expr\BinaryOp\Smaller':
119 12
                return new Expression\Operators\Comparison\Smaller();
120 30
            case 'PhpParser\Node\Expr\BinaryOp\SmallerOrEqual':
121 12
                return new Expression\Operators\Comparison\SmallerOrEqual();
122
            /**
123
             * Another
124
             */
125 18
            case 'PhpParser\Node\Expr\UnaryMinus':
126 9
                return new Expression\Operators\UnaryMinus();
127 9
            case 'PhpParser\Node\Expr\UnaryPlus':
128 9
                return new Expression\Operators\UnaryPlus();
129
        }
130
131
        return false;
132
    }
133
134
    /**
135
     * @param object|string $expr
136
     * @return CompiledExpression
137
     */
138 363
    public function compile($expr)
0 ignored issues
show
Complexity introduced by
This operation has 320 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...
139
    {
140 363
        if (is_string($expr)) {
141
            return new CompiledExpression(CompiledExpression::STRING, $expr);
142
        }
143
144 363
        if (!is_object($expr)) {
145
            throw new InvalidArgumentException('$expr must be string/object');
146
        }
147
148 363
        $className = get_class($expr);
149
        switch ($className) {
150 363
            case 'PhpParser\Node\Expr\PropertyFetch':
151
                return $this->passPropertyFetch($expr);
152 363
            case 'PhpParser\Node\Stmt\Property':
153
                return $this->passProperty($expr);
154 363
            case 'PhpParser\Node\Expr\ClassConstFetch':
155
                return $this->passConstFetch($expr);
156 363
            case 'PhpParser\Node\Expr\Assign':
157
                return $this->passSymbol($expr);
158 363
            case 'PhpParser\Node\Expr\AssignRef':
159
                return $this->passSymbolByRef($expr);
160 363
            case 'PhpParser\Node\Expr\Variable':
161
                return $this->passExprVariable($expr);
162
            /**
163
             * Cast operators
164
             */
165 363
            case 'PhpParser\Node\Expr\Cast\Bool_':
166
                return $this->passCastBoolean($expr);
167 363
            case 'PhpParser\Node\Expr\Cast\Int_':
168
                return $this->passCastInt($expr);
169 363
            case 'PhpParser\Node\Expr\Cast\Double':
170
                return $this->passCastFloat($expr);
171 363
            case 'PhpParser\Node\Expr\Cast\String_':
172
                return $this->passCastString($expr);
173 363
            case 'PhpParser\Node\Expr\Cast\Unset_':
174
                return $this->passCastUnset($expr);
175
            /**
176
             * Expressions
177
             */
178 363
            case 'PhpParser\Node\Expr\Array_':
179 11
                return $this->getArray($expr);
180 362
            case 'PhpParser\Node\Expr\ConstFetch':
181
                return $this->constFetch($expr);
182 362
            case 'PhpParser\Node\Name':
183
                return $this->getNodeName($expr);
184
            /**
185
             * Simple Scalar(s)
186
             */
187 362
            case 'PHPSA\Node\Scalar\Nil':
188 8
                return new CompiledExpression(CompiledExpression::NULL);
189 361
            case 'PhpParser\Node\Scalar\LNumber':
190 256
                return new CompiledExpression(CompiledExpression::INTEGER, $expr->value);
191 358
            case 'PhpParser\Node\Scalar\DNumber':
192 131
                return new CompiledExpression(CompiledExpression::DOUBLE, $expr->value);
193 357
            case 'PhpParser\Node\Scalar\String_':
194 8
                return new CompiledExpression(CompiledExpression::STRING, $expr->value);
195 355
            case 'PHPSA\Node\Scalar\Boolean':
196 60
                return new CompiledExpression(CompiledExpression::BOOLEAN, $expr->value);
197 353
            case 'PHPSA\Node\Scalar\Fake':
198 29
                return new CompiledExpression($expr->type, $expr->value);
199
        }
200
201 353
        $expressionCompiler = $this->factory($expr);
202 353
        if (!$expressionCompiler) {
203
            $this->context->debug("Expression compiler is not implemented for {$className}");
204
            return new CompiledExpression(CompiledExpression::UNIMPLEMENTED);
205
        }
206
207 353
        $result = $expressionCompiler->pass($expr, $this->context);
208 353
        if (!$result instanceof CompiledExpression) {
209
            throw new RuntimeException('Please return CompiledExpression from ' . get_class($expressionCompiler));
210
        }
211
212 353
        return $result;
213
    }
214
215
    /**
216
     * @todo Implement
217
     *
218
     * @param Node\Stmt\Property $st
219
     * @return CompiledExpression
220
     */
221
    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...
222
    {
223
        return new CompiledExpression();
224
    }
225
226
    /**
227
     * @param Node\Expr\Variable $expr
228
     * @return CompiledExpression
229
     */
230
    public function declareVariable(Node\Expr\Variable $expr)
231
    {
232
        $variable = $this->context->getSymbol($expr->name);
233
        if ($variable) {
234
            $variable->incGets();
235
            return new CompiledExpression($variable->getType(), $variable->getValue(), $variable);
236
        }
237
238
        $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...
239
        $this->context->addVariable($symbol);
240
241
        return new CompiledExpression;
242
    }
243
244
    /**
245
     * @param Node\Name $expr
246
     * @return CompiledExpression
247
     */
248
    public function getNodeName(Node\Name $expr)
249
    {
250
        if ($expr->parts[0] === 'null') {
251
            return new CompiledExpression(CompiledExpression::NULL);
252
        }
253
254
        if (in_array($expr, ['self', 'static'])) {
255
            $scopePointer = $this->context->scopePointer;
256
            if ($scopePointer) {
257
                if ($scopePointer->isClassMethod()) {
258
                    $classMethod = $scopePointer->getObject();
259
                    if ($classMethod && $classMethod->isStatic()) {
260
                        return CompiledExpression::fromZvalValue($this->context->scope);
261
                    }
262
                }
263
            }
264
        }
265
266
        $this->context->debug('[Unknown] How to get Node\Name for ' . $expr);
267
        return new CompiledExpression();
268
    }
269
270
    /**
271
     * (bool) {$expr}
272
     *
273
     * @param Node\Expr\Cast\Bool_ $expr
274
     * @return CompiledExpression
275
     */
276
    protected function passCastBoolean(Node\Expr\Cast\Bool_ $expr)
277
    {
278
        $expression = new Expression($this->context);
279
        $compiledExpression = $expression->compile($expr->expr);
280
281
        switch ($compiledExpression->getType()) {
282
            case CompiledExpression::BOOLEAN:
283
                $this->context->notice('stupid-cast', "You are trying to cast 'boolean' to 'boolean'", $expr);
284
                return $compiledExpression;
285
            case CompiledExpression::DOUBLE:
286
            case CompiledExpression::INTEGER:
287
                return new CompiledExpression(CompiledExpression::BOOLEAN, (bool) $compiledExpression->getValue());
288
        }
289
290
        return new CompiledExpression();
291
    }
292
293
    /**
294
     * (int) {$expr}
295
     *
296
     * @param Node\Expr\Cast\Int_ $expr
297
     * @return CompiledExpression
298
     */
299
    protected function passCastInt(Node\Expr\Cast\Int_ $expr)
300
    {
301
        $expression = new Expression($this->context);
302
        $compiledExpression = $expression->compile($expr->expr);
303
304
        switch ($compiledExpression->getType()) {
305
            case CompiledExpression::INTEGER:
306
                $this->context->notice('stupid-cast', "You are trying to cast 'int' to 'int'", $expr);
307
                return $compiledExpression;
308
            case CompiledExpression::BOOLEAN:
309
            case CompiledExpression::DOUBLE:
310
            case CompiledExpression::STRING:
311
                return new CompiledExpression(CompiledExpression::INTEGER, (int) $compiledExpression->getValue());
312
        }
313
314
        return new CompiledExpression();
315
    }
316
317
    /**
318
     * (float) {$expr}
319
     *
320
     * @param Node\Expr\Cast\Double $expr
321
     * @return CompiledExpression
322
     */
323
    protected function passCastFloat(Node\Expr\Cast\Double $expr)
324
    {
325
        $expression = new Expression($this->context);
326
        $compiledExpression = $expression->compile($expr->expr);
327
328
        switch ($compiledExpression->getType()) {
329
            case CompiledExpression::DOUBLE:
330
                $this->context->notice('stupid-cast', "You are trying to cast 'float' to 'float'", $expr);
331
                return $compiledExpression;
332
            case CompiledExpression::BOOLEAN:
333
            case CompiledExpression::INTEGER:
334
            case CompiledExpression::STRING:
335
                return new CompiledExpression(CompiledExpression::DOUBLE, (float) $compiledExpression->getValue());
336
        }
337
338
        return new CompiledExpression();
339
    }
340
341
    /**
342
     * (string) {$expr}
343
     *
344
     * @param Node\Expr\Cast\String_ $expr
345
     * @return CompiledExpression
346
     */
347
    protected function passCastString(Node\Expr\Cast\String_ $expr)
348
    {
349
        $expression = new Expression($this->context);
350
        $compiledExpression = $expression->compile($expr->expr);
351
352
        switch ($compiledExpression->getType()) {
353
            case CompiledExpression::STRING:
354
                $this->context->notice('stupid-cast', "You are trying to cast 'string' to 'string'", $expr);
355
                return $compiledExpression;
356
            case CompiledExpression::BOOLEAN:
357
            case CompiledExpression::INTEGER:
358
            case CompiledExpression::DOUBLE:
359
                return new CompiledExpression(CompiledExpression::DOUBLE, (string) $compiledExpression->getValue());
360
        }
361
362
        return new CompiledExpression();
363
    }
364
365
    /**
366
     * (unset) {$expr}
367
     *
368
     * @param Node\Expr\Cast\Unset_ $expr
369
     * @return CompiledExpression
370
     */
371
    protected function passCastUnset(Node\Expr\Cast\Unset_ $expr)
372
    {
373
        $expression = new Expression($this->context);
374
        $compiledExpression = $expression->compile($expr->expr);
375
376
        switch ($compiledExpression->getType()) {
377
            case CompiledExpression::NULL:
378
                $this->context->notice('stupid-cast', "You are trying to cast 'unset' to 'null'", $expr);
379
                return $compiledExpression;
380
        }
381
382
        return new CompiledExpression(CompiledExpression::NULL, null);
383
    }
384
385
    /**
386
     * @param Node\Expr\PropertyFetch $expr
387
     * @return CompiledExpression
388
     */
389
    protected function passPropertyFetch(Node\Expr\PropertyFetch $expr)
390
    {
391
        $propertNameCE = $this->compile($expr->name);
392
393
        $scopeExpression = $this->compile($expr->var);
394
        if ($scopeExpression->isObject()) {
395
            $scopeExpressionValue = $scopeExpression->getValue();
396
            if ($scopeExpressionValue instanceof ClassDefinition) {
397
                $propertyName = $propertNameCE->isString() ? $propertNameCE->getValue() : false;
398
                if ($propertyName) {
399
                    if ($scopeExpressionValue->hasProperty($propertyName, true)) {
400
                        $property = $scopeExpressionValue->getProperty($propertyName, true);
401
                        return $this->compile($property);
402
                    } else {
403
                        $this->context->notice(
404
                            'undefined-property',
405
                            sprintf(
406
                                'Property %s does not exist in %s scope',
407
                                $propertyName,
408
                                $scopeExpressionValue->getName()
409
                            ),
410
                            $expr
411
                        );
412
                    }
413
                }
414
            }
415
        } elseif (!$scopeExpression->canBeObject()) {
416
            return new CompiledExpression(CompiledExpression::UNKNOWN);
417
        }
418
419
        $this->context->notice(
420
            'property-fetch-on-non-object',
421
            "It's not possible to fetch property on not object",
422
            $expr,
423
            Check::CHECK_BETA
424
        );
425
426
        return new CompiledExpression(CompiledExpression::UNKNOWN);
427
    }
428
429
    /**
430
     * @param Node\Expr\ClassConstFetch $expr
431
     * @return CompiledExpression
432
     */
433
    protected function passConstFetch(Node\Expr\ClassConstFetch $expr)
434
    {
435
        if ($expr->class instanceof Node\Name) {
436
            $scope = $expr->class->parts[0];
437
438
            if ($scope == 'self' || $scope == 'this') {
439
                if ($this->context->scope === null) {
440
                    throw new RuntimeException("Current {$scope} scope is null");
441
                }
442
443
                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...
444
                    $this->context->notice(
445
                        'undefined-const',
446
                        sprintf('Constant %s does not exist in %s scope', $expr->name, $scope),
447
                        $expr
448
                    );
449
                }
450
451
                return new CompiledExpression();
452
            }
453
        }
454
455
        $this->context->debug('Unknown const fetch');
456
        return new CompiledExpression();
457
    }
458
459
    /**
460
     * @param Node\Expr\Assign $expr
461
     * @return CompiledExpression
462
     */
463
    protected function passSymbol(Node\Expr\Assign $expr)
464
    {
465
        $expression = new Expression($this->context);
466
        $compiledExpression = $expression->compile($expr->expr);
467
468
        if ($expr->var instanceof Node\Expr\List_) {
469
            $isCorrectType = false;
470
471
            switch ($compiledExpression->getType()) {
472
                case CompiledExpression::ARR:
473
                    $isCorrectType = true;
474
                    break;
475
            }
476
477
            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...
478
                foreach ($expr->var->vars as $key => $var) {
479
                    if ($var instanceof Node\Expr\Variable) {
480
                        $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...
481
482
                        $symbol = $this->context->getSymbol($name);
483
                        if (!$symbol) {
484
                            $symbol = new Variable(
485
                                $name,
0 ignored issues
show
Bug introduced by
It seems like $name defined by $expr->var->name on line 480 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...
486
                                null,
487
                                CompiledExpression::UNKNOWN,
488
                                $this->context->getCurrentBranch()
489
                            );
490
                            $this->context->addVariable($symbol);
491
                        }
492
493
                        if (!$isCorrectType) {
494
                            $symbol->modify(CompiledExpression::NULL, null);
495
                        }
496
497
                        $symbol->incSets();
498
                    }
499
                }
500
            }
501
502
            return new CompiledExpression();
503
        }
504
505
        if ($expr->var instanceof Node\Expr\Variable) {
506
            $name = $expr->var->name;
507
508
            $symbol = $this->context->getSymbol($name);
509
            if ($symbol) {
510
                $symbol->modify($compiledExpression->getType(), $compiledExpression->getValue());
511
            } else {
512
                $symbol = new Variable(
513
                    $name,
0 ignored issues
show
Bug introduced by
It seems like $name defined by $expr->var->name on line 506 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...
514
                    $compiledExpression->getValue(),
515
                    $compiledExpression->getType(),
516
                    $this->context->getCurrentBranch()
517
                );
518
                $this->context->addVariable($symbol);
519
            }
520
521
            $symbol->incSets();
522
            return $compiledExpression;
523
        }
524
525
        if ($expr->var instanceof Node\Expr\PropertyFetch) {
526
            $compiledExpression = $this->compile($expr->var->var);
527
            if ($compiledExpression->getType() == CompiledExpression::OBJECT) {
528
                $objectDefinition = $compiledExpression->getValue();
529
                if ($objectDefinition instanceof ClassDefinition) {
530
                    if (is_string($expr->var->name)) {
531
                        if ($objectDefinition->hasProperty($expr->var->name)) {
532
                            return $this->compile($objectDefinition->getProperty($expr->var->name));
533
                        }
534
                    }
535
                }
536
            }
537
        }
538
539
        $this->context->debug('Unknown how to pass symbol');
540
        return new CompiledExpression();
541
    }
542
543
    /**
544
     * @param Node\Expr\AssignRef $expr
545
     * @return CompiledExpression
546
     */
547
    protected function passSymbolByRef(Node\Expr\AssignRef $expr)
548
    {
549
        if ($expr->var instanceof \PhpParser\Node\Expr\List_) {
550
            return new CompiledExpression();
551
        }
552
553
        if ($expr->var instanceof Node\Expr\Variable) {
554
            $name = $expr->var->name;
555
556
            $expression = new Expression($this->context);
557
            $compiledExpression = $expression->compile($expr->expr);
558
559
            $symbol = $this->context->getSymbol($name);
560
            if ($symbol) {
561
                $symbol->modify($compiledExpression->getType(), $compiledExpression->getValue());
562
563
                if ($expr->expr instanceof Node\Expr\Variable) {
564
                    $rightVarName = $expr->expr->name;
565
566
                    $rightSymbol = $this->context->getSymbol($rightVarName);
567
                    if ($rightSymbol) {
568
                        $rightSymbol->incUse();
569
                        $symbol->setReferencedTo($rightSymbol);
570
                    } else {
571
                        $this->context->debug('Cannot fetch variable by name: ' . $rightVarName);
572
                    }
573
                }
574
575
                $this->context->debug('Unknown how to pass referenced to symbol: ' . get_class($expr->expr));
576
            } else {
577
                $symbol = new Variable(
578
                    $name,
0 ignored issues
show
Bug introduced by
It seems like $name defined by $expr->var->name on line 554 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...
579
                    $compiledExpression->getValue(),
580
                    $compiledExpression->getType(),
581
                    $this->context->getCurrentBranch()
582
                );
583
                $this->context->addVariable($symbol);
584
            }
585
586
            $symbol->incSets();
587
            return $compiledExpression;
588
        }
589
590
        $this->context->debug('Unknown how to pass symbol by ref');
591
        return new CompiledExpression();
592
    }
593
594
    /**
595
     * @param Node\Expr\Variable $expr
596
     * @return CompiledExpression
597
     */
598
    protected function passExprVariable(Node\Expr\Variable $expr)
599
    {
600
        $variable = $this->context->getSymbol($expr->name);
601
        if ($variable) {
602
            $variable->incGets();
603
            return new CompiledExpression($variable->getType(), $variable->getValue(), $variable);
604
        }
605
606
        $this->context->notice(
607
            'undefined-variable',
608
            sprintf('You trying to use undefined variable $%s', $expr->name),
609
            $expr
610
        );
611
612
        return new CompiledExpression();
613
    }
614
615
    /**
616
     * Compile Array_ expression to CompiledExpression
617
     *
618
     * @param Node\Expr\Array_ $expr
619
     * @return CompiledExpression
620
     */
621 11
    protected function getArray(Node\Expr\Array_ $expr)
622
    {
623 11
        if ($expr->items === array()) {
624 8
            return new CompiledExpression(CompiledExpression::ARR, array());
625
        }
626
627 3
        $resultArray = array();
628
629 3
        foreach ($expr->items as $item) {
630 3
            $compiledValueResult = $this->compile($item->value);
631 3
            if ($item->key) {
632 1
                $compiledKeyResult = $this->compile($item->key);
633 1
                switch ($compiledKeyResult->getType()) {
634 1
                    case CompiledExpression::INTEGER:
635 1
                    case CompiledExpression::DOUBLE:
636 1
                    case CompiledExpression::BOOLEAN:
637 1
                    case CompiledExpression::NULL:
638 1
                    case CompiledExpression::STRING:
639 1
                        $resultArray[$compiledKeyResult->getValue()] = $compiledValueResult->getValue();
640 1
                        break;
641
                    default:
642
                        $this->context->debug("Type {$compiledKeyResult->getType()} is not supported for key value");
643
                        return new CompiledExpression(CompiledExpression::ARR);
644
                        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...
645 1
                }
646 1
            } else {
647 2
                $resultArray[] = $compiledValueResult->getValue();
648
            }
649 3
        }
650
651 3
        return new CompiledExpression(CompiledExpression::ARR, $resultArray);
652
    }
653
654
    /**
655
     * Convert const fetch expr to CompiledExpression
656
     *
657
     * @param Node\Expr\ConstFetch $expr
658
     * @return CompiledExpression
659
     */
660
    protected function constFetch(Node\Expr\ConstFetch $expr)
661
    {
662
        if ($expr->name instanceof Node\Name) {
663
            if ($expr->name->parts[0] === 'true') {
664
                return new CompiledExpression(CompiledExpression::BOOLEAN, true);
665
            }
666
667
            if ($expr->name->parts[0] === 'false') {
668
                return new CompiledExpression(CompiledExpression::BOOLEAN, false);
669
            }
670
        }
671
672
        /**
673
         * @todo Implement check
674
         */
675
676
        $expression = new Expression($this->context);
677
        $compiledExpr = $expression->compile($expr->name);
678
679
        return $compiledExpr;
680
    }
681
}
682