Skip to content

Commit 325e8f1

Browse files
authored
Merge branch refs/heads/2.1.x into 2.2.x
2 parents 67951c3 + 456275f commit 325e8f1

File tree

206 files changed

+6465
-12602
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

206 files changed

+6465
-12602
lines changed

phpstan-baseline.neon

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@ parameters:
3030
count: 1
3131
path: src/Analyser/ExprHandler/BooleanNotHandler.php
3232

33+
-
34+
rawMessage: 'Only numeric types are allowed in pre-increment, float|int|string|null given.'
35+
identifier: preInc.nonNumeric
36+
count: 1
37+
path: src/Analyser/ExprHandler/PreIncHandler.php
38+
3339
-
3440
rawMessage: Cannot assign offset 'realCount' to array<mixed>|string.
3541
identifier: offsetAssign.dimType
@@ -54,12 +60,6 @@ parameters:
5460
count: 2
5561
path: src/Analyser/MutatingScope.php
5662

57-
-
58-
rawMessage: 'Only numeric types are allowed in pre-increment, float|int|string|null given.'
59-
identifier: preInc.nonNumeric
60-
count: 1
61-
path: src/Analyser/MutatingScope.php
62-
6363
-
6464
rawMessage: 'Parameter #2 $node of method PHPStan\BetterReflection\SourceLocator\Ast\Strategy\NodeToReflection::__invoke() expects PhpParser\Node\Expr\ArrowFunction|PhpParser\Node\Expr\Closure|PhpParser\Node\Expr\FuncCall|PhpParser\Node\Stmt\Class_|PhpParser\Node\Stmt\Const_|PhpParser\Node\Stmt\Enum_|PhpParser\Node\Stmt\Function_|PhpParser\Node\Stmt\Interface_|PhpParser\Node\Stmt\Trait_, PhpParser\Node\Stmt\ClassLike given.'
6565
identifier: argument.type

src/Analyser/ExprHandler/ArrayDimFetchHandler.php

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,24 @@
33
namespace PHPStan\Analyser\ExprHandler;
44

55
use ArrayAccess;
6+
use PhpParser\Node\Arg;
67
use PhpParser\Node\Expr;
78
use PhpParser\Node\Expr\ArrayDimFetch;
89
use PhpParser\Node\Expr\MethodCall;
10+
use PhpParser\Node\Identifier;
911
use PhpParser\Node\Stmt;
1012
use PHPStan\Analyser\ExpressionContext;
1113
use PHPStan\Analyser\ExpressionResult;
1214
use PHPStan\Analyser\ExpressionResultStorage;
1315
use PHPStan\Analyser\ExprHandler;
16+
use PHPStan\Analyser\ExprHandler\Helper\NullsafeShortCircuitingHelper;
1417
use PHPStan\Analyser\MutatingScope;
1518
use PHPStan\Analyser\NodeScopeResolver;
1619
use PHPStan\Analyser\NoopNodeCallback;
1720
use PHPStan\DependencyInjection\AutowiredService;
21+
use PHPStan\Type\NeverType;
1822
use PHPStan\Type\ObjectType;
23+
use PHPStan\Type\Type;
1924
use function array_merge;
2025

