4343use PHPStan \Type \Constant \ConstantArrayType ;
4444use PHPStan \Type \Constant \ConstantArrayTypeBuilder ;
4545use PHPStan \Type \Constant \ConstantBooleanType ;
46+ use PHPStan \Type \Constant \ConstantFloatType ;
4647use PHPStan \Type \Constant \ConstantIntegerType ;
4748use PHPStan \Type \Constant \ConstantStringType ;
4849use PHPStan \Type \ConstantScalarType ;
@@ -1610,7 +1611,7 @@ private function processBooleanNotSureConditionalTypes(Scope $scope, SpecifiedTy
16101611 }
16111612
16121613 /**
1613- * @return array{Expr, ConstantScalarType}|null
1614+ * @return array{Expr, ConstantScalarType, Type }|null
16141615 */
16151616 private function findTypeExpressionsFromBinaryOperation (Scope $ scope , Node \Expr \BinaryOp $ binaryOperation ): ?array
16161617 {
@@ -1632,13 +1633,13 @@ private function findTypeExpressionsFromBinaryOperation(Scope $scope, Node\Expr\
16321633 && !$ rightExpr instanceof ConstFetch
16331634 && !$ rightExpr instanceof ClassConstFetch
16341635 ) {
1635- return [$ binaryOperation ->right , $ leftType ];
1636+ return [$ binaryOperation ->right , $ leftType, $ rightType ];
16361637 } elseif (
16371638 $ rightType instanceof ConstantScalarType
16381639 && !$ leftExpr instanceof ConstFetch
16391640 && !$ leftExpr instanceof ClassConstFetch
16401641 ) {
1641- return [$ binaryOperation ->left , $ rightType ];
1642+ return [$ binaryOperation ->left , $ rightType, $ leftType ];
16421643 }
16431644
16441645 return null ;
@@ -1949,7 +1950,21 @@ public function resolveEqual(Expr\BinaryOp\Equal $expr, Scope $scope, TypeSpecif
19491950 if ($ expressions !== null ) {
19501951 $ exprNode = $ expressions [0 ];
19511952 $ 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 ) {
19531968 return $ this ->specifyTypesInCondition (
19541969 $ scope ,
19551970 $ exprNode ,
@@ -1967,6 +1982,52 @@ public function resolveEqual(Expr\BinaryOp\Equal $expr, Scope $scope, TypeSpecif
19671982 );
19681983 }
19691984
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+
19702031 if (
19712032 $ exprNode instanceof FuncCall
19722033 && $ exprNode ->name instanceof Name
@@ -2060,11 +2121,13 @@ public function resolveEqual(Expr\BinaryOp\Equal $expr, Scope $scope, TypeSpecif
20602121
20612122 public function resolveIdentical (Expr \BinaryOp \Identical $ expr , Scope $ scope , TypeSpecifierContext $ context , ?Expr $ rootExpr ): SpecifiedTypes
20622123 {
2124+ // Normalize to: fn() === expr
20632125 $ leftExpr = $ expr ->left ;
20642126 $ rightExpr = $ expr ->right ;
20652127 if ($ rightExpr instanceof FuncCall && !$ leftExpr instanceof FuncCall) {
20662128 [$ leftExpr , $ rightExpr ] = [$ rightExpr , $ leftExpr ];
20672129 }
2130+
20682131 $ unwrappedLeftExpr = $ leftExpr ;
20692132 if ($ leftExpr instanceof AlwaysRememberedExpr) {
20702133 $ unwrappedLeftExpr = $ leftExpr ->getExpr ();
@@ -2073,8 +2136,10 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty
20732136 if ($ rightExpr instanceof AlwaysRememberedExpr) {
20742137 $ unwrappedRightExpr = $ rightExpr ->getExpr ();
20752138 }
2139+
20762140 $ rightType = $ scope ->getType ($ rightExpr );
20772141
2142+ // (count($a) === $b)
20782143 if (
20792144 !$ context ->null ()
20802145 && $ unwrappedLeftExpr instanceof FuncCall
@@ -2139,6 +2204,7 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty
21392204 }
21402205 }
21412206
2207+ // strlen($a) === $b
21422208 if (
21432209 !$ context ->null ()
21442210 && $ unwrappedLeftExpr instanceof FuncCall
@@ -2175,6 +2241,7 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty
21752241 }
21762242 }
21772243
2244+ // preg_match($a) === $b
21782245 if (
21792246 $ context ->true ()
21802247 && $ unwrappedLeftExpr instanceof FuncCall
@@ -2190,6 +2257,7 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty
21902257 );
21912258 }
21922259
2260+ // get_class($a) === 'Foo'
21932261 if (
21942262 $ context ->true ()
21952263 && $ unwrappedLeftExpr instanceof FuncCall
@@ -2209,6 +2277,7 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty
22092277 }
22102278 }
22112279
2280+ // get_class($a) === 'Foo'
22122281 if (
22132282 $ context ->truthy ()
22142283 && $ unwrappedLeftExpr instanceof FuncCall
@@ -2289,6 +2358,7 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty
22892358 }
22902359 }
22912360
2361+ // $a::class === 'Foo'
22922362 if (
22932363 $ context ->true () &&
22942364 $ unwrappedLeftExpr instanceof ClassConstFetch &&
@@ -2311,6 +2381,8 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty
23112381 }
23122382
23132383 $ leftType = $ scope ->getType ($ leftExpr );
2384+
2385+ // 'Foo' === $a::class
23142386 if (
23152387 $ context ->true () &&
23162388 $ unwrappedRightExpr instanceof ClassConstFetch &&
@@ -2356,7 +2428,11 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty
23562428 $ types = null ;
23572429 if (
23582430 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 ())
23602436 ) {
23612437 $ types = $ this ->create (
23622438 $ rightExpr ,
@@ -2379,7 +2455,12 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty
23792455 }
23802456 if (
23812457 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+ )
23832464 ) {
23842465 $ leftTypes = $ this ->create (
23852466 $ leftExpr ,
0 commit comments