diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/ArithmeticOpAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/ArithmeticOpAnalyzer.php index c754d17756e..7a85011e61d 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/ArithmeticOpAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/ArithmeticOpAnalyzer.php @@ -47,6 +47,7 @@ use function array_diff_key; use function array_values; use function count; +use function get_class; use function is_int; use function is_numeric; use function max; @@ -309,8 +310,8 @@ private static function analyzeOperands( bool &$has_string_increment, Union &$result_type = null ): ?Union { - if ($left_type_part instanceof TLiteralInt - && $right_type_part instanceof TLiteralInt + if (($left_type_part instanceof TLiteralInt || $left_type_part instanceof TLiteralFloat) + && ($right_type_part instanceof TLiteralInt || $right_type_part instanceof TLiteralFloat) && ( //we don't try to do arithmetics on variables in loops $context === null @@ -318,6 +319,21 @@ private static function analyzeOperands( || (!$left instanceof PhpParser\Node\Expr\Variable && !$right instanceof PhpParser\Node\Expr\Variable) ) ) { + // get_class is fine here because both classes are final. + if ($statements_source !== null + && $config->strict_binary_operands + && get_class($left_type_part) !== get_class($right_type_part) + ) { + IssueBuffer::maybeAdd( + new InvalidOperand( + 'Cannot process numeric types together in strict operands mode, '. + 'please cast explicitly', + new CodeLocation($statements_source, $parent) + ), + $statements_source->getSuppressedIssues() + ); + } + // time for some arithmetic! $calculated_type = self::arithmeticOperation( $parent, diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/ClassConstAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/ClassConstAnalyzer.php index 3797f6dd642..135bdb80161 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/ClassConstAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/ClassConstAnalyzer.php @@ -712,7 +712,6 @@ public static function analyzeAssignment( "{$class_storage->name}::{$const->name->name}" ), $const_storage->suppressed_issues, - true ); } } diff --git a/tests/BinaryOperationTest.php b/tests/BinaryOperationTest.php index b6a0138093a..3dec45eddde 100644 --- a/tests/BinaryOperationTest.php +++ b/tests/BinaryOperationTest.php @@ -191,7 +191,7 @@ function returnsABool(): bool { $this->analyzeFile('somefile.php', new Context()); } - public function testDifferingNumericTypesAdditionInStrictMode(): void + public function testDifferingNumericLiteralTypesAdditionInStrictMode(): void { $config = Config::getInstance(); $config->strict_binary_operands = true; @@ -208,6 +208,25 @@ public function testDifferingNumericTypesAdditionInStrictMode(): void $this->analyzeFile('somefile.php', new Context()); } + public function testDifferingNumericTypesAdditionInStrictMode(): void + { + $config = Config::getInstance(); + $config->strict_binary_operands = true; + + $this->addFile( + 'somefile.php', + 'expectException(CodeException::class); + $this->expectExceptionMessage('InvalidOperand'); + + $this->analyzeFile('somefile.php', new Context()); + } + public function testConcatenationWithNumberInStrictMode(): void { $config = Config::getInstance(); @@ -852,6 +871,12 @@ function toPositiveInt(int $i): int return 1; }', ], + 'calculateLiteralResultForFloats' => [ + 'code' => ' ['$foo===' => 'float(3)'], + ], ]; } diff --git a/tests/ConstantTest.php b/tests/ConstantTest.php index 1c62914a355..48847e38f99 100644 --- a/tests/ConstantTest.php +++ b/tests/ConstantTest.php @@ -1419,6 +1419,15 @@ interface Foo interface Bar extends Foo {} ', ], + 'classConstsUsingFutureFloatDeclarationWithMultipleLevels' => [ + 'code' => '