Skip to content

Commit

Permalink
Fix object-like array keys when combining string and automatic keys (f…
Browse files Browse the repository at this point in the history
…ixes #5427). (#5428)

* Fix object-like array keys (fixes #5427).

* Fix incorrect return types for tests.

* Fix false positive list with literal int key.
  • Loading branch information
AndrolGenhald authored Mar 20, 2021
1 parent 42d3bce commit d459071
Show file tree
Hide file tree
Showing 76 changed files with 122 additions and 97 deletions.
28 changes: 12 additions & 16 deletions src/Psalm/Internal/Analyzer/Statements/Expression/ArrayAnalyzer.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public static function analyze(

$array_creation_info = new ArrayCreationInfo();

foreach ($stmt->items as $int_offset => $item) {
foreach ($stmt->items as $item) {
if ($item === null) {
\Psalm\IssueBuffer::add(
new \Psalm\Issue\ParseError(
Expand All @@ -55,7 +55,6 @@ public static function analyze(
$statements_analyzer,
$context,
$array_creation_info,
$int_offset,
$item
);
}
Expand Down Expand Up @@ -219,7 +218,6 @@ private static function analyzeArrayItem(
StatementsAnalyzer $statements_analyzer,
Context $context,
ArrayCreationInfo $array_creation_info,
int $int_offset,
PhpParser\Node\Expr\ArrayItem $item
) : void {
if (ExpressionAnalyzer::analyze($statements_analyzer, $item->value, $context) === false) {
Expand All @@ -236,7 +234,6 @@ private static function analyzeArrayItem(
self::handleUnpackedArray(
$statements_analyzer,
$array_creation_info,
$int_offset,
$item,
$unpacked_array_type
);
Expand Down Expand Up @@ -269,10 +266,9 @@ private static function analyzeArrayItem(
}

$item_key_value = null;
$item_is_list_item = false;

if ($item->key) {
$array_creation_info->all_list = false;

$was_inside_use = $context->inside_use;
$context->inside_use = true;
if (ExpressionAnalyzer::analyze($statements_analyzer, $item->key, $context) === false) {
Expand Down Expand Up @@ -312,16 +308,22 @@ private static function analyzeArrayItem(
} elseif ($key_type->isSingleIntLiteral()) {
$item_key_value = $key_type->getSingleIntLiteral()->value;

if ($item_key_value > $int_offset + $array_creation_info->int_offset_diff) {
$array_creation_info->int_offset_diff = $item_key_value - $int_offset;
if ($item_key_value >= $array_creation_info->int_offset) {
if ($item_key_value === $array_creation_info->int_offset) {
$item_is_list_item = true;
}
$array_creation_info->int_offset = $item_key_value + 1;
}
}
}
} else {
$item_key_value = $int_offset + $array_creation_info->int_offset_diff;
$item_is_list_item = true;
$item_key_value = $array_creation_info->int_offset++;
$array_creation_info->item_key_atomic_types[] = new Type\Atomic\TInt();
}

$array_creation_info->all_list = $array_creation_info->all_list && $item_is_list_item;

if ($item_key_value !== null) {
if (isset($array_creation_info->array_keys[$item_key_value])) {
if (IssueBuffer::accepts(
Expand Down Expand Up @@ -420,13 +422,11 @@ private static function analyzeArrayItem(
private static function handleUnpackedArray(
StatementsAnalyzer $statements_analyzer,
ArrayCreationInfo $array_creation_info,
int $int_offset,
PhpParser\Node\Expr\ArrayItem $item,
Type\Union $unpacked_array_type
) : void {
foreach ($unpacked_array_type->getAtomicTypes() as $unpacked_atomic_type) {
if ($unpacked_atomic_type instanceof Type\Atomic\TKeyedArray) {
$unpacked_array_offset = 0;
foreach ($unpacked_atomic_type->properties as $key => $property_value) {
if (\is_string($key)) {
if (IssueBuffer::accepts(
Expand All @@ -448,15 +448,11 @@ private static function handleUnpackedArray(
array_values($property_value->getAtomicTypes())
);

$new_int_offset = $int_offset + $array_creation_info->int_offset_diff + $unpacked_array_offset;
$new_int_offset = $array_creation_info->int_offset++;

$array_creation_info->array_keys[$new_int_offset] = true;
$array_creation_info->property_types[$new_int_offset] = $property_value;

$unpacked_array_offset++;
}

$array_creation_info->int_offset_diff += $unpacked_array_offset - 1;
} else {
$array_creation_info->can_create_objectlike = false;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class ArrayCreationInfo
/**
* @var int
*/
public $int_offset_diff = 0;
public $int_offset = 0;

/**
* @var bool
Expand Down
2 changes: 1 addition & 1 deletion tests/AnnotationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1181,7 +1181,7 @@ function takesFlags(int $flags) : void {
}

/**
* @return iterable<string,array{string,error_message:string,2?:string[],3?:bool,4?:string}>
* @return iterable<string,array{string,error_message:string,1?:string[],2?:bool,3?:string}>
*/
public function providerInvalidCodeParse(): iterable
{
Expand Down
2 changes: 1 addition & 1 deletion tests/ArgTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ function bar(string $p1, int $p3 = 10) : void {}'
}

/**
* @return iterable<string,array{string,error_message:string,2?:string[],3?:bool,4?:string}>
* @return iterable<string,array{string,error_message:string,1?:string[],2?:bool,3?:string}>
*/
public function providerInvalidCodeParse(): iterable
{
Expand Down
2 changes: 1 addition & 1 deletion tests/ArrayAccessTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -987,7 +987,7 @@ function foo(?array $arr, string $s) : void {
}

/**
* @return iterable<string,array{string,error_message:string,2?:string[],3?:bool,4?:string}>
* @return iterable<string,array{string,error_message:string,1?:string[],2?:bool,3?:string}>
*/
public function providerInvalidCodeParse(): iterable
{
Expand Down
9 changes: 8 additions & 1 deletion tests/ArrayAssignmentTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -862,6 +862,13 @@ public function offsetSet($offset, $value): void
'$f' => 'array{0: string}',
],
],
'dontIncrementIntOffsetForKeyedItems' => [
'<?php
$a = [1, "a" => 2, 3];',
'assertions' => [
'$a' => 'array{0: int, 1: int, a: int}',
],
],
'assignArrayOrSetNull' => [
'<?php
$a = [];
Expand Down Expand Up @@ -1520,7 +1527,7 @@ function with($key): void
}

/**
* @return iterable<string,array{string,error_message:string,2?:string[],3?:bool,4?:string}>
* @return iterable<string,array{string,error_message:string,1?:string[],2?:bool,3?:string}>
*/
public function providerInvalidCodeParse(): iterable
{
Expand Down
8 changes: 4 additions & 4 deletions tests/ArrayFunctionCallTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1922,7 +1922,7 @@ function bar(Container $container, array $data): array {
}

/**
* @return iterable<string,array{string,error_message:string,2?:string[],3?:bool,4?:string}>
* @return iterable<string,array{string,error_message:string,1?:string[],2?:bool,3?:string}>
*/
public function providerInvalidCodeParse(): iterable
{
Expand Down Expand Up @@ -2104,9 +2104,9 @@ function ints(array $ints) : void {}
$list = [3, 2, 5, 9];
usort($list, fn(int $a, string $b): int => (int) ($a > $b));',
'error_message' => 'InvalidScalarArgument',
2 => [],
3 => false,
4 => '7.4',
[],
false,
'7.4',
],
'usortInvalidComparison' => [
'<?php
Expand Down
2 changes: 1 addition & 1 deletion tests/AssertAnnotationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1513,7 +1513,7 @@ function allIsInstanceOf($value, $class): void {}'
}

/**
* @return iterable<string,array{string,error_message:string,2?:string[],3?:bool,4?:string}>
* @return iterable<string,array{string,error_message:string,1?:string[],2?:bool,3?:string}>
*/
public function providerInvalidCodeParse(): iterable
{
Expand Down
2 changes: 1 addition & 1 deletion tests/AssignmentTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ function foo2(?string &$u, ?string &$v): void {}
}

/**
* @return iterable<string,array{string,error_message:string,2?:string[],3?:bool,4?:string}>
* @return iterable<string,array{string,error_message:string,1?:string[],2?:bool,3?:string}>
*/
public function providerInvalidCodeParse(): iterable
{
Expand Down
2 changes: 1 addition & 1 deletion tests/AttributeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ class A {}
}

/**
* @return iterable<string,array{string,error_message:string,2?:string[],3?:bool,4?:string}>
* @return iterable<string,array{string,error_message:string,1?:string[],2?:bool,3?:string}>
*/
public function providerInvalidCodeParse(): iterable
{
Expand Down
2 changes: 1 addition & 1 deletion tests/BinaryOperationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,7 @@ function foo($a, $b): void {
}

/**
* @return iterable<string,array{string,error_message:string,2?:string[],3?:bool,4?:string}>
* @return iterable<string,array{string,error_message:string,1?:string[],2?:bool,3?:string}>
*/
public function providerInvalidCodeParse(): iterable
{
Expand Down
2 changes: 1 addition & 1 deletion tests/CallableTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -868,7 +868,7 @@ abstract class TestClass {
}

/**
* @return iterable<string,array{string,error_message:string,2?:string[],3?:bool,4?:string}>
* @return iterable<string,array{string,error_message:string,1?:string[],2?:bool,3?:string}>
*/
public function providerInvalidCodeParse(): iterable
{
Expand Down
2 changes: 1 addition & 1 deletion tests/ClassLoadOrderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ class C extends B {
}

/**
* @return iterable<string,array{string,error_message:string,2?:string[],3?:bool,4?:string}>
* @return iterable<string,array{string,error_message:string,1?:string[],2?:bool,3?:string}>
*/
public function providerInvalidCodeParse(): iterable
{
Expand Down
2 changes: 1 addition & 1 deletion tests/ClassScopeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ private function boop(): void {}
}

/**
* @return iterable<string,array{string,error_message:string,2?:string[],3?:bool,4?:string}>
* @return iterable<string,array{string,error_message:string,1?:string[],2?:bool,3?:string}>
*/
public function providerInvalidCodeParse(): iterable
{
Expand Down
2 changes: 1 addition & 1 deletion tests/ClassStringTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -771,7 +771,7 @@ class Beep {
}

/**
* @return iterable<string,array{string,error_message:string,2?:string[],3?:bool,4?:string}>
* @return iterable<string,array{string,error_message:string,1?:string[],2?:bool,3?:string}>
*/
public function providerInvalidCodeParse(): iterable
{
Expand Down
2 changes: 1 addition & 1 deletion tests/ClassTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -650,7 +650,7 @@ abstract class C implements I {}
}

/**
* @return iterable<string,array{string,error_message:string,2?:string[],3?:bool,4?:string}>
* @return iterable<string,array{string,error_message:string,1?:string[],2?:bool,3?:string}>
*/
public function providerInvalidCodeParse(): iterable
{
Expand Down
2 changes: 1 addition & 1 deletion tests/CloneTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public function foo(): self {
}

/**
* @return iterable<string,array{string,error_message:string,2?:string[],3?:bool,4?:string}>
* @return iterable<string,array{string,error_message:string,1?:string[],2?:bool,3?:string}>
*/
public function providerInvalidCodeParse(): iterable
{
Expand Down
2 changes: 1 addition & 1 deletion tests/ClosureTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -557,7 +557,7 @@ function maker(string $className) {
}

/**
* @return iterable<string,array{string,error_message:string,2?:string[],3?:bool,4?:string}>
* @return iterable<string,array{string,error_message:string,1?:string[],2?:bool,3?:string}>
*/
public function providerInvalidCodeParse(): iterable
{
Expand Down
2 changes: 1 addition & 1 deletion tests/ConstantTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -952,7 +952,7 @@ class C {
}

/**
* @return iterable<string,array{string,error_message:string,2?:string[],3?:bool,4?:string}>
* @return iterable<string,array{string,error_message:string,1?:string[],2?:bool,3?:string}>
*/
public function providerInvalidCodeParse(): iterable
{
Expand Down
2 changes: 1 addition & 1 deletion tests/DeprecatedAnnotationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ class A {
}

/**
* @return iterable<string,array{string,error_message:string,2?:string[],3?:bool,4?:string}>
* @return iterable<string,array{string,error_message:string,1?:string[],2?:bool,3?:string}>
*/
public function providerInvalidCodeParse(): iterable
{
Expand Down
2 changes: 1 addition & 1 deletion tests/DocblockInheritanceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ function takesF(F $f) : B {
}

/**
* @return iterable<string,array{string,error_message:string,2?:string[],3?:bool,4?:string}>
* @return iterable<string,array{string,error_message:string,1?:string[],2?:bool,3?:string}>
*/
public function providerInvalidCodeParse(): iterable
{
Expand Down
2 changes: 1 addition & 1 deletion tests/EnumTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ class C {}
}

/**
* @return iterable<string,array{string,error_message:string,2?:string[],3?:bool,4?:string}>
* @return iterable<string,array{string,error_message:string,1?:string[],2?:bool,3?:string}>
*/
public function providerInvalidCodeParse(): iterable
{
Expand Down
2 changes: 1 addition & 1 deletion tests/ExtendsFinalClassTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class B extends A {}'
}

/**
* @return iterable<string,array{string,error_message:string,2?:string[],3?:bool,4?:string}>
* @return iterable<string,array{string,error_message:string,1?:string[],2?:bool,3?:string}>
*/
public function providerInvalidCodeParse(): iterable
{
Expand Down
2 changes: 1 addition & 1 deletion tests/ForbiddenCodeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ class ForbiddenCodeTest extends TestCase
use Traits\ValidCodeAnalysisTestTrait;

/**
* @return iterable<string,array{string,error_message:string,2?:string[],3?:bool,4?:string}>
* @return iterable<string,array{string,error_message:string,1?:string[],2?:bool,3?:string}>
*/
public function providerInvalidCodeParse(): iterable
{
Expand Down
2 changes: 1 addition & 1 deletion tests/FunctionCallTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1557,7 +1557,7 @@ function foo(DateTimeImmutable $fooDate): string
}

/**
* @return iterable<string,array{string,error_message:string,2?:string[],3?:bool,4?:string}>
* @return iterable<string,array{string,error_message:string,1?:string[],2?:bool,3?:string}>
*/
public function providerInvalidCodeParse(): iterable
{
Expand Down
2 changes: 1 addition & 1 deletion tests/GeneratorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ function f() : Generator {
}

/**
* @return iterable<string,array{string,error_message:string,2?:string[],3?:bool,4?:string}>
* @return iterable<string,array{string,error_message:string,1?:string[],2?:bool,3?:string}>
*/
public function providerInvalidCodeParse(): iterable
{
Expand Down
2 changes: 1 addition & 1 deletion tests/ImmutableAnnotationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -526,7 +526,7 @@ public function get(): int {
}

/**
* @return iterable<string,array{string,error_message:string,2?:string[],3?:bool,4?:string}>
* @return iterable<string,array{string,error_message:string,1?:string[],2?:bool,3?:string}>
*/
public function providerInvalidCodeParse(): iterable
{
Expand Down
2 changes: 1 addition & 1 deletion tests/InterfaceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -719,7 +719,7 @@ function takesAorB(SomeClass|SomeInterface $some): void {
}

/**
* @return iterable<string,array{string,error_message:string,2?:string[],3?:bool,4?:string}>
* @return iterable<string,array{string,error_message:string,1?:string[],2?:bool,3?:string}>
*/
public function providerInvalidCodeParse(): iterable
{
Expand Down
2 changes: 1 addition & 1 deletion tests/InternalAnnotationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -524,7 +524,7 @@ public function batBat() : void {
}

/**
* @return iterable<string,array{string,error_message:string,2?:string[],3?:bool,4?:string}>
* @return iterable<string,array{string,error_message:string,1?:string[],2?:bool,3?:string}>
*/
public function providerInvalidCodeParse(): iterable
{
Expand Down
2 changes: 1 addition & 1 deletion tests/IssueSuppressionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ function foo($a)
}

/**
* @return iterable<string,array{string,error_message:string,2?:string[],3?:bool,4?:string}>
* @return iterable<string,array{string,error_message:string,1?:string[],2?:bool,3?:string}>
*/
public function providerInvalidCodeParse(): iterable
{
Expand Down
Loading

0 comments on commit d459071

Please sign in to comment.