diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/NonDivArithmeticOpAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/NonDivArithmeticOpAnalyzer.php index e293eb13d..7e59c0527 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/NonDivArithmeticOpAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/NonDivArithmeticOpAnalyzer.php @@ -288,6 +288,48 @@ class NonDivArithmeticOpAnalyzer bool &$has_string_increment, Type\Union &$result_type = null ): ?Type\Union { + if ($left_type_part instanceof TLiteralInt + && $right_type_part instanceof TLiteralInt + && ($left instanceof PhpParser\Node\Scalar || $left instanceof PhpParser\Node\Expr\ConstFetch) + && ($right instanceof PhpParser\Node\Scalar || $right instanceof PhpParser\Node\Expr\ConstFetch) + ) { + // time for some arithmetic! + + $calculated_type = null; + + if ($parent instanceof PhpParser\Node\Expr\BinaryOp\Plus) { + $calculated_type = Type::getInt(false, $left_type_part->value + $right_type_part->value); + } elseif ($parent instanceof PhpParser\Node\Expr\BinaryOp\Minus) { + $calculated_type = Type::getInt(false, $left_type_part->value - $right_type_part->value); + } elseif ($parent instanceof PhpParser\Node\Expr\BinaryOp\Mod) { + $calculated_type = Type::getInt(false, $left_type_part->value % $right_type_part->value); + } elseif ($parent instanceof PhpParser\Node\Expr\BinaryOp\Mul) { + $calculated_type = Type::getInt(false, $left_type_part->value * $right_type_part->value); + } elseif ($parent instanceof PhpParser\Node\Expr\BinaryOp\Pow) { + $calculated_type = Type::getInt(false, $left_type_part->value ^ $right_type_part->value); + } elseif ($parent instanceof PhpParser\Node\Expr\BinaryOp\BitwiseOr) { + $calculated_type = Type::getInt(false, $left_type_part->value | $right_type_part->value); + } elseif ($parent instanceof PhpParser\Node\Expr\BinaryOp\BitwiseAnd) { + $calculated_type = Type::getInt(false, $left_type_part->value & $right_type_part->value); + } + + if ($calculated_type) { + if ($result_type) { + $result_type = Type::combineUnionTypes( + $calculated_type, + $result_type + ); + } else { + $result_type = $calculated_type; + } + + $has_valid_left_operand = true; + $has_valid_right_operand = true; + + return null; + } + } + if ($left_type_part instanceof TNull || $right_type_part instanceof TNull) { // null case is handled above return null; diff --git a/stubs/CoreGenericFunctions.phpstub b/stubs/CoreGenericFunctions.phpstub index adec2461b..b4bcaaff9 100644 --- a/stubs/CoreGenericFunctions.phpstub +++ b/stubs/CoreGenericFunctions.phpstub @@ -607,7 +607,11 @@ function explode(string $delimiter, string $string, int $limit = -1) : array {} * * @psalm-flow ($subject) -(array-assignment)-> return * - * @return ($flags is 0 ? non-empty-list|false : list|list>|false) + * @template TFlags as 0|1|2|3|4|5|6|7 + * + * @param TFlags $flags + * + * @return (TFlags is 0|2 ? non-empty-list|false : (TFlags is 1|3 ? list|false : list|false)) * * @psalm-ignore-falsable-return */ diff --git a/tests/BinaryOperationTest.php b/tests/BinaryOperationTest.php index 124daa62d..d83ba51e4 100644 --- a/tests/BinaryOperationTest.php +++ b/tests/BinaryOperationTest.php @@ -133,7 +133,7 @@ class BinaryOperationTest extends TestCase $c = 25 % 2.5; $d = 25.5 % 2.5;', 'assertions' => [ - '$a' => 'int|int', + '$a' => 'int', '$b' => 'int', '$c' => 'int', '$d' => 'int', diff --git a/tests/FunctionCallTest.php b/tests/FunctionCallTest.php index e3f6c2fbb..bd36030f2 100644 --- a/tests/FunctionCallTest.php +++ b/tests/FunctionCallTest.php @@ -1362,6 +1362,13 @@ class FunctionCallTest extends TestCase return preg_split("/ /", $s); }' ], + 'pregSplitWithFlags' => [ + ' */ + function foo(string $s) { + return preg_split("/ /", $s, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE); + }' + ], 'mbConvertEncodingWithArray' => [ 'fooFoo(); } }