44
44
use PHPStan \Type \Constant \ConstantArrayType ;
45
45
use PHPStan \Type \Constant \ConstantArrayTypeBuilder ;
46
46
use PHPStan \Type \Constant \ConstantBooleanType ;
47
+ use PHPStan \Type \Constant \ConstantFloatType ;
47
48
use PHPStan \Type \Constant \ConstantIntegerType ;
48
49
use PHPStan \Type \Constant \ConstantStringType ;
49
50
use PHPStan \Type \ConstantScalarType ;
@@ -1561,7 +1562,7 @@ private function processBooleanNotSureConditionalTypes(Scope $scope, SpecifiedTy
1561
1562
}
1562
1563
1563
1564
/**
1564
- * @return array{Expr, ConstantScalarType}|null
1565
+ * @return array{Expr, ConstantScalarType, Type }|null
1565
1566
*/
1566
1567
private function findTypeExpressionsFromBinaryOperation (Scope $ scope , Node \Expr \BinaryOp $ binaryOperation ): ?array
1567
1568
{
@@ -1583,13 +1584,13 @@ private function findTypeExpressionsFromBinaryOperation(Scope $scope, Node\Expr\
1583
1584
&& !$ rightExpr instanceof ConstFetch
1584
1585
&& !$ rightExpr instanceof ClassConstFetch
1585
1586
) {
1586
- return [$ binaryOperation ->right , $ leftType ];
1587
+ return [$ binaryOperation ->right , $ leftType, $ rightType ];
1587
1588
} elseif (
1588
1589
$ rightType instanceof ConstantScalarType
1589
1590
&& !$ leftExpr instanceof ConstFetch
1590
1591
&& !$ leftExpr instanceof ClassConstFetch
1591
1592
) {
1592
- return [$ binaryOperation ->left , $ rightType ];
1593
+ return [$ binaryOperation ->left , $ rightType, $ leftType ];
1593
1594
}
1594
1595
1595
1596
return null ;
@@ -1891,7 +1892,21 @@ public function resolveEqual(Expr\BinaryOp\Equal $expr, Scope $scope, TypeSpecif
1891
1892
if ($ expressions !== null ) {
1892
1893
$ exprNode = $ expressions [0 ];
1893
1894
$ 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 ) {
1895
1910
return $ this ->specifyTypesInCondition (
1896
1911
$ scope ,
1897
1912
$ exprNode ,
@@ -1907,6 +1922,52 @@ public function resolveEqual(Expr\BinaryOp\Equal $expr, Scope $scope, TypeSpecif
1907
1922
)->setRootExpr ($ expr );
1908
1923
}
1909
1924
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
+
1910
1971
if (
1911
1972
$ exprNode instanceof FuncCall
1912
1973
&& $ exprNode ->name instanceof Name
@@ -1998,11 +2059,13 @@ public function resolveEqual(Expr\BinaryOp\Equal $expr, Scope $scope, TypeSpecif
1998
2059
1999
2060
public function resolveIdentical (Expr \BinaryOp \Identical $ expr , Scope $ scope , TypeSpecifierContext $ context ): SpecifiedTypes
2000
2061
{
2062
+ // Normalize to: fn() === expr
2001
2063
$ leftExpr = $ expr ->left ;
2002
2064
$ rightExpr = $ expr ->right ;
2003
2065
if ($ rightExpr instanceof FuncCall && !$ leftExpr instanceof FuncCall) {
2004
2066
[$ leftExpr , $ rightExpr ] = [$ rightExpr , $ leftExpr ];
2005
2067
}
2068
+
2006
2069
$ unwrappedLeftExpr = $ leftExpr ;
2007
2070
if ($ leftExpr instanceof AlwaysRememberedExpr) {
2008
2071
$ unwrappedLeftExpr = $ leftExpr ->getExpr ();
@@ -2011,8 +2074,10 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty
2011
2074
if ($ rightExpr instanceof AlwaysRememberedExpr) {
2012
2075
$ unwrappedRightExpr = $ rightExpr ->getExpr ();
2013
2076
}
2077
+
2014
2078
$ rightType = $ scope ->getType ($ rightExpr );
2015
2079
2080
+ // (count($a) === $b)
2016
2081
if (
2017
2082
!$ context ->null ()
2018
2083
&& $ unwrappedLeftExpr instanceof FuncCall
@@ -2077,6 +2142,7 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty
2077
2142
}
2078
2143
}
2079
2144
2145
+ // strlen($a) === $b
2080
2146
if (
2081
2147
!$ context ->null ()
2082
2148
&& $ unwrappedLeftExpr instanceof FuncCall
@@ -2113,6 +2179,7 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty
2113
2179
}
2114
2180
}
2115
2181
2182
+ // preg_match($a) === $b
2116
2183
if (
2117
2184
$ context ->true ()
2118
2185
&& $ unwrappedLeftExpr instanceof FuncCall
@@ -2127,6 +2194,7 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty
2127
2194
)->setRootExpr ($ expr );
2128
2195
}
2129
2196
2197
+ // get_class($a) === 'Foo'
2130
2198
if (
2131
2199
$ context ->true ()
2132
2200
&& $ unwrappedLeftExpr instanceof FuncCall
@@ -2144,6 +2212,7 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty
2144
2212
}
2145
2213
}
2146
2214
2215
+ // get_class($a) === 'Foo'
2147
2216
if (
2148
2217
$ context ->truthy ()
2149
2218
&& $ unwrappedLeftExpr instanceof FuncCall
@@ -2222,6 +2291,7 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty
2222
2291
}
2223
2292
}
2224
2293
2294
+ // $a::class === 'Foo'
2225
2295
if (
2226
2296
$ context ->true () &&
2227
2297
$ unwrappedLeftExpr instanceof ClassConstFetch &&
@@ -2243,6 +2313,8 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty
2243
2313
}
2244
2314
2245
2315
$ leftType = $ scope ->getType ($ leftExpr );
2316
+
2317
+ // 'Foo' === $a::class
2246
2318
if (
2247
2319
$ context ->true () &&
2248
2320
$ unwrappedRightExpr instanceof ClassConstFetch &&
@@ -2287,7 +2359,11 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty
2287
2359
$ types = null ;
2288
2360
if (
2289
2361
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 ())
2291
2367
) {
2292
2368
$ types = $ this ->create (
2293
2369
$ rightExpr ,
@@ -2306,7 +2382,12 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty
2306
2382
}
2307
2383
if (
2308
2384
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
+ )
2310
2391
) {
2311
2392
$ leftTypes = $ this ->create (
2312
2393
$ leftExpr ,
0 commit comments