@@ -1572,15 +1572,8 @@ private function findTypeExpressionsFromBinaryOperation(Scope $scope, Node\Expr\
1572
1572
$ leftType = $ scope ->getType ($ binaryOperation ->left );
1573
1573
$ rightType = $ scope ->getType ($ binaryOperation ->right );
1574
1574
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 );
1584
1577
1585
1578
if (
1586
1579
$ leftType instanceof ConstantScalarType
@@ -1599,6 +1592,39 @@ private function findTypeExpressionsFromBinaryOperation(Scope $scope, Node\Expr\
1599
1592
return null ;
1600
1593
}
1601
1594
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
+
1602
1628
/** @api */
1603
1629
public function create (
1604
1630
Expr $ expr ,
@@ -1894,10 +1920,10 @@ public function resolveEqual(Expr\BinaryOp\Equal $expr, Scope $scope, TypeSpecif
1894
1920
$ expressions = $ this ->findTypeExpressionsFromBinaryOperation ($ scope , $ expr );
1895
1921
if ($ expressions !== null ) {
1896
1922
$ exprNode = $ expressions [0 ];
1897
- $ constantType = $ expressions [1 ];
1923
+ $ enumCaseObjectType = $ expressions [1 ];
1898
1924
$ otherType = $ expressions [2 ];
1899
1925
1900
- if (!$ context ->null () && $ constantType ->getValue () === null ) {
1926
+ if (!$ context ->null () && $ enumCaseObjectType ->getValue () === null ) {
1901
1927
$ trueTypes = [
1902
1928
new NullType (),
1903
1929
new ConstantBooleanType (false ),
@@ -1909,23 +1935,23 @@ public function resolveEqual(Expr\BinaryOp\Equal $expr, Scope $scope, TypeSpecif
1909
1935
return $ this ->create ($ exprNode , new UnionType ($ trueTypes ), $ context , $ scope )->setRootExpr ($ expr );
1910
1936
}
1911
1937
1912
- if (!$ context ->null () && $ constantType ->getValue () === false ) {
1938
+ if (!$ context ->null () && $ enumCaseObjectType ->getValue () === false ) {
1913
1939
return $ this ->specifyTypesInCondition (
1914
1940
$ scope ,
1915
1941
$ exprNode ,
1916
1942
$ context ->true () ? TypeSpecifierContext::createFalsey () : TypeSpecifierContext::createFalsey ()->negate (),
1917
1943
)->setRootExpr ($ expr );
1918
1944
}
1919
1945
1920
- if (!$ context ->null () && $ constantType ->getValue () === true ) {
1946
+ if (!$ context ->null () && $ enumCaseObjectType ->getValue () === true ) {
1921
1947
return $ this ->specifyTypesInCondition (
1922
1948
$ scope ,
1923
1949
$ exprNode ,
1924
1950
$ context ->true () ? TypeSpecifierContext::createTruthy () : TypeSpecifierContext::createTruthy ()->negate (),
1925
1951
)->setRootExpr ($ expr );
1926
1952
}
1927
1953
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 ()) {
1929
1955
/* There is a difference between php 7.x and 8.x on the equality
1930
1956
* behavior between zero and the empty string, so to be conservative
1931
1957
* we leave it untouched regardless of the language version */
@@ -1949,7 +1975,7 @@ public function resolveEqual(Expr\BinaryOp\Equal $expr, Scope $scope, TypeSpecif
1949
1975
return $ this ->create ($ exprNode , new UnionType ($ trueTypes ), $ context , $ scope )->setRootExpr ($ expr );
1950
1976
}
1951
1977
1952
- if (!$ context ->null () && $ constantType ->getValue () === '' ) {
1978
+ if (!$ context ->null () && $ enumCaseObjectType ->getValue () === '' ) {
1953
1979
/* There is a difference between php 7.x and 8.x on the equality
1954
1980
* behavior between zero and the empty string, so to be conservative
1955
1981
* we leave it untouched regardless of the language version */
@@ -1976,7 +2002,7 @@ public function resolveEqual(Expr\BinaryOp\Equal $expr, Scope $scope, TypeSpecif
1976
2002
&& $ exprNode ->name instanceof Name
1977
2003
&& in_array (strtolower ($ exprNode ->name ->toString ()), ['gettype ' , 'get_class ' , 'get_debug_type ' ], true )
1978
2004
&& isset ($ exprNode ->getArgs ()[0 ])
1979
- && $ constantType ->isString ()->yes ()
2005
+ && $ enumCaseObjectType ->isString ()->yes ()
1980
2006
) {
1981
2007
return $ this ->specifyTypesInCondition ($ scope , new Expr \BinaryOp \Identical ($ expr ->left , $ expr ->right ), $ context )->setRootExpr ($ expr );
1982
2008
}
@@ -1986,10 +2012,31 @@ public function resolveEqual(Expr\BinaryOp\Equal $expr, Scope $scope, TypeSpecif
1986
2012
&& $ exprNode instanceof FuncCall
1987
2013
&& $ exprNode ->name instanceof Name
1988
2014
&& $ exprNode ->name ->toLowerString () === 'preg_match '
1989
- && (new ConstantIntegerType (1 ))->isSuperTypeOf ($ constantType )->yes ()
2015
+ && (new ConstantIntegerType (1 ))->isSuperTypeOf ($ enumCaseObjectType )->yes ()
1990
2016
) {
1991
2017
return $ this ->specifyTypesInCondition ($ scope , new Expr \BinaryOp \Identical ($ expr ->left , $ expr ->right ), $ context )->setRootExpr ($ expr );
1992
2018
}
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
+ }
1993
2040
}
1994
2041
1995
2042
$ leftType = $ scope ->getType ($ expr ->left );
0 commit comments