4444use PHPStan \Type \Constant \ConstantArrayType ;
4545use PHPStan \Type \Constant \ConstantArrayTypeBuilder ;
4646use PHPStan \Type \Constant \ConstantBooleanType ;
47+ use PHPStan \Type \Constant \ConstantFloatType ;
4748use PHPStan \Type \Constant \ConstantIntegerType ;
4849use PHPStan \Type \Constant \ConstantStringType ;
4950use 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 ,
0 commit comments