2126
/**
@@ -30,6 +35,47 @@ public function supports(Expr $expr): bool
3035
return $expr instanceof ArrayDimFetch;
3136
}
3237

38+
/**
39+
* @param ArrayDimFetch $expr
40+
*/
41+
public function resolveType(MutatingScope $scope, Expr $expr): Type
42+
{
43+
if ($expr->dim === null) {
44+
return new NeverType();
45+
}
46+
47+
$offsetAccessibleType = $scope->getType($expr->var);
48+
if ($offsetAccessibleType instanceof NeverType) {
49+
return NullsafeShortCircuitingHelper::getType($scope, $expr->var, $offsetAccessibleType);
50+
}
51+
52+
if (
53+
!$offsetAccessibleType->isArray()->yes()
54+
&& (new ObjectType(ArrayAccess::class))->isSuperTypeOf($offsetAccessibleType)->yes()
55+
) {
56+
return NullsafeShortCircuitingHelper::getType(
57+
$scope,
58+
$expr->var,
59+
$scope->getType(
60+
new MethodCall(
61+
$expr->var,
62+
new Identifier('offsetGet'),
63+
[
64+
new Arg($expr->dim),
65+
],
66+
),
67+
),
68+
);
69+
}
70+
71+
$offsetType = $scope->getType($expr->dim);
72+
return NullsafeShortCircuitingHelper::getType(
73+
$scope,
74+
$expr->var,
75+
$offsetAccessibleType->getOffsetValueType($offsetType),
76+
);
77+
}
78+
3379
public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Expr $expr, MutatingScope $scope, ExpressionResultStorage $storage, callable $nodeCallback, ExpressionContext $context): ExpressionResult
3480
{
3581
$hasYield = false;

src/Analyser/ExprHandler/BooleanAndHandler.php

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,23 @@
44

55
use PhpParser\Node\Expr;
66
use PhpParser\Node\Expr\BinaryOp\BooleanAnd;
7+
use PhpParser\Node\Expr\BinaryOp\BooleanOr;
78
use PhpParser\Node\Expr\BinaryOp\LogicalAnd;
9+
use PhpParser\Node\Expr\BinaryOp\LogicalOr;
810
use PhpParser\Node\Stmt;
911
use PHPStan\Analyser\ExpressionContext;
1012
use PHPStan\Analyser\ExpressionResult;
1113
use PHPStan\Analyser\ExpressionResultStorage;
1214
use PHPStan\Analyser\ExprHandler;
1315
use PHPStan\Analyser\MutatingScope;
1416
use PHPStan\Analyser\NodeScopeResolver;
17+
use PHPStan\Analyser\NoopNodeCallback;
1518
use PHPStan\DependencyInjection\AutowiredService;
1619
use PHPStan\Node\BooleanAndNode;
20+
use PHPStan\Type\BooleanType;
21+
use PHPStan\Type\Constant\ConstantBooleanType;
1722
use PHPStan\Type\NeverType;
23+
use PHPStan\Type\Type;
1824
use function array_merge;
1925

2026
/**
@@ -24,11 +30,64 @@
2430
final class BooleanAndHandler implements ExprHandler
2531
{
2632

33+
private const BOOLEAN_EXPRESSION_MAX_PROCESS_DEPTH = 4;
34+
35+
public function __construct(
36+
private NodeScopeResolver $nodeScopeResolver,
37+
)
38+
{
39+
}
40+
2741
public function supports(Expr $expr): bool
2842
{
2943
return $expr instanceof BooleanAnd || $expr instanceof LogicalAnd;
3044
}
3145

46+
/**
47+
* @param BooleanAnd|LogicalAnd $expr
48+
*/
49+
public function resolveType(MutatingScope $scope, Expr $expr): Type
50+
{
51+
$leftBooleanType = $scope->getType($expr->left)->toBoolean();
52+
if ($leftBooleanType->isFalse()->yes()) {
53+
return new ConstantBooleanType(false);
54+
}
55+
56+
if (self::getBooleanExpressionDepth($expr->left) <= self::BOOLEAN_EXPRESSION_MAX_PROCESS_DEPTH) {
57+
$leftResult = $this->nodeScopeResolver->processExprNode(new Stmt\Expression($expr->left), $expr->left, $scope, new ExpressionResultStorage(), new NoopNodeCallback(), ExpressionContext::createDeep());
58+
$rightBooleanType = $leftResult->getTruthyScope()->getType($expr->right)->toBoolean();
59+
} else {
60+
$rightBooleanType = $scope->filterByTruthyValue($expr->left)->getType($expr->right)->toBoolean();
61+
}
62+
63+
if ($rightBooleanType->isFalse()->yes()) {
64+
return new ConstantBooleanType(false);
65+
}
66+
67+
if (
68+
$leftBooleanType->isTrue()->yes()
69+
&& $rightBooleanType->isTrue()->yes()
70+
) {
71+
return new ConstantBooleanType(true);
72+
}
73+
74+
return new BooleanType();
75+
}
76+
77+
public static function getBooleanExpressionDepth(Expr $expr, int $depth = 0): int
78+
{
79+
while (
80+
$expr instanceof BooleanOr
81+
|| $expr instanceof LogicalOr
82+
|| $expr instanceof BooleanAnd
83+
|| $expr instanceof LogicalAnd
84+
) {
85+
return self::getBooleanExpressionDepth($expr->left, $depth + 1);
86+
}
87+
88+
return $depth;
89+
}
90+
3291
public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Expr $expr, MutatingScope $scope, ExpressionResultStorage $storage, callable $nodeCallback, ExpressionContext $context): ExpressionResult
3392
{
3493
$leftResult = $nodeScopeResolver->processExprNode($stmt, $expr->left, $scope, $storage, $nodeCallback, $context->enterDeep());

src/Analyser/ExprHandler/BooleanOrHandler.php

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,13 @@
1212
use PHPStan\Analyser\ExprHandler;
1313
use PHPStan\Analyser\MutatingScope;
1414
use PHPStan\Analyser\NodeScopeResolver;
15+
use PHPStan\Analyser\NoopNodeCallback;
1516
use PHPStan\DependencyInjection\AutowiredService;
1617
use PHPStan\Node\BooleanOrNode;
18+
use PHPStan\Type\BooleanType;
19+
use PHPStan\Type\Constant\ConstantBooleanType;
1720
use PHPStan\Type\NeverType;
21+
use PHPStan\Type\Type;
1822
use function array_merge;
1923

2024
/**
@@ -24,11 +28,50 @@
2428
final class BooleanOrHandler implements ExprHandler
2529
{
2630

31+
private const BOOLEAN_EXPRESSION_MAX_PROCESS_DEPTH = 4;
32+
33+
public function __construct(
34+
private NodeScopeResolver $nodeScopeResolver,
35+
)
36+
{
37+
}
38+
2739
public function supports(Expr $expr): bool
2840
{
2941
return $expr instanceof BooleanOr || $expr instanceof LogicalOr;
3042
}
3143

44+
/**
45+
* @param BooleanOr|LogicalOr $expr
46+
*/
47+
public function resolveType(MutatingScope $scope, Expr $expr): Type
48+
{
49+
$leftBooleanType = $scope->getType($expr->left)->toBoolean();
50+
if ($leftBooleanType->isTrue()->yes()) {
51+
return new ConstantBooleanType(true);
52+
}
53+
54+
if (BooleanAndHandler::getBooleanExpressionDepth($expr->left) <= self::BOOLEAN_EXPRESSION_MAX_PROCESS_DEPTH) {
55+
$leftResult = $this->nodeScopeResolver->processExprNode(new Stmt\Expression($expr->left), $expr->left, $scope, new ExpressionResultStorage(), new NoopNodeCallback(), ExpressionContext::createDeep());
56+
$rightBooleanType = $leftResult->getFalseyScope()->getType($expr->right)->toBoolean();
57+
} else {
58+
$rightBooleanType = $scope->filterByFalseyValue($expr->left)->getType($expr->right)->toBoolean();
59+
}
60+
61+
if ($rightBooleanType->isTrue()->yes()) {
62+
return new ConstantBooleanType(true);
63+
}
64+
65+
if (
66+
$leftBooleanType->isFalse()->yes()
67+
&& $rightBooleanType->isFalse()->yes()
68+
) {
69+
return new ConstantBooleanType(false);
70+
}
71+
72+
return new BooleanType();
73+
}
74+
3275
public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Expr $expr, MutatingScope $scope, ExpressionResultStorage $storage, callable $nodeCallback, ExpressionContext $context): ExpressionResult
3376
{
3477
$leftResult = $nodeScopeResolver->processExprNode($stmt, $expr->left, $scope, $storage, $nodeCallback, $context->enterDeep());

src/Analyser/ExprHandler/CoalesceHandler.php

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
use PHPStan\Analyser\NodeScopeResolver;
1515
use PHPStan\DependencyInjection\AutowiredService;
1616
use PHPStan\Type\NeverType;
17+
use PHPStan\Type\Type;
18+
use PHPStan\Type\TypeCombinator;
1719
use function array_merge;
1820

1921
/**
@@ -34,6 +36,38 @@ public function supports(Expr $expr): bool
3436
return $expr instanceof Coalesce;
3537
}
3638

39+
/**
40+
* @param Coalesce $expr
41+
*/
42+
public function resolveType(MutatingScope $scope, Expr $expr): Type
43+
{
44+
$issetLeftExpr = new Expr\Isset_([$expr->left]);
45+
46+
$result = $scope->issetCheck($expr->left, static function (Type $type): ?bool {
47+
$isNull = $type->isNull();
48+
if ($isNull->maybe()) {
49+
return null;
50+
}
51+
52+
return !$isNull->yes();
53+
});
54+
55+
if ($result !== null && $result !== false) {
56+
return TypeCombinator::removeNull($scope->filterByTruthyValue($issetLeftExpr)->getType($expr->left));
57+
}
58+
59+
$rightType = $scope->filterByFalseyValue($issetLeftExpr)->getType($expr->right);
60+
61+
if ($result === null) {
62+
return TypeCombinator::union(
63+
TypeCombinator::removeNull($scope->filterByTruthyValue($issetLeftExpr)->getType($expr->left)),
64+
$rightType,
65+
);
66+
}
67+
68+
return $rightType;
69+
}
70+
3771
public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Expr $expr, MutatingScope $scope, ExpressionResultStorage $storage, callable $nodeCallback, ExpressionContext $context): ExpressionResult
3872
{
3973
$nonNullabilityResult = $this->nonNullabilityHelper->ensureNonNullability($scope, $expr->left);

src/Analyser/ExprHandler/EvalHandler.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
use PHPStan\Analyser\MutatingScope;
1515
use PHPStan\Analyser\NodeScopeResolver;
1616
use PHPStan\DependencyInjection\AutowiredService;
17+
use PHPStan\Type\MixedType;
18+
use PHPStan\Type\Type;
1719
use function array_merge;
1820

1921
/**
@@ -28,6 +30,14 @@ public function supports(Expr $expr): bool
2830
return $expr instanceof Eval_;
2931
}
3032

33+
/**
34+
* @param Eval_ $expr
35+
*/
36+
public function resolveType(MutatingScope $scope, Expr $expr): Type
37+
{
38+
return new MixedType();
39+
}
40+
3141
public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Expr $expr, MutatingScope $scope, ExpressionResultStorage $storage, callable $nodeCallback, ExpressionContext $context): ExpressionResult
3242
{
3343
$result = $nodeScopeResolver->processExprNode($stmt, $expr->expr, $scope, $storage, $nodeCallback, $context->enterDeep());
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Analyser\ExprHandler\Helper;
4+
5+
use PhpParser\Node\Expr;
6+
use PhpParser\Node\Expr\ArrayDimFetch;
7+
use PhpParser\Node\Expr\MethodCall;
8+
use PhpParser\Node\Expr\NullsafeMethodCall;
9+
use PhpParser\Node\Expr\NullsafePropertyFetch;
10+
use PhpParser\Node\Expr\PropertyFetch;
11+
use PhpParser\Node\Expr\StaticCall;
12+
use PhpParser\Node\Expr\StaticPropertyFetch;
13+
use PHPStan\Analyser\MutatingScope;
14+
use PHPStan\Type\Type;
15+
use PHPStan\Type\TypeCombinator;
16+
17+
final class NullsafeShortCircuitingHelper
18+
{
19+
20+
public static function getType(MutatingScope $scope, Expr $expr, Type $type): Type
21+
{
22+
if ($expr instanceof NullsafePropertyFetch || $expr instanceof NullsafeMethodCall) {
23+
$varType = $scope->getType($expr->var);
24+
if (TypeCombinator::containsNull($varType)) {
25+
return TypeCombinator::addNull($type);
26+
}
27+
28+
return $type;
29+
}
30+
31+
if ($expr instanceof ArrayDimFetch) {
32+
return self::getType($scope, $expr->var, $type);
33+
}
34+
35+
if ($expr instanceof PropertyFetch) {
36+
return self::getType($scope, $expr->var, $type);
37+
}
38+
39+
if ($expr instanceof StaticPropertyFetch && $expr->class instanceof Expr) {
40+
return self::getType($scope, $expr->class, $type);
41+
}
42+
43+
if ($expr instanceof MethodCall) {
44+
return self::getType($scope, $expr->var, $type);
45+
}
46+
47+
if ($expr instanceof StaticCall && $expr->class instanceof Expr) {
48+
return self::getType($scope, $expr->class, $type);
49+
}
50+
51+
return $type;
52+
}
53+
54+
}

0 commit comments

Comments
 (0)