@@ -1572,15 +1572,8 @@ private function findTypeExpressionsFromBinaryOperation(Scope $scope, Node\Expr\
15721572 $ leftType = $ scope ->getType ($ binaryOperation ->left );
15731573 $ rightType = $ scope ->getType ($ binaryOperation ->right );
15741574
1575- $ rightExpr = $ binaryOperation ->right ;
1576- if ($ rightExpr instanceof AlwaysRememberedExpr) {
1577- $ rightExpr = $ rightExpr ->getExpr ();
1578- }
1579-
1580- $ leftExpr = $ binaryOperation ->left ;
1581- if ($ leftExpr instanceof AlwaysRememberedExpr) {
1582- $ leftExpr = $ leftExpr ->getExpr ();
1583- }
1575+ $ rightExpr = $ this ->extractExpression ($ binaryOperation ->right );
1576+ $ leftExpr = $ this ->extractExpression ($ binaryOperation ->left );
15841577
15851578 if (
15861579 $ leftType instanceof ConstantScalarType
@@ -1599,6 +1592,39 @@ private function findTypeExpressionsFromBinaryOperation(Scope $scope, Node\Expr\
15991592 return null ;
16001593 }
16011594
1595+ /**
1596+ * @return array{Expr, Type, Type}|null
1597+ */
1598+ private function findEnumTypeExpressionsFromBinaryOperation (Scope $ scope , Node \Expr \BinaryOp $ binaryOperation ): ?array
1599+ {
1600+ $ leftType = $ scope ->getType ($ binaryOperation ->left );
1601+ $ rightType = $ scope ->getType ($ binaryOperation ->right );
1602+
1603+ $ rightExpr = $ this ->extractExpression ($ binaryOperation ->right );
1604+ $ leftExpr = $ this ->extractExpression ($ binaryOperation ->left );
1605+
1606+ if (
1607+ $ leftType ->getEnumCases () === [$ leftType ]
1608+ && !$ rightExpr instanceof ConstFetch
1609+ && !$ rightExpr instanceof ClassConstFetch
1610+ ) {
1611+ return [$ binaryOperation ->right , $ leftType , $ rightType ];
1612+ } elseif (
1613+ $ rightType ->getEnumCases () === [$ rightType ]
1614+ && !$ leftExpr instanceof ConstFetch
1615+ && !$ leftExpr instanceof ClassConstFetch
1616+ ) {
1617+ return [$ binaryOperation ->left , $ rightType , $ leftType ];
1618+ }
1619+
1620+ return null ;
1621+ }
1622+
1623+ private function extractExpression (Expr $ expr ): Expr
1624+ {
1625+ return $ expr instanceof AlwaysRememberedExpr ? $ expr ->getExpr () : $ expr ;
1626+ }
1627+
16021628 /** @api */
16031629 public function create (
16041630 Expr $ expr ,
@@ -1894,10 +1920,10 @@ public function resolveEqual(Expr\BinaryOp\Equal $expr, Scope $scope, TypeSpecif
18941920 $ expressions = $ this ->findTypeExpressionsFromBinaryOperation ($ scope , $ expr );
18951921 if ($ expressions !== null ) {
18961922 $ exprNode = $ expressions [0 ];
1897- $ constantType = $ expressions [1 ];
1923+ $ enumCaseObjectType = $ expressions [1 ];
18981924 $ otherType = $ expressions [2 ];
18991925
1900- if (!$ context ->null () && $ constantType ->getValue () === null ) {
1926+ if (!$ context ->null () && $ enumCaseObjectType ->getValue () === null ) {
19011927 $ trueTypes = [
19021928 new NullType (),
19031929 new ConstantBooleanType (false ),
@@ -1909,23 +1935,23 @@ public function resolveEqual(Expr\BinaryOp\Equal $expr, Scope $scope, TypeSpecif
19091935 return $ this ->create ($ exprNode , new UnionType ($ trueTypes ), $ context , $ scope )->setRootExpr ($ expr );
19101936 }
19111937
1912- if (!$ context ->null () && $ constantType ->getValue () === false ) {
1938+ if (!$ context ->null () && $ enumCaseObjectType ->getValue () === false ) {
19131939 return $ this ->specifyTypesInCondition (
19141940 $ scope ,
19151941 $ exprNode ,
19161942 $ context ->true () ? TypeSpecifierContext::createFalsey () : TypeSpecifierContext::createFalsey ()->negate (),
19171943 )->setRootExpr ($ expr );
19181944 }
19191945
1920- if (!$ context ->null () && $ constantType ->getValue () === true ) {
1946+ if (!$ context ->null () && $ enumCaseObjectType ->getValue () === true ) {
19211947 return $ this ->specifyTypesInCondition (
19221948 $ scope ,
19231949 $ exprNode ,
19241950 $ context ->true () ? TypeSpecifierContext::createTruthy () : TypeSpecifierContext::createTruthy ()->negate (),
19251951 )->setRootExpr ($ expr );
19261952 }
19271953
1928- if (!$ context ->null () && $ constantType ->getValue () === 0 && !$ otherType ->isInteger ()->yes () && !$ otherType ->isBoolean ()->yes ()) {
1954+ if (!$ context ->null () && $ enumCaseObjectType ->getValue () === 0 && !$ otherType ->isInteger ()->yes () && !$ otherType ->isBoolean ()->yes ()) {
19291955 /* There is a difference between php 7.x and 8.x on the equality
19301956 * behavior between zero and the empty string, so to be conservative
19311957 * we leave it untouched regardless of the language version */
@@ -1949,7 +1975,7 @@ public function resolveEqual(Expr\BinaryOp\Equal $expr, Scope $scope, TypeSpecif
19491975 return $ this ->create ($ exprNode , new UnionType ($ trueTypes ), $ context , $ scope )->setRootExpr ($ expr );
19501976 }
19511977
1952- if (!$ context ->null () && $ constantType ->getValue () === '' ) {
1978+ if (!$ context ->null () && $ enumCaseObjectType ->getValue () === '' ) {
19531979 /* There is a difference between php 7.x and 8.x on the equality
19541980 * behavior between zero and the empty string, so to be conservative
19551981 * we leave it untouched regardless of the language version */
@@ -1976,7 +2002,7 @@ public function resolveEqual(Expr\BinaryOp\Equal $expr, Scope $scope, TypeSpecif
19762002 && $ exprNode ->name instanceof Name
19772003 && in_array (strtolower ($ exprNode ->name ->toString ()), ['gettype ' , 'get_class ' , 'get_debug_type ' ], true )
19782004 && isset ($ exprNode ->getArgs ()[0 ])
1979- && $ constantType ->isString ()->yes ()
2005+ && $ enumCaseObjectType ->isString ()->yes ()
19802006 ) {
19812007 return $ this ->specifyTypesInCondition ($ scope , new Expr \BinaryOp \Identical ($ expr ->left , $ expr ->right ), $ context )->setRootExpr ($ expr );
19822008 }
@@ -1986,10 +2012,31 @@ public function resolveEqual(Expr\BinaryOp\Equal $expr, Scope $scope, TypeSpecif
19862012 && $ exprNode instanceof FuncCall
19872013 && $ exprNode ->name instanceof Name
19882014 && $ exprNode ->name ->toLowerString () === 'preg_match '
1989- && (new ConstantIntegerType (1 ))->isSuperTypeOf ($ constantType )->yes ()
2015+ && (new ConstantIntegerType (1 ))->isSuperTypeOf ($ enumCaseObjectType )->yes ()
19902016 ) {
19912017 return $ this ->specifyTypesInCondition ($ scope , new Expr \BinaryOp \Identical ($ expr ->left , $ expr ->right ), $ context )->setRootExpr ($ expr );
19922018 }
2019+
2020+ if (!$ context ->null () && TypeCombinator::containsNull ($ otherType )) {
2021+ if ($ enumCaseObjectType ->toBoolean ()->isTrue ()->yes ()) {
2022+ $ otherType = TypeCombinator::remove ($ otherType , new NullType ());
2023+ }
2024+
2025+ if (!$ otherType ->isSuperTypeOf ($ enumCaseObjectType )->no ()) {
2026+ return $ this ->create ($ exprNode , TypeCombinator::intersect ($ enumCaseObjectType , $ otherType ), $ context , $ scope )->setRootExpr ($ expr );
2027+ }
2028+ }
2029+ }
2030+
2031+ $ expressions = $ this ->findEnumTypeExpressionsFromBinaryOperation ($ scope , $ expr );
2032+ if ($ expressions !== null ) {
2033+ $ exprNode = $ expressions [0 ];
2034+ $ enumCaseObjectType = $ expressions [1 ];
2035+ $ otherType = $ expressions [2 ];
2036+
2037+ if (!$ context ->null ()) {
2038+ return $ this ->create ($ exprNode , TypeCombinator::intersect ($ enumCaseObjectType , $ otherType ), $ context , $ scope )->setRootExpr ($ expr );
2039+ }
19932040 }
19942041
19952042 $ leftType = $ scope ->getType ($ expr ->left );
0 commit comments