Skip to content

Commit b80f208

Browse files
authored
Merge branch refs/heads/1.12.x into 2.0.x
2 parents 91bdfb5 + 5e3a364 commit b80f208

File tree

8 files changed

+281
-16
lines changed

8 files changed

+281
-16
lines changed

src/Analyser/TypeSpecifier.php

Lines changed: 87 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
use PHPStan\Type\Constant\ConstantArrayType;
4545
use PHPStan\Type\Constant\ConstantArrayTypeBuilder;
4646
use PHPStan\Type\Constant\ConstantBooleanType;
47+
use PHPStan\Type\Constant\ConstantFloatType;
4748
use PHPStan\Type\Constant\ConstantIntegerType;
4849
use PHPStan\Type\Constant\ConstantStringType;
4950
use PHPStan\Type\ConstantScalarType;
@@ -1561,7 +1562,7 @@ private function processBooleanNotSureConditionalTypes(Scope $scope, SpecifiedTy
15611562
}
15621563

15631564
/**
1564-
* @return array{Expr, ConstantScalarType}|null
1565+
* @return array{Expr, ConstantScalarType, Type}|null
15651566
*/
15661567
private function findTypeExpressionsFromBinaryOperation(Scope $scope, Node\Expr\BinaryOp $binaryOperation): ?array
15671568
{
@@ -1583,13 +1584,13 @@ private function findTypeExpressionsFromBinaryOperation(Scope $scope, Node\Expr\
15831584
&& !$rightExpr instanceof ConstFetch
15841585
&& !$rightExpr instanceof ClassConstFetch
15851586
) {
1586-
return [$binaryOperation->right, $leftType];
1587+
return [$binaryOperation->right, $leftType, $rightType];
15871588
} elseif (
15881589
$rightType instanceof ConstantScalarType
15891590
&& !$leftExpr instanceof ConstFetch
15901591
&& !$leftExpr instanceof ClassConstFetch
15911592
) {
1592-
return [$binaryOperation->left, $rightType];
1593+
return [$binaryOperation->left, $rightType, $leftType];
15931594
}
15941595

15951596
return null;
@@ -1891,7 +1892,21 @@ public function resolveEqual(Expr\BinaryOp\Equal $expr, Scope $scope, TypeSpecif
18911892
if ($expressions !== null) {
18921893
$exprNode = $expressions[0];
18931894
$constantType = $expressions[1];
1894-
if (!$context->null() && ($constantType->getValue() === false || $constantType->getValue() === null)) {
1895+
$otherType = $expressions[2];
1896+
1897+
if (!$context->null() && $constantType->getValue() === null) {
1898+
$trueTypes = [
1899+
new NullType(),
1900+
new ConstantBooleanType(false),
1901+
new ConstantIntegerType(0),
1902+
new ConstantFloatType(0.0),
1903+
new ConstantStringType(''),
1904+
new ConstantArrayType([], []),
1905+
];
1906+
return $this->create($exprNode, new UnionType($trueTypes), $context, false, $scope, $rootExpr);
1907+
}
1908+
1909+
if (!$context->null() && $constantType->getValue() === false) {
18951910
return $this->specifyTypesInCondition(
18961911
$scope,
18971912
$exprNode,
@@ -1907,6 +1922,52 @@ public function resolveEqual(Expr\BinaryOp\Equal $expr, Scope $scope, TypeSpecif
19071922
)->setRootExpr($expr);
19081923
}
19091924

1925+
if (!$context->null() && $constantType->getValue() === 0 && !$otherType->isInteger()->yes() && !$otherType->isBoolean()->yes()) {
1926+
/* There is a difference between php 7.x and 8.x on the equality
1927+
* behavior between zero and the empty string, so to be conservative
1928+
* we leave it untouched regardless of the language version */
1929+
if ($context->true()) {
1930+
$trueTypes = [
1931+
new NullType(),
1932+
new ConstantBooleanType(false),
1933+
new ConstantIntegerType(0),
1934+
new ConstantFloatType(0.0),
1935+
new StringType(),
1936+
];
1937+
} else {
1938+
$trueTypes = [
1939+
new NullType(),
1940+
new ConstantBooleanType(false),
1941+
new ConstantIntegerType(0),
1942+
new ConstantFloatType(0.0),
1943+
new ConstantStringType('0'),
1944+
];
1945+
}
1946+
return $this->create($exprNode, new UnionType($trueTypes), $context, false, $scope, $rootExpr);
1947+
}
1948+
1949+
if (!$context->null() && $constantType->getValue() === '') {
1950+
/* There is a difference between php 7.x and 8.x on the equality
1951+
* behavior between zero and the empty string, so to be conservative
1952+
* we leave it untouched regardless of the language version */
1953+
if ($context->true()) {
1954+
$trueTypes = [
1955+
new NullType(),
1956+
new ConstantBooleanType(false),
1957+
new ConstantIntegerType(0),
1958+
new ConstantFloatType(0.0),
1959+
new ConstantStringType(''),
1960+
];
1961+
} else {
1962+
$trueTypes = [
1963+
new NullType(),
1964+
new ConstantBooleanType(false),
1965+
new ConstantStringType(''),
1966+
];
1967+
}
1968+
return $this->create($exprNode, new UnionType($trueTypes), $context, false, $scope, $rootExpr);
1969+
}
1970+
19101971
if (
19111972
$exprNode instanceof FuncCall
19121973
&& $exprNode->name instanceof Name
@@ -1998,11 +2059,13 @@ public function resolveEqual(Expr\BinaryOp\Equal $expr, Scope $scope, TypeSpecif
19982059

19992060
public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes
20002061
{
2062+
// Normalize to: fn() === expr
20012063
$leftExpr = $expr->left;
20022064
$rightExpr = $expr->right;
20032065
if ($rightExpr instanceof FuncCall && !$leftExpr instanceof FuncCall) {
20042066
[$leftExpr, $rightExpr] = [$rightExpr, $leftExpr];
20052067
}
2068+
20062069
$unwrappedLeftExpr = $leftExpr;
20072070
if ($leftExpr instanceof AlwaysRememberedExpr) {
20082071
$unwrappedLeftExpr = $leftExpr->getExpr();
@@ -2011,8 +2074,10 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty
20112074
if ($rightExpr instanceof AlwaysRememberedExpr) {
20122075
$unwrappedRightExpr = $rightExpr->getExpr();
20132076
}
2077+
20142078
$rightType = $scope->getType($rightExpr);
20152079

2080+
// (count($a) === $b)
20162081
if (
20172082
!$context->null()
20182083
&& $unwrappedLeftExpr instanceof FuncCall
@@ -2077,6 +2142,7 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty
20772142
}
20782143
}
20792144

2145+
// strlen($a) === $b
20802146
if (
20812147
!$context->null()
20822148
&& $unwrappedLeftExpr instanceof FuncCall
@@ -2113,6 +2179,7 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty
21132179
}
21142180
}
21152181

2182+
// preg_match($a) === $b
21162183
if (
21172184
$context->true()
21182185
&& $unwrappedLeftExpr instanceof FuncCall
@@ -2127,6 +2194,7 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty
21272194
)->setRootExpr($expr);
21282195
}
21292196

2197+
// get_class($a) === 'Foo'
21302198
if (
21312199
$context->true()
21322200
&& $unwrappedLeftExpr instanceof FuncCall
@@ -2144,6 +2212,7 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty
21442212
}
21452213
}
21462214

2215+
// get_class($a) === 'Foo'
21472216
if (
21482217
$context->truthy()
21492218
&& $unwrappedLeftExpr instanceof FuncCall
@@ -2222,6 +2291,7 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty
22222291
}
22232292
}
22242293

2294+
// $a::class === 'Foo'
22252295
if (
22262296
$context->true() &&
22272297
$unwrappedLeftExpr instanceof ClassConstFetch &&
@@ -2243,6 +2313,8 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty
22432313
}
22442314

22452315
$leftType = $scope->getType($leftExpr);
2316+
2317+
// 'Foo' === $a::class
22462318
if (
22472319
$context->true() &&
22482320
$unwrappedRightExpr instanceof ClassConstFetch &&
@@ -2287,7 +2359,11 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty
22872359
$types = null;
22882360
if (
22892361
count($leftType->getFiniteTypes()) === 1
2290-
|| ($context->true() && $leftType->isConstantValue()->yes() && !$rightType->equals($leftType) && $rightType->isSuperTypeOf($leftType)->yes())
2362+
|| (
2363+
$context->true()
2364+
&& $leftType->isConstantValue()->yes()
2365+
&& !$rightType->equals($leftType)
2366+
&& $rightType->isSuperTypeOf($leftType)->yes())
22912367
) {
22922368
$types = $this->create(
22932369
$rightExpr,
@@ -2306,7 +2382,12 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty
23062382
}
23072383
if (
23082384
count($rightType->getFiniteTypes()) === 1
2309-
|| ($context->true() && $rightType->isConstantValue()->yes() && !$leftType->equals($rightType) && $leftType->isSuperTypeOf($rightType)->yes())
2385+
|| (
2386+
$context->true()
2387+
&& $rightType->isConstantValue()->yes()
2388+
&& !$leftType->equals($rightType)
2389+
&& $leftType->isSuperTypeOf($rightType)->yes()
2390+
)
23102391
) {
23112392
$leftTypes = $this->create(
23122393
$leftExpr,

tests/PHPStan/Analyser/TypeSpecifierTest.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -478,8 +478,8 @@ public function dataCondition(): iterable
478478
new Variable('foo'),
479479
new Expr\ConstFetch(new Name('null')),
480480
),
481-
['$foo' => self::SURE_NOT_TRUTHY],
482-
['$foo' => self::SURE_NOT_FALSEY],
481+
['$foo' => '0|0.0|\'\'|array{}|false|null'],
482+
['$foo' => '~0|0.0|\'\'|array{}|false|null'],
483483
],
484484
[
485485
new Expr\BinaryOp\Identical(

0 commit comments

Comments
 (0)