1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-26 20:34:47 +01:00

Fix issue with nested object-like array updating

This commit is contained in:
Matt Brown 2017-11-17 16:33:36 -05:00
parent 1c25ca1a0c
commit 39baa00fd3
4 changed files with 199 additions and 25 deletions

View File

@ -40,6 +40,7 @@ use Psalm\Issue\UndefinedPropertyFetch;
use Psalm\Issue\UndefinedThisPropertyFetch;
use Psalm\IssueBuffer;
use Psalm\Type;
use Psalm\Type\Atomic\ObjectLike;
use Psalm\Type\Atomic\Scalar;
use Psalm\Type\Atomic\TArray;
use Psalm\Type\Atomic\TBool;
@ -243,15 +244,15 @@ class FetchChecker
if (isset($class_storage->pseudo_property_get_types['$' . $stmt->name])) {
$stmt->inferredType = clone $class_storage->pseudo_property_get_types['$' . $stmt->name];
continue;
} else {
$stmt->inferredType = Type::getMixed();
/*
* If we have an explicit list of all allowed magic properties on the class, and we're
* not in that list, fall through
*/
if (!$class_storage->sealed_properties) {
continue;
}
}
$stmt->inferredType = Type::getMixed();
/*
* If we have an explicit list of all allowed magic properties on the class, and we're
* not in that list, fall through
*/
if (!$class_storage->sealed_properties) {
continue;
}
}
}
@ -827,7 +828,7 @@ class FetchChecker
if (!$keyed_assignment_type || $keyed_assignment_type->isEmpty()) {
if (!$assignment_key_type->isMixed() && !$assignment_key_type->hasInt() && $assignment_key_value) {
$keyed_assignment_type = new Type\Union([
new Type\Atomic\ObjectLike([
new ObjectLike([
$assignment_key_value => $assignment_value_type,
]),
]);
@ -887,14 +888,35 @@ class FetchChecker
}
$type = $refined_type;
} elseif ($type instanceof Type\Atomic\ObjectLike && $assignment_key_value) {
if (isset($type->properties[$assignment_key_value])) {
$type->properties[$assignment_key_value] = Type::combineUnionTypes(
$type->properties[$assignment_key_value],
$assignment_value_type
);
} elseif ($type instanceof ObjectLike) {
if ($assignment_key_value) {
if (isset($type->properties[$assignment_key_value])) {
$type->properties[$assignment_key_value] = Type::combineUnionTypes(
$type->properties[$assignment_key_value],
$assignment_value_type
);
} else {
$type->properties[$assignment_key_value] = $assignment_value_type;
}
} else {
$type->properties[$assignment_key_value] = $assignment_value_type;
$refined_type = self::refineArrayType(
$statements_checker,
$type,
$assignment_key_type,
$assignment_value_type,
$var_id,
new CodeLocation($statements_checker->getSource(), $stmt)
);
if ($refined_type === false) {
return false;
}
if ($refined_type === null) {
continue;
}
$type = $refined_type;
}
}
}
@ -967,11 +989,11 @@ class FetchChecker
continue;
}
if ($type instanceof Type\Atomic\TArray || $type instanceof Type\Atomic\ObjectLike) {
if ($type instanceof TArray || $type instanceof ObjectLike) {
$value_index = null;
$has_array_access = true;
if ($type instanceof Type\Atomic\TArray) {
if ($type instanceof TArray) {
// create a union type to pass back to the statement
$value_index = count($type->type_params) - 1;
@ -1017,7 +1039,7 @@ class FetchChecker
}
if ($array_var_id === $var_id) {
if ($type instanceof Type\Atomic\ObjectLike ||
if ($type instanceof ObjectLike ||
(
$type instanceof TArray &&
!$used_key_type->hasInt() &&
@ -1030,7 +1052,7 @@ class FetchChecker
if ($properties) {
$assignment_type = new Type\Union([
new Type\Atomic\ObjectLike($properties),
new ObjectLike($properties),
]);
} else {
if (!$keyed_assignment_type) {
@ -1143,7 +1165,7 @@ class FetchChecker
$stmt->inferredType = Type::getMixed();
}
}
} elseif ($type instanceof Type\Atomic\ObjectLike) {
} elseif ($type instanceof ObjectLike) {
if ($string_key_value || $int_key_value !== null) {
if ($string_key_value && isset($type->properties[$string_key_value])) {
$has_valid_offset = true;
@ -1366,8 +1388,9 @@ class FetchChecker
$project_checker = $statements_checker->getFileChecker()->project_checker;
if (!$type instanceof TArray &&
(!$type instanceof TNamedObject
if (!$type instanceof TArray
&& !$type instanceof ObjectLike
&& (!$type instanceof TNamedObject
|| !ClassChecker::classImplements($project_checker, $type->value, 'ArrayAccess'))
) {
if (IssueBuffer::accepts(
@ -1384,6 +1407,13 @@ class FetchChecker
return $type;
}
if ($type instanceof ObjectLike) {
$type = new TArray([
Type::getString(),
$type->getGenericTypeParam(),
]);
}
if ($type instanceof Type\Atomic\Generic && $type instanceof TArray) {
if ($type->type_params[1]->isEmpty()) {
$type->type_params[0] = $assignment_key_type;

View File

@ -566,6 +566,34 @@ abstract class Type
foreach ($combination->type_params as $generic_type => $generic_type_params) {
if ($generic_type === 'array') {
if ($combination->objectlike_entries) {
$object_like_generic_type = null;
foreach ($combination->objectlike_entries as $property_key => $property_type) {
if ($object_like_generic_type) {
$object_like_generic_type = Type::combineUnionTypes(
$property_type,
$object_like_generic_type
);
} else {
$object_like_generic_type = $property_type;
}
}
if (!$object_like_generic_type) {
throw new \InvalidArgumentException('Cannot be null');
}
$generic_type_params[0] = Type::combineUnionTypes(
$generic_type_params[0],
Type::getString()
);
$generic_type_params[1] = Type::combineUnionTypes(
$generic_type_params[1],
$object_like_generic_type
);
}
$new_types[] = new TArray($generic_type_params);
} elseif (!isset($combination->value_types[$generic_type])) {
$new_types[] = new TGenericObject($generic_type, $generic_type_params);

View File

@ -101,7 +101,7 @@ class ArrayAccessTest extends TestCase
takesBool($b[2]);
}',
],
'updateObjectLike' => [
'updateStringIntKey' => [
'<?php
$string = "c";
$int = 5;
@ -138,6 +138,94 @@ class ArrayAccessTest extends TestCase
'$e' => 'array<int|string, int>',
],
],
'updateStringIntKeyWithIntRootAndNumberOffset' => [
'<?php
$string = "c";
$int = 5;
$a = [];
$a[0]["a"] = 5;
$a[0][0] = 3;',
'assertions' => [
'$a' => 'array<int, array<string|int, int>>',
],
],
'updateStringIntKeyWithIntRoot' => [
'<?php
$string = "c";
$int = 5;
$b = [];
$b[0][$string] = 5;
$b[0][0] = 3;
$c = [];
$c[0][0] = 3;
$c[0][$string] = 5;
$d = [];
$d[0][$int] = 3;
$d[0]["a"] = 5;
$e = [];
$e[0][$int] = 3;
$e[0][$string] = 5;',
'assertions' => [
'$b' => 'array<int, array<string|int, int>>',
'$c' => 'array<int, array<int|string, int>>',
'$d' => 'array<int, array<int|string, int>>',
'$e' => 'array<int, array<int|string, int>>',
],
],
'updateStringIntKeyWithObjectLikeRootAndNumberOffset' => [
'<?php
$string = "c";
$int = 5;
$a = [];
$a["root"]["a"] = 5;
$a["root"][0] = 3;',
'assertions' => [
'$a' => 'array{root:array<string|int, int>}',
],
],
'updateStringIntKeyWithObjectLikeRoot' => [
'<?php
$string = "c";
$int = 5;
$b = [];
$b["root"][$string] = 5;
$b["root"][0] = 3;
$c = [];
$c["root"][0] = 3;
$c["root"][$string] = 5;
$d = [];
$d["root"][$int] = 3;
$d["root"]["a"] = 5;
$e = [];
$e["root"][$int] = 3;
$e["root"][$string] = 5;',
'assertions' => [
'$b' => 'array{root:array<string|int, int>}',
'$c' => 'array{root:array<int|string, int>}',
'$d' => 'array{root:array<int|string, int>}',
'$e' => 'array{root:array<int|string, int>}',
],
],
];
}

View File

@ -154,6 +154,34 @@ class TypeCombinationTest extends TestCase
'array{a:string,b:string}',
],
],
'combineObjectTypeWithIntKeyedArray' => [
'array<int|string, string|int>',
[
'array{a:int}',
'array<int, string>',
],
],
'combineNestedObjectTypeWithObjectLikeIntKeyedArray' => [
'array{a:array<int|string, string|int>}',
[
'array{a:array{a:int}}',
'array{a:array<int, string>}',
],
],
'combineIntKeyedObjectTypeWithNestedIntKeyedArray' => [
'array<int, array<int|string, string|int>>',
[
'array<int, array{a:int}>',
'array<int, array<int, string>>',
],
],
'combineNestedObjectTypeWithNestedIntKeyedArray' => [
'array<int|string, array<int|string, string|int>>',
[
'array{a:array{a:int}}',
'array<int, array<int, string>>',
],
],
];
}