Skip to content

Commit

Permalink
Merge pull request #6613 from orklah/string-unpacking
Browse files Browse the repository at this point in the history
String unpacking
  • Loading branch information
orklah committed Oct 13, 2021
2 parents 91c3d78 + c96be1f commit e17290a
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 73 deletions.
66 changes: 41 additions & 25 deletions src/Psalm/Internal/Analyzer/Statements/Expression/ArrayAnalyzer.php
Expand Up @@ -247,7 +247,8 @@ private static function analyzeArrayItem(
$statements_analyzer,
$array_creation_info,
$item,
$unpacked_array_type
$unpacked_array_type,
$codebase
);

if (($data_flow_graph = $statements_analyzer->data_flow_graph)
Expand Down Expand Up @@ -482,35 +483,42 @@ private static function handleUnpackedArray(
StatementsAnalyzer $statements_analyzer,
ArrayCreationInfo $array_creation_info,
PhpParser\Node\Expr\ArrayItem $item,
Type\Union $unpacked_array_type
Type\Union $unpacked_array_type,
Codebase $codebase
) : void {
foreach ($unpacked_array_type->getAtomicTypes() as $unpacked_atomic_type) {
if ($unpacked_atomic_type instanceof Type\Atomic\TKeyedArray) {
foreach ($unpacked_atomic_type->properties as $key => $property_value) {
if (\is_string($key)) {
if (IssueBuffer::accepts(
new DuplicateArrayKey(
'String keys are not supported in unpacked arrays',
new CodeLocation($statements_analyzer->getSource(), $item->value)
),
$statements_analyzer->getSuppressedIssues()
)) {
// fall through
if ($codebase->php_major_version < 8 ||
($codebase->php_major_version === 8 && $codebase->php_minor_version < 1)
) {
if (IssueBuffer::accepts(
new DuplicateArrayKey(
'String keys are not supported in unpacked arrays',
new CodeLocation($statements_analyzer->getSource(), $item->value)
),
$statements_analyzer->getSuppressedIssues()
)) {
// fall through
}

return;
}

return;
$new_offset = $key;
$array_creation_info->item_key_atomic_types[] = new Type\Atomic\TLiteralString($new_offset);
} else {
$new_offset = $array_creation_info->int_offset++;
$array_creation_info->item_key_atomic_types[] = new Type\Atomic\TLiteralInt($new_offset);
}

$new_int_offset = $array_creation_info->int_offset++;

$array_creation_info->item_key_atomic_types[] = new Type\Atomic\TLiteralInt($new_int_offset);
$array_creation_info->item_value_atomic_types = array_merge(
$array_creation_info->item_value_atomic_types,
array_values($property_value->getAtomicTypes())
);

$array_creation_info->array_keys[$new_int_offset] = true;
$array_creation_info->property_types[$new_int_offset] = $property_value;
$array_creation_info->array_keys[$new_offset] = true;
$array_creation_info->property_types[$new_offset] = $property_value;
}
} else {
$codebase = $statements_analyzer->getCodebase();
Expand All @@ -529,15 +537,23 @@ private static function handleUnpackedArray(
$array_creation_info->can_create_objectlike = false;

if ($unpacked_atomic_type->type_params[0]->hasString()) {
if (IssueBuffer::accepts(
new DuplicateArrayKey(
'String keys are not supported in unpacked arrays',
new CodeLocation($statements_analyzer->getSource(), $item->value)
),
$statements_analyzer->getSuppressedIssues()
)) {
// fall through
if ($codebase->php_major_version < 8 ||
($codebase->php_major_version === 8 && $codebase->php_minor_version < 1)
) {
if (IssueBuffer::accepts(
new DuplicateArrayKey(
'String keys are not supported in unpacked arrays',
new CodeLocation($statements_analyzer->getSource(), $item->value)
),
$statements_analyzer->getSuppressedIssues()
)) {
// fall through
}

return;
}

$array_creation_info->item_key_atomic_types[] = new Type\Atomic\TString();
} elseif ($unpacked_atomic_type->type_params[0]->hasInt()) {
$array_creation_info->item_key_atomic_types[] = new Type\Atomic\TInt();
}
Expand Down
Expand Up @@ -671,20 +671,20 @@ private static function handleUnpackedArray(
if ($unpacked_atomic_type instanceof Type\Atomic\TKeyedArray) {
foreach ($unpacked_atomic_type->properties as $key => $property_value) {
if (\is_string($key)) {
// string keys are not supported in unpacked arrays
return false;
$new_offset = $key;
$array_creation_info->item_key_atomic_types[] = new Type\Atomic\TLiteralString($new_offset);
} else {
$new_offset = $array_creation_info->int_offset++;
$array_creation_info->item_key_atomic_types[] = new Type\Atomic\TLiteralInt($new_offset);
}

$new_int_offset = $array_creation_info->int_offset++;

$array_creation_info->item_key_atomic_types[] = new Type\Atomic\TLiteralInt($new_int_offset);
$array_creation_info->item_value_atomic_types = array_merge(
$array_creation_info->item_value_atomic_types,
array_values($property_value->getAtomicTypes())
);

$array_creation_info->array_keys[$new_int_offset] = true;
$array_creation_info->property_types[$new_int_offset] = $property_value;
$array_creation_info->array_keys[$new_offset] = true;
$array_creation_info->property_types[$new_offset] = $property_value;
}
} elseif ($unpacked_atomic_type instanceof Type\Atomic\TArray) {
/** @psalm-suppress PossiblyUndefinedArrayOffset provably true, but Psalm can’t see it */
Expand All @@ -694,8 +694,7 @@ private static function handleUnpackedArray(
$array_creation_info->can_create_objectlike = false;

if ($unpacked_atomic_type->type_params[0]->hasString()) {
// string keys are not supported in unpacked arrays
return false;
$array_creation_info->item_key_atomic_types[] = new Type\Atomic\TString();
}

if ($unpacked_atomic_type->type_params[0]->hasInt()) {
Expand Down
97 changes: 58 additions & 39 deletions tests/ArrayAssignmentTest.php
Expand Up @@ -1234,6 +1234,19 @@ function foo(array $arr) : string {
'$arr3' => 'array{1: int, 2: int, 3: int, 4: int}',
]
],
'arraySpreadWithString' => [
'<?php
$x = [
"a" => 0,
...["a" => 1],
...["b" => 2]
];',
[
'$x===' => 'array{a: 1, b: 2}',
],
[],
'8.1'
],
'listPropertyAssignmentAfterIsset' => [
'<?php
class Collection {
Expand Down Expand Up @@ -1631,6 +1644,51 @@ final class Token
',
'assertions' => ['$_a===' => 'array{16: 16, 17: 17, 18: 18}']
],
'unpackTypedIterableWithStringKeysIntoArray' => [
'<?php
/**
* @param iterable<string, string> $data
* @return list<string>
*/
function unpackIterable(iterable $data): array
{
return [...$data];
}',
[],
[],
'8.1'
],
'unpackTypedTraversableWithStringKeysIntoArray' => [
'<?php
/**
* @param Traversable<string, string> $data
* @return list<string>
*/
function unpackIterable(Traversable $data): array
{
return [...$data];
}',
[],
[],
'8.1'
],
'unpackArrayWithArrayKeyIntoArray' => [
'<?php
/**
* @param array<array-key, mixed> $data
* @return list<mixed>
*/
function unpackArray(array $data): array
{
return [...$data];
}',
[],
[],
'8.1'
],
];
}

Expand Down Expand Up @@ -1882,45 +1940,6 @@ function getTwoPartsLocale(array $cache, string $a, string $b) : string
}',
'error_message' => 'NullableReturnStatement',
],
'unpackTypedIterableWithStringKeysIntoArray' => [
'<?php
/**
* @param iterable<string, string> $data
* @return list<string>
*/
function unpackIterable(iterable $data): array
{
return [...$data];
}',
'error_message' => 'DuplicateArrayKey'
],
'unpackTypedTraversableWithStringKeysIntoArray' => [
'<?php
/**
* @param Traversable<string, string> $data
* @return list<string>
*/
function unpackIterable(Traversable $data): array
{
return [...$data];
}',
'error_message' => 'DuplicateArrayKey'
],
'unpackArrayWithArrayKeyIntoArray' => [
'<?php
/**
* @param array<array-key, mixed> $data
* @return list<mixed>
*/
function unpackArray(array $data): array
{
return [...$data];
}',
'error_message' => 'DuplicateArrayKey',
],
'ArrayCreateOffsetObject' => [
'<?php
$_a = [new stdClass => "a"];
Expand Down

0 comments on commit e17290a

Please sign in to comment.