1
0
mirror of https://github.com/danog/psalm.git synced 2024-12-11 16:59:45 +01:00
This commit is contained in:
Daniil Gentili 2022-11-20 19:01:49 +01:00
parent 5b3358937f
commit cc4461f756
7 changed files with 59 additions and 86 deletions

View File

@ -148,7 +148,12 @@ class ArrayAnalyzer
if ($array_creation_info->can_be_empty) { if ($array_creation_info->can_be_empty) {
$array_type = new TList($item_value_type ?? Type::getMixed()); $array_type = new TList($item_value_type ?? Type::getMixed());
} else { } else {
$array_type = new TNonEmptyList($item_value_type ?? Type::getMixed()); $array_type = new TKeyedArray(
[$item_value_type ?? Type::getMixed()],
null,
[Type::getInt(), Type::getMixed()],
true
);
} }
$stmt_type = new Union([ $stmt_type = new Union([

View File

@ -345,7 +345,7 @@ class ArrayAssignmentAnalyzer
&& $key_values[0] instanceof TLiteralInt && $key_values[0] instanceof TLiteralInt
) { ) {
$key_value = $key_values[0]; $key_value = $key_values[0];
$count = ($type->count ?? $type->min_count) ?? 1; $count = $type->getMinCount() ?? 1;
if ($key_value->value < $count) { if ($key_value->value < $count) {
$has_matching_objectlike_property = true; $has_matching_objectlike_property = true;

View File

@ -179,15 +179,33 @@ class ArrayMergeReturnTypeProvider implements FunctionReturnTypeProviderInterfac
continue; continue;
} }
if ($unpacked_type_part instanceof TMixed
&& $unpacked_type_part->from_loop_isset
) {
$unpacked_type_part = new TArray([
Type::getArrayKey(),
Type::getMixed(true),
]);
}
if ($unpacked_type_part instanceof TList) { if ($unpacked_type_part instanceof TList) {
$all_keyed_arrays = false; $all_keyed_arrays = false;
if ($unpacked_type_part instanceof TNonEmptyList && !$unpacking_possibly_empty) { if ($is_replace) {
$any_nonempty = true; foreach ($generic_properties as $key => $keyed_type) {
} else { if (is_string($key)) {
$all_nonempty_lists = false; continue;
}
$generic_properties[$key] = Type::combineUnionTypes(
$keyed_type,
$unpacked_type_part->type_param,
$codebase
);
}
} }
$all_nonempty_lists = false;
} elseif ($unpacked_type_part instanceof TArray) { } elseif ($unpacked_type_part instanceof TArray) {
if ($unpacked_type_part->isEmptyArray()) { if ($unpacked_type_part->isEmptyArray()) {
continue; continue;
@ -211,13 +229,6 @@ class ArrayMergeReturnTypeProvider implements FunctionReturnTypeProviderInterfac
if ($unpacked_type_part instanceof TNonEmptyArray && !$unpacking_possibly_empty) { if ($unpacked_type_part instanceof TNonEmptyArray && !$unpacking_possibly_empty) {
$any_nonempty = true; $any_nonempty = true;
} }
} elseif ($unpacked_type_part instanceof TMixed
&& $unpacked_type_part->from_loop_isset
) {
$unpacked_type_part = new TArray([
Type::getArrayKey(),
Type::getMixed(true),
]);
} else { } else {
return Type::getArray(); return Type::getArray();
} }

View File

@ -722,15 +722,23 @@ class SimpleAssertionReconciler extends Reconciler
$count $count
); );
$existing_var_type->removeType('array');
$existing_var_type->addType( $existing_var_type->addType(
$non_empty_array $non_empty_array
); );
} elseif ($array_atomic_type instanceof TList) { } elseif ($array_atomic_type instanceof TList) {
$non_empty_list = new TNonEmptyList( $properties = [];
$array_atomic_type->type_param, for ($x = 0; $x < $count; $x++) {
$count $properties []= $array_atomic_type->type_param;
}
$non_empty_list = new TKeyedArray(
$properties,
null,
null,
true
); );
$existing_var_type->removeType('array');
$existing_var_type->addType( $existing_var_type->addType(
$non_empty_list $non_empty_list
); );

View File

@ -1520,13 +1520,22 @@ class TypeCombiner
[Type::getInt(), $combination->array_type_params[1]], [Type::getInt(), $combination->array_type_params[1]],
true true
); );
} else if ($combination->array_counts && count($combination->array_counts) === 1) {
$cnt = array_keys($combination->array_counts)[0];
$properties = [];
for ($x = 0; $x < $cnt; $x++) {
$properties []= $generic_type_params[1];
}
$array_type = new TKeyedArray(
$properties,
null,
null,
true
);
} else { } else {
/** @psalm-suppress ArgumentTypeCoercion */ /** @psalm-suppress ArgumentTypeCoercion */
$array_type = new TNonEmptyList( $array_type = new TNonEmptyList(
$generic_type_params[1], $generic_type_params[1],
$combination->array_counts && count($combination->array_counts) === 1
? array_keys($combination->array_counts)[0]
: null,
$combination->array_min_counts $combination->array_min_counts
? min(array_keys($combination->array_min_counts)) ? min(array_keys($combination->array_min_counts))
: null : null
@ -1536,9 +1545,6 @@ class TypeCombiner
/** @psalm-suppress ArgumentTypeCoercion */ /** @psalm-suppress ArgumentTypeCoercion */
$array_type = new TNonEmptyArray( $array_type = new TNonEmptyArray(
$generic_type_params, $generic_type_params,
$combination->array_counts && count($combination->array_counts) === 1
? array_keys($combination->array_counts)[0]
: null,
$combination->array_min_counts $combination->array_min_counts
? min(array_keys($combination->array_min_counts)) ? min(array_keys($combination->array_min_counts))
: null : null

View File

@ -21,6 +21,7 @@ use Psalm\Type\Atomic\TFloat;
use Psalm\Type\Atomic\TInt; use Psalm\Type\Atomic\TInt;
use Psalm\Type\Atomic\TIntRange; use Psalm\Type\Atomic\TIntRange;
use Psalm\Type\Atomic\TIterable; use Psalm\Type\Atomic\TIterable;
use Psalm\Type\Atomic\TKeyedArray;
use Psalm\Type\Atomic\TList; use Psalm\Type\Atomic\TList;
use Psalm\Type\Atomic\TLiteralClassString; use Psalm\Type\Atomic\TLiteralClassString;
use Psalm\Type\Atomic\TLiteralFloat; use Psalm\Type\Atomic\TLiteralFloat;
@ -454,7 +455,12 @@ abstract class Type
*/ */
public static function getNonEmptyList(): Union public static function getNonEmptyList(): Union
{ {
$type = new TNonEmptyList(new Union([new TMixed])); $type = new TKeyedArray(
[self::getMixed()],
null,
[self::getInt(), self::getMixed()],
true
);
return new Union([$type]); return new Union([$type]);
} }

View File

@ -1,63 +0,0 @@
<?php
namespace Psalm\Type\Atomic;
use Psalm\Type\Union;
/**
* Represents a non-empty list
* @psalm-immutable
*/
class TNonEmptyList extends TList
{
/**
* @var positive-int|null
*/
public $count;
/**
* @var positive-int|null
*/
public $min_count;
/** @var non-empty-lowercase-string */
public const KEY = 'non-empty-list';
/**
* Constructs a new instance of a list
*
* @param positive-int|null $count
* @param positive-int|null $min_count
*/
public function __construct(
Union $type_param,
?int $count = null,
?int $min_count = null,
bool $from_docblock = false
) {
$this->type_param = $type_param;
$this->count = $count;
$this->min_count = $min_count;
$this->from_docblock = $from_docblock;
}
/**
* @param positive-int|null $count
*
* @return static
*/
public function setCount(?int $count): self
{
if ($count === $this->count) {
return $this;
}
$cloned = clone $this;
$cloned->count = $count;
return $cloned;
}
public function getAssertionString(): string
{
return 'non-empty-list';
}
}