diff --git a/src/Psalm/Internal/Analyzer/Statements/Block/IfElseAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Block/IfElseAnalyzer.php index 26de808b925..40d8385b5ae 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Block/IfElseAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Block/IfElseAnalyzer.php @@ -130,28 +130,26 @@ public static function analyze( $if_clauses = []; } - $if_clauses = array_values( - array_map( - /** - * @return Clause - */ - function (Clause $c) use ($mixed_var_ids, $cond_object_id): Clause { - $keys = array_keys($c->possibilities); - - $mixed_var_ids = \array_diff($mixed_var_ids, $keys); - - foreach ($keys as $key) { - foreach ($mixed_var_ids as $mixed_var_id) { - if (preg_match('/^' . preg_quote($mixed_var_id, '/') . '(\[|-)/', $key)) { - return new Clause([], $cond_object_id, $cond_object_id, true); - } + $if_clauses = array_map( + /** + * @return Clause + */ + function (Clause $c) use ($mixed_var_ids, $cond_object_id): Clause { + $keys = array_keys($c->possibilities); + + $mixed_var_ids = \array_diff($mixed_var_ids, $keys); + + foreach ($keys as $key) { + foreach ($mixed_var_ids as $mixed_var_id) { + if (preg_match('/^' . preg_quote($mixed_var_id, '/') . '(\[|-)/', $key)) { + return new Clause([], $cond_object_id, $cond_object_id, true); } } + } - return $c; - }, - $if_clauses - ) + return $c; + }, + $if_clauses ); $entry_clauses = $context->clauses; diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/NamedFunctionCallHandler.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/NamedFunctionCallHandler.php index 25637330586..c2e663a4a52 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/NamedFunctionCallHandler.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/NamedFunctionCallHandler.php @@ -364,6 +364,39 @@ function (array $_) : bool { return; } + if ($first_arg && $function_id === 'array_values') { + $first_arg_type = $statements_analyzer->node_data->getType($first_arg->value); + + if ($first_arg_type + && UnionTypeComparator::isContainedBy( + $codebase, + $first_arg_type, + Type::getList() + ) + ) { + if ($first_arg_type->from_docblock) { + if (IssueBuffer::accepts( + new \Psalm\Issue\RedundantCastGivenDocblockType( + 'The call to array_values is unnecessary given the list docblock type '.$first_arg_type, + new CodeLocation($statements_analyzer, $function_name) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } else { + if (IssueBuffer::accepts( + new \Psalm\Issue\RedundantCast( + 'The call to array_values is unnecessary, '.$first_arg_type.' is already a list', + new CodeLocation($statements_analyzer, $function_name) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + } + } if ($first_arg && $function_id === 'strtolower') { $first_arg_type = $statements_analyzer->node_data->getType($first_arg->value); diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/MatchAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/MatchAnalyzer.php index f1f18b5851d..74fccfe9c0b 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/MatchAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/MatchAnalyzer.php @@ -209,7 +209,7 @@ public static function analyze( } $all_match_condition = self::convertCondsToConditional( - \array_values($all_conds), + $all_conds, $match_condition, $match_condition->getAttributes() ); diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/TernaryAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/TernaryAnalyzer.php index 0145c48d85c..6f735b7628b 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/TernaryAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/TernaryAnalyzer.php @@ -83,28 +83,26 @@ public static function analyze( } } - $if_clauses = array_values( - array_map( + $if_clauses = array_map( /** * @return \Psalm\Internal\Clause */ - function (\Psalm\Internal\Clause $c) use ($mixed_var_ids, $cond_id): \Psalm\Internal\Clause { - $keys = array_keys($c->possibilities); + function (\Psalm\Internal\Clause $c) use ($mixed_var_ids, $cond_id): \Psalm\Internal\Clause { + $keys = array_keys($c->possibilities); - $mixed_var_ids = \array_diff($mixed_var_ids, $keys); + $mixed_var_ids = \array_diff($mixed_var_ids, $keys); - foreach ($keys as $key) { - foreach ($mixed_var_ids as $mixed_var_id) { - if (preg_match('/^' . preg_quote($mixed_var_id, '/') . '(\[|-)/', $key)) { - return new \Psalm\Internal\Clause([], $cond_id, $cond_id, true); - } + foreach ($keys as $key) { + foreach ($mixed_var_ids as $mixed_var_id) { + if (preg_match('/^' . preg_quote($mixed_var_id, '/') . '(\[|-)/', $key)) { + return new \Psalm\Internal\Clause([], $cond_id, $cond_id, true); } } + } - return $c; - }, - $if_clauses - ) + return $c; + }, + $if_clauses ); // this will see whether any of the clauses in set A conflict with the clauses in set B diff --git a/src/Psalm/Internal/Analyzer/Statements/UnsetAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/UnsetAnalyzer.php index febeb353ee6..63f9a946362 100644 --- a/src/Psalm/Internal/Analyzer/Statements/UnsetAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/UnsetAnalyzer.php @@ -7,6 +7,8 @@ use Psalm\Internal\Analyzer\StatementsAnalyzer; use Psalm\Type; +use function count; + class UnsetAnalyzer { public static function analyze( @@ -50,7 +52,11 @@ public static function analyze( || $var->dim instanceof PhpParser\Node\Scalar\LNumber ) { if (isset($atomic_root_type->properties[$var->dim->value])) { - $atomic_root_type->is_list = false; + if ($atomic_root_type->is_list + && $var->dim->value !== count($atomic_root_type->properties)-1 + ) { + $atomic_root_type->is_list = false; + } unset($atomic_root_type->properties[$var->dim->value]); $root_type->bustCache(); //remove id cache } diff --git a/src/Psalm/Internal/Fork/Pool.php b/src/Psalm/Internal/Fork/Pool.php index 70fac92cf46..e1eb0ca7476 100644 --- a/src/Psalm/Internal/Fork/Pool.php +++ b/src/Psalm/Internal/Fork/Pool.php @@ -387,7 +387,7 @@ private function readResultsFromChildren(): array } } - return array_values($terminationMessages); + return $terminationMessages; } /** diff --git a/tests/ArrayAssignmentTest.php b/tests/ArrayAssignmentTest.php index 513505f5ed2..6812f748ea9 100644 --- a/tests/ArrayAssignmentTest.php +++ b/tests/ArrayAssignmentTest.php @@ -831,6 +831,7 @@ public function offsetSet($offset, $value): void 'keyedIntOffsetArrayValues' => [ ' [ @@ -2007,6 +2008,17 @@ function baz(array $bar) : void { }', 'error_message' => 'RedundantCast', ], + 'arrayValuesOnList' => [ + ' $a + * @return list + */ + function foo(array $a) : array { + return array_values($a); + }', + 'error_message' => 'RedundantCast', + ], 'assignToListWithUpdatedForeachKey' => [ 'assertSame( - array_values($issue_data), + $issue_data, json_decode(IssueBuffer::getOutput(IssueBuffer::getIssuesData(), $json_report_options), true) ); } diff --git a/tests/Template/ClassTemplateTest.php b/tests/Template/ClassTemplateTest.php index 0e8acc866c3..dbd7b9cd17b 100644 --- a/tests/Template/ClassTemplateTest.php +++ b/tests/Template/ClassTemplateTest.php @@ -2287,6 +2287,7 @@ public function __construct(array $elements) { * @return static */ public function map(callable $callback) { + /** @psalm-suppress RedundantCast */ return new static(array_values(array_map($callback, $this->elements))); } }