From af28d650f3e3a6600d2b839faf01134b3f61e5d7 Mon Sep 17 00:00:00 2001 From: Matthew Brown Date: Wed, 18 Dec 2019 23:48:25 +0000 Subject: [PATCH] Fix #2408 - existing offsets checked with isset should be valid --- .../Assignment/ArrayAssignmentAnalyzer.php | 28 ++++++++++++++++--- src/Psalm/Type/Union.php | 8 ++++++ tests/ArrayAssignmentTest.php | 13 +++++++++ 3 files changed, 45 insertions(+), 4 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/ArrayAssignmentAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/ArrayAssignmentAnalyzer.php index 6891ca0a3..e2e2cf060 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/ArrayAssignmentAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/ArrayAssignmentAnalyzer.php @@ -144,6 +144,7 @@ class ArrayAssignmentAnalyzer $parent_var_id = null; + $offset_already_existed = false; $full_var_id = true; $child_stmt = null; @@ -330,6 +331,14 @@ class ArrayAssignmentAnalyzer && !$child_stmt_var_type->hasObjectType() ) { $array_var_id = $root_var_id . implode('', $var_id_additions); + $parent_var_id = $root_var_id . implode('', \array_slice($var_id_additions, 0, -1)); + + if (isset($context->vars_in_scope[$array_var_id]) + && !$context->vars_in_scope[$array_var_id]->possibly_undefined + ) { + $offset_already_existed = true; + } + $context->vars_in_scope[$array_var_id] = clone $assignment_type; } @@ -519,10 +528,21 @@ class ArrayAssignmentAnalyzer $array_atomic_key_type = Type::getMixed(); } - $array_atomic_type = new TNonEmptyArray([ - $array_atomic_key_type, - $current_type, - ]); + if ($offset_already_existed + && $child_stmt + && $parent_var_id + && ($parent_type = $context->vars_in_scope[$parent_var_id] ?? null) + && $parent_type->hasList() + ) { + $array_atomic_type = new TNonEmptyList( + $current_type, + ); + } else { + $array_atomic_type = new TNonEmptyArray([ + $array_atomic_key_type, + $current_type, + ]); + } } else { $array_atomic_type = new TNonEmptyList($current_type); } diff --git a/src/Psalm/Type/Union.php b/src/Psalm/Type/Union.php index ac50a35ae..90a950e39 100644 --- a/src/Psalm/Type/Union.php +++ b/src/Psalm/Type/Union.php @@ -618,6 +618,14 @@ class Union return isset($this->types['array']); } + /** + * @return bool + */ + public function hasList() + { + return isset($this->types['array']) && $this->types['array'] instanceof Atomic\TList; + } + /** * @return bool */ diff --git a/tests/ArrayAssignmentTest.php b/tests/ArrayAssignmentTest.php index a1eaaf306..ea7528bfa 100644 --- a/tests/ArrayAssignmentTest.php +++ b/tests/ArrayAssignmentTest.php @@ -1273,6 +1273,19 @@ class ArrayAssignmentTest extends TestCase '$arr3' => 'array{1: int, 2: int, 3: int, 4: int}', ] ], + 'listPropertyAssignmentAfterIsset' => [ + ' */ + private $list = []; + + public function override(int $offset): void { + if (isset($this->list[$offset])) { + $this->list[$offset] = "a"; + } + } + }', + ], ]; }