From 03e8d19eecc4a59febe107664a3e877f30fb7e77 Mon Sep 17 00:00:00 2001 From: robchett Date: Sat, 16 Sep 2023 15:26:52 +0100 Subject: [PATCH] Only the binary op 'plus' works with two arrays Treat the result of any other operation as int|float Fixes #2123 --- .../BinaryOp/ArithmeticOpAnalyzer.php | 152 +++++++++--------- tests/BinaryOperationTest.php | 52 ++++++ 2 files changed, 128 insertions(+), 76 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/ArithmeticOpAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/ArithmeticOpAnalyzer.php index 36ae21d63c8..4e2b30f63b4 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/ArithmeticOpAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/ArithmeticOpAnalyzer.php @@ -542,97 +542,97 @@ private static function analyzeOperands( $has_valid_right_operand = true; } - $result_type = Type::getArray(); - return null; } - $has_valid_right_operand = true; - $has_valid_left_operand = true; + if ($parent instanceof PhpParser\Node\Expr\BinaryOp\Plus) { + $has_valid_right_operand = true; + $has_valid_left_operand = true; - if ($left_type_part instanceof TKeyedArray - && $right_type_part instanceof TKeyedArray - ) { - $definitely_existing_mixed_right_properties = array_diff_key( - $right_type_part->properties, - $left_type_part->properties, - ); + if ($left_type_part instanceof TKeyedArray + && $right_type_part instanceof TKeyedArray + ) { + $definitely_existing_mixed_right_properties = array_diff_key( + $right_type_part->properties, + $left_type_part->properties, + ); - $properties = $left_type_part->properties; - - foreach ($right_type_part->properties as $key => $type) { - if (!isset($properties[$key])) { - $properties[$key] = $type; - } elseif ($properties[$key]->possibly_undefined) { - $properties[$key] = Type::combineUnionTypes( - $properties[$key], - $type, - $codebase, - false, - true, - 500, - $type->possibly_undefined, - ); + $properties = $left_type_part->properties; + + foreach ($right_type_part->properties as $key => $type) { + if (!isset($properties[$key])) { + $properties[$key] = $type; + } elseif ($properties[$key]->possibly_undefined) { + $properties[$key] = Type::combineUnionTypes( + $properties[$key], + $type, + $codebase, + false, + true, + 500, + $type->possibly_undefined, + ); + } } - } - if ($left_type_part->fallback_params !== null) { - foreach ($definitely_existing_mixed_right_properties as $key => $type) { - $properties[$key] = Type::combineUnionTypes(Type::getMixed(), $type); + if ($left_type_part->fallback_params !== null) { + foreach ($definitely_existing_mixed_right_properties as $key => $type) { + $properties[$key] = Type::combineUnionTypes(Type::getMixed(), $type); + } } - } - if ($left_type_part->fallback_params === null - && $right_type_part->fallback_params === null - ) { - $fallback_params = null; - } elseif ($left_type_part->fallback_params !== null - && $right_type_part->fallback_params !== null - ) { - $fallback_params = [ - Type::combineUnionTypes( - $left_type_part->fallback_params[0], - $right_type_part->fallback_params[0], - ), - Type::combineUnionTypes( - $left_type_part->fallback_params[1], - $right_type_part->fallback_params[1], - ), - ]; + if ($left_type_part->fallback_params === null + && $right_type_part->fallback_params === null + ) { + $fallback_params = null; + } elseif ($left_type_part->fallback_params !== null + && $right_type_part->fallback_params !== null + ) { + $fallback_params = [ + Type::combineUnionTypes( + $left_type_part->fallback_params[0], + $right_type_part->fallback_params[0], + ), + Type::combineUnionTypes( + $left_type_part->fallback_params[1], + $right_type_part->fallback_params[1], + ), + ]; + } else { + $fallback_params = $left_type_part->fallback_params ?: $right_type_part->fallback_params; + } + + $new_keyed_array = new TKeyedArray( + $properties, + null, + $fallback_params, + ); + $result_type_member = new Union([$new_keyed_array]); } else { - $fallback_params = $left_type_part->fallback_params ?: $right_type_part->fallback_params; + $result_type_member = TypeCombiner::combine( + [$left_type_part, $right_type_part], + $codebase, + true, + ); } - $new_keyed_array = new TKeyedArray( - $properties, - null, - $fallback_params, - ); - $result_type_member = new Union([$new_keyed_array]); - } else { - $result_type_member = TypeCombiner::combine( - [$left_type_part, $right_type_part], - $codebase, - true, - ); - } + $result_type = Type::combineUnionTypes($result_type_member, $result_type, $codebase, true); - $result_type = Type::combineUnionTypes($result_type_member, $result_type, $codebase, true); + if ($left instanceof PhpParser\Node\Expr\ArrayDimFetch + && $context + && $statements_source instanceof StatementsAnalyzer + ) { + ArrayAssignmentAnalyzer::updateArrayType( + $statements_source, + $left, + $right, + $result_type, + $context, + ); + } - if ($left instanceof PhpParser\Node\Expr\ArrayDimFetch - && $context - && $statements_source instanceof StatementsAnalyzer - ) { - ArrayAssignmentAnalyzer::updateArrayType( - $statements_source, - $left, - $right, - $result_type, - $context, - ); + return null; } - - return null; } if (($left_type_part instanceof TNamedObject && strtolower($left_type_part->value) === 'gmp') diff --git a/tests/BinaryOperationTest.php b/tests/BinaryOperationTest.php index 3ac18147fd4..f0fe0c9e3ae 100644 --- a/tests/BinaryOperationTest.php +++ b/tests/BinaryOperationTest.php @@ -1022,6 +1022,58 @@ function toPositiveInt(int $i): int '$a===' => 'float(9.2233720368548E+18)', ], ], + 'invalidArrayOperations' => [ + 'code' => <<<'PHP' + [ + '$a1' => 'float|int', + '$a2' => 'float|int', + '$a3' => 'array', + '$b1' => 'float|int', + '$b2' => 'float|int', + '$b3' => 'float|int', + '$c1' => 'float|int', + '$c2' => 'float|int', + '$c3' => 'float|int', + '$d1' => 'float|int', + '$d2' => 'float|int', + '$d3' => 'float|int', + '$e1' => 'float|int', + '$e2' => 'float|int', + '$e3' => 'float|int', + '$f1' => 'float|int', + '$f2' => 'float|int', + '$f3' => 'float|int', + ], + 'ignored_issues' => ['InvalidOperand'], + ], ]; }