43
43
use PHPStan \Type \Constant \ConstantArrayType ;
44
44
use PHPStan \Type \Constant \ConstantArrayTypeBuilder ;
45
45
use PHPStan \Type \Constant \ConstantBooleanType ;
46
+ use PHPStan \Type \Constant \ConstantFloatType ;
46
47
use PHPStan \Type \Constant \ConstantIntegerType ;
47
48
use PHPStan \Type \Constant \ConstantStringType ;
48
49
use PHPStan \Type \ConstantScalarType ;
@@ -1610,7 +1611,7 @@ private function processBooleanNotSureConditionalTypes(Scope $scope, SpecifiedTy
1610
1611
}
1611
1612
1612
1613
/**
1613
- * @return array{Expr, ConstantScalarType}|null
1614
+ * @return array{Expr, ConstantScalarType, Type }|null
1614
1615
*/
1615
1616
private function findTypeExpressionsFromBinaryOperation (Scope $ scope , Node \Expr \BinaryOp $ binaryOperation ): ?array
1616
1617
{
@@ -1632,13 +1633,13 @@ private function findTypeExpressionsFromBinaryOperation(Scope $scope, Node\Expr\
1632
1633
&& !$ rightExpr instanceof ConstFetch
1633
1634
&& !$ rightExpr instanceof ClassConstFetch
1634
1635
) {
1635
- return [$ binaryOperation ->right , $ leftType ];
1636
+ return [$ binaryOperation ->right , $ leftType, $ rightType ];
1636
1637
} elseif (
1637
1638
$ rightType instanceof ConstantScalarType
1638
1639
&& !$ leftExpr instanceof ConstFetch
1639
1640
&& !$ leftExpr instanceof ClassConstFetch
1640
1641
) {
1641
- return [$ binaryOperation ->left , $ rightType ];
1642
+ return [$ binaryOperation ->left , $ rightType, $ leftType ];
1642
1643
}
1643
1644
1644
1645
return null ;
@@ -1949,7 +1950,21 @@ public function resolveEqual(Expr\BinaryOp\Equal $expr, Scope $scope, TypeSpecif
1949
1950
if ($ expressions !== null ) {
1950
1951
$ exprNode = $ expressions [0 ];
1951
1952
$ constantType = $ expressions [1 ];
1952
- if (!$ context ->null () && ($ constantType ->getValue () === false || $ constantType ->getValue () === null )) {
1953
+ $ otherType = $ expressions [2 ];
1954
+
1955
+ if (!$ context ->null () && $ constantType ->getValue () === null ) {
1956
+ $ trueTypes = [
1957
+ new NullType (),
1958
+ new ConstantBooleanType (false ),
1959
+ new ConstantIntegerType (0 ),
1960
+ new ConstantFloatType (0.0 ),
1961
+ new ConstantStringType ('' ),
1962
+ new ConstantArrayType ([], []),
1963
+ ];
1964
+ return $ this ->create ($ exprNode , new UnionType ($ trueTypes ), $ context , false , $ scope , $ rootExpr );
1965
+ }
1966
+
1967
+ if (!$ context ->null () && $ constantType ->getValue () === false ) {
1953
1968
return $ this ->specifyTypesInCondition (
1954
1969
$ scope ,
1955
1970
$ exprNode ,
@@ -1967,6 +1982,52 @@ public function resolveEqual(Expr\BinaryOp\Equal $expr, Scope $scope, TypeSpecif
1967
1982
);
1968
1983
}
1969
1984
1985
+ if (!$ context ->null () && $ constantType ->getValue () === 0 && !$ otherType ->isInteger ()->yes () && !$ otherType ->isBoolean ()->yes ()) {
1986
+ /* There is a difference between php 7.x and 8.x on the equality
1987
+ * behavior between zero and the empty string, so to be conservative
1988
+ * we leave it untouched regardless of the language version */
1989
+ if ($ context ->true ()) {
1990
+ $ trueTypes = [
1991
+ new NullType (),
1992
+ new ConstantBooleanType (false ),
1993
+ new ConstantIntegerType (0 ),
1994
+ new ConstantFloatType (0.0 ),
1995
+ new StringType (),
1996
+ ];
1997
+ } else {
1998
+ $ trueTypes = [
1999
+ new NullType (),
2000
+ new ConstantBooleanType (false ),
2001
+ new ConstantIntegerType (0 ),
2002
+ new ConstantFloatType (0.0 ),
2003
+ new ConstantStringType ('0 ' ),
2004
+ ];
2005
+ }
2006
+ return $ this ->create ($ exprNode , new UnionType ($ trueTypes ), $ context , false , $ scope , $ rootExpr );
2007
+ }
2008
+
2009
+ if (!$ context ->null () && $ constantType ->getValue () === '' ) {
2010
+ /* There is a difference between php 7.x and 8.x on the equality
2011
+ * behavior between zero and the empty string, so to be conservative
2012
+ * we leave it untouched regardless of the language version */
2013
+ if ($ context ->true ()) {
2014
+ $ trueTypes = [
2015
+ new NullType (),
2016
+ new ConstantBooleanType (false ),
2017
+ new ConstantIntegerType (0 ),
2018
+ new ConstantFloatType (0.0 ),
2019
+ new ConstantStringType ('' ),
2020
+ ];
2021
+ } else {
2022
+ $ trueTypes = [
2023
+ new NullType (),
2024
+ new ConstantBooleanType (false ),
2025
+ new ConstantStringType ('' ),
2026
+ ];
2027
+ }
2028
+ return $ this ->create ($ exprNode , new UnionType ($ trueTypes ), $ context , false , $ scope , $ rootExpr );
2029
+ }
2030
+
1970
2031
if (
1971
2032
$ exprNode instanceof FuncCall
1972
2033
&& $ exprNode ->name instanceof Name
@@ -2060,11 +2121,13 @@ public function resolveEqual(Expr\BinaryOp\Equal $expr, Scope $scope, TypeSpecif
2060
2121
2061
2122
public function resolveIdentical (Expr \BinaryOp \Identical $ expr , Scope $ scope , TypeSpecifierContext $ context , ?Expr $ rootExpr ): SpecifiedTypes
2062
2123
{
2124
+ // Normalize to: fn() === expr
2063
2125
$ leftExpr = $ expr ->left ;
2064
2126
$ rightExpr = $ expr ->right ;
2065
2127
if ($ rightExpr instanceof FuncCall && !$ leftExpr instanceof FuncCall) {
2066
2128
[$ leftExpr , $ rightExpr ] = [$ rightExpr , $ leftExpr ];
2067
2129
}
2130
+
2068
2131
$ unwrappedLeftExpr = $ leftExpr ;
2069
2132
if ($ leftExpr instanceof AlwaysRememberedExpr) {
2070
2133
$ unwrappedLeftExpr = $ leftExpr ->getExpr ();
@@ -2073,8 +2136,10 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty
2073
2136
if ($ rightExpr instanceof AlwaysRememberedExpr) {
2074
2137
$ unwrappedRightExpr = $ rightExpr ->getExpr ();
2075
2138
}
2139
+
2076
2140
$ rightType = $ scope ->getType ($ rightExpr );
2077
2141
2142
+ // (count($a) === $b)
2078
2143
if (
2079
2144
!$ context ->null ()
2080
2145
&& $ unwrappedLeftExpr instanceof FuncCall
@@ -2139,6 +2204,7 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty
2139
2204
}
2140
2205
}
2141
2206
2207
+ // strlen($a) === $b
2142
2208
if (
2143
2209
!$ context ->null ()
2144
2210
&& $ unwrappedLeftExpr instanceof FuncCall
@@ -2175,6 +2241,7 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty
2175
2241
}
2176
2242
}
2177
2243
2244
+ // preg_match($a) === $b
2178
2245
if (
2179
2246
$ context ->true ()
2180
2247
&& $ unwrappedLeftExpr instanceof FuncCall
@@ -2190,6 +2257,7 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty
2190
2257
);
2191
2258
}
2192
2259
2260
+ // get_class($a) === 'Foo'
2193
2261
if (
2194
2262
$ context ->true ()
2195
2263
&& $ unwrappedLeftExpr instanceof FuncCall
@@ -2209,6 +2277,7 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty
2209
2277
}
2210
2278
}
2211
2279
2280
+ // get_class($a) === 'Foo'
2212
2281
if (
2213
2282
$ context ->truthy ()
2214
2283
&& $ unwrappedLeftExpr instanceof FuncCall
@@ -2289,6 +2358,7 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty
2289
2358
}
2290
2359
}
2291
2360
2361
+ // $a::class === 'Foo'
2292
2362
if (
2293
2363
$ context ->true () &&
2294
2364
$ unwrappedLeftExpr instanceof ClassConstFetch &&
@@ -2311,6 +2381,8 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty
2311
2381
}
2312
2382
2313
2383
$ leftType = $ scope ->getType ($ leftExpr );
2384
+
2385
+ // 'Foo' === $a::class
2314
2386
if (
2315
2387
$ context ->true () &&
2316
2388
$ unwrappedRightExpr instanceof ClassConstFetch &&
@@ -2356,7 +2428,11 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty
2356
2428
$ types = null ;
2357
2429
if (
2358
2430
count ($ leftType ->getFiniteTypes ()) === 1
2359
- || ($ context ->true () && $ leftType ->isConstantValue ()->yes () && !$ rightType ->equals ($ leftType ) && $ rightType ->isSuperTypeOf ($ leftType )->yes ())
2431
+ || (
2432
+ $ context ->true ()
2433
+ && $ leftType ->isConstantValue ()->yes ()
2434
+ && !$ rightType ->equals ($ leftType )
2435
+ && $ rightType ->isSuperTypeOf ($ leftType )->yes ())
2360
2436
) {
2361
2437
$ types = $ this ->create (
2362
2438
$ rightExpr ,
@@ -2379,7 +2455,12 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty
2379
2455
}
2380
2456
if (
2381
2457
count ($ rightType ->getFiniteTypes ()) === 1
2382
- || ($ context ->true () && $ rightType ->isConstantValue ()->yes () && !$ leftType ->equals ($ rightType ) && $ leftType ->isSuperTypeOf ($ rightType )->yes ())
2458
+ || (
2459
+ $ context ->true ()
2460
+ && $ rightType ->isConstantValue ()->yes ()
2461
+ && !$ leftType ->equals ($ rightType )
2462
+ && $ leftType ->isSuperTypeOf ($ rightType )->yes ()
2463
+ )
2383
2464
) {
2384
2465
$ leftTypes = $ this ->create (
2385
2466
$ leftExpr ,
0 commit comments