mirror of
https://github.com/danog/psalm.git
synced 2025-01-22 05:41:20 +01:00
Fix #713 - support offsets of known array types
This commit is contained in:
parent
0181fce46f
commit
21261172a8
@ -96,7 +96,7 @@ class FunctionChecker extends FunctionLikeChecker
|
|||||||
&& $atomic_types['array']->sealed
|
&& $atomic_types['array']->sealed
|
||||||
) {
|
) {
|
||||||
return new Type\Union([
|
return new Type\Union([
|
||||||
new Type\Atomic\TInt([count($atomic_types['array']->properties) => true])
|
new Type\Atomic\TLiteralInt([count($atomic_types['array']->properties) => true])
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -60,7 +60,7 @@ class ArrayChecker
|
|||||||
if ($item->key instanceof PhpParser\Node\Scalar\String_
|
if ($item->key instanceof PhpParser\Node\Scalar\String_
|
||||||
&& preg_match('/^(0|[1-9][0-9]*)$/', $item->key->value)
|
&& preg_match('/^(0|[1-9][0-9]*)$/', $item->key->value)
|
||||||
) {
|
) {
|
||||||
$key_type = Type::getInt(false, [$item->key->value => true]);
|
$key_type = Type::getInt(false, [(int)$item->key->value => true]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($item_key_type) {
|
if ($item_key_type) {
|
||||||
@ -154,7 +154,7 @@ class ArrayChecker
|
|||||||
$item_value_type ?: Type::getMixed(),
|
$item_value_type ?: Type::getMixed(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$array_type->count = new Type\Atomic\TInt([count($stmt->items) => true]);
|
$array_type->count = new Type\Atomic\TLiteralInt([count($stmt->items) => true]);
|
||||||
|
|
||||||
$stmt->inferredType = new Type\Union([
|
$stmt->inferredType = new Type\Union([
|
||||||
$array_type,
|
$array_type,
|
||||||
|
@ -323,7 +323,7 @@ class ArrayAssignmentChecker
|
|||||||
} elseif ($atomic_root_types['array'] instanceof ObjectLike
|
} elseif ($atomic_root_types['array'] instanceof ObjectLike
|
||||||
&& $atomic_root_types['array']->sealed
|
&& $atomic_root_types['array']->sealed
|
||||||
) {
|
) {
|
||||||
$array_atomic_type->count = new Type\Atomic\TInt([
|
$array_atomic_type->count = new Type\Atomic\TLiteralInt([
|
||||||
count($atomic_root_types['array']->properties) => true
|
count($atomic_root_types['array']->properties) => true
|
||||||
]);
|
]);
|
||||||
$from_countable_object_like = true;
|
$from_countable_object_like = true;
|
||||||
@ -350,7 +350,7 @@ class ArrayAssignmentChecker
|
|||||||
$new_counts = [];
|
$new_counts = [];
|
||||||
|
|
||||||
foreach ($atomic_root_types['array']->count->values as $count => $_) {
|
foreach ($atomic_root_types['array']->count->values as $count => $_) {
|
||||||
$new_counts[(string)((int)$count + 1)] = true;
|
$new_counts[((int)$count + 1)] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
$atomic_root_types['array']->count->values = $new_counts;
|
$atomic_root_types['array']->count->values = $new_counts;
|
||||||
|
@ -169,10 +169,16 @@ class MethodCallChecker extends \Psalm\Checker\Statements\Expression\CallChecker
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case Type\Atomic\TInt::class:
|
case Type\Atomic\TInt::class:
|
||||||
|
case Type\Atomic\TLiteralInt::class:
|
||||||
|
case Type\Atomic\TFloat::class:
|
||||||
|
case Type\Atomic\TLiteralFloat::class:
|
||||||
case Type\Atomic\TBool::class:
|
case Type\Atomic\TBool::class:
|
||||||
case Type\Atomic\TTrue::class:
|
case Type\Atomic\TTrue::class:
|
||||||
case Type\Atomic\TArray::class:
|
case Type\Atomic\TArray::class:
|
||||||
|
case Type\Atomic\TArray::class:
|
||||||
|
case Type\Atomic\ObjectLike::class:
|
||||||
case Type\Atomic\TString::class:
|
case Type\Atomic\TString::class:
|
||||||
|
case Type\Atomic\TLiteralString::class:
|
||||||
case Type\Atomic\TNumericString::class:
|
case Type\Atomic\TNumericString::class:
|
||||||
case Type\Atomic\TClassString::class:
|
case Type\Atomic\TClassString::class:
|
||||||
$invalid_method_call_types[] = (string)$class_type_part;
|
$invalid_method_call_types[] = (string)$class_type_part;
|
||||||
|
@ -29,7 +29,12 @@ use Psalm\Type;
|
|||||||
use Psalm\Type\Atomic\ObjectLike;
|
use Psalm\Type\Atomic\ObjectLike;
|
||||||
use Psalm\Type\Atomic\TArray;
|
use Psalm\Type\Atomic\TArray;
|
||||||
use Psalm\Type\Atomic\TEmpty;
|
use Psalm\Type\Atomic\TEmpty;
|
||||||
|
use Psalm\Type\Atomic\TLiteralFloat;
|
||||||
|
use Psalm\Type\Atomic\TLiteralInt;
|
||||||
|
use Psalm\Type\Atomic\TLiteralString;
|
||||||
|
use Psalm\Type\Atomic\TFloat;
|
||||||
use Psalm\Type\Atomic\TGenericParam;
|
use Psalm\Type\Atomic\TGenericParam;
|
||||||
|
use Psalm\Type\Atomic\TInt;
|
||||||
use Psalm\Type\Atomic\TMixed;
|
use Psalm\Type\Atomic\TMixed;
|
||||||
use Psalm\Type\Atomic\TNamedObject;
|
use Psalm\Type\Atomic\TNamedObject;
|
||||||
use Psalm\Type\Atomic\TNull;
|
use Psalm\Type\Atomic\TNull;
|
||||||
@ -194,6 +199,26 @@ class ArrayFetchChecker
|
|||||||
|| $stmt->dim instanceof PhpParser\Node\Scalar\LNumber
|
|| $stmt->dim instanceof PhpParser\Node\Scalar\LNumber
|
||||||
) {
|
) {
|
||||||
$key_value = $stmt->dim->value;
|
$key_value = $stmt->dim->value;
|
||||||
|
} elseif (isset($stmt->dim->inferredType)) {
|
||||||
|
foreach ($stmt->dim->inferredType->getTypes() as $possible_value_type) {
|
||||||
|
if ($possible_value_type instanceof TLiteralString
|
||||||
|
|| $possible_value_type instanceof TLiteralFloat
|
||||||
|
|| $possible_value_type instanceof TLiteralInt
|
||||||
|
) {
|
||||||
|
if (!$key_value && count($possible_value_type->values) === 1) {
|
||||||
|
$key_value = array_keys($possible_value_type->values)[0];
|
||||||
|
} else {
|
||||||
|
$key_value = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} elseif ($possible_value_type instanceof TString
|
||||||
|
|| $possible_value_type instanceof TFloat
|
||||||
|
|| $possible_value_type instanceof TInt
|
||||||
|
) {
|
||||||
|
$key_value = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$array_access_type = null;
|
$array_access_type = null;
|
||||||
@ -305,7 +330,7 @@ class ArrayFetchChecker
|
|||||||
true,
|
true,
|
||||||
$offset_type->ignore_falsable_issues
|
$offset_type->ignore_falsable_issues
|
||||||
)) {
|
)) {
|
||||||
$expected_offset_types[] = (string)$type->type_params[0];
|
$expected_offset_types[] = $type->type_params[0]->getId();
|
||||||
} else {
|
} else {
|
||||||
$has_valid_offset = true;
|
$has_valid_offset = true;
|
||||||
}
|
}
|
||||||
@ -315,7 +340,7 @@ class ArrayFetchChecker
|
|||||||
$new_counts = [];
|
$new_counts = [];
|
||||||
|
|
||||||
foreach ($type->count->values as $count => $_) {
|
foreach ($type->count->values as $count => $_) {
|
||||||
$new_counts[(string)((int)$count + 1)] = true;
|
$new_counts[(int)$count + 1] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
$type->count->values = $new_counts;
|
$type->count->values = $new_counts;
|
||||||
@ -388,18 +413,20 @@ class ArrayFetchChecker
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$object_like_keys = array_keys($type->properties);
|
if (!$inside_isset || $type->sealed) {
|
||||||
|
$object_like_keys = array_keys($type->properties);
|
||||||
|
|
||||||
if (count($object_like_keys) === 1) {
|
if (count($object_like_keys) === 1) {
|
||||||
$expected_keys_string = '\'' . $object_like_keys[0] . '\'';
|
$expected_keys_string = '\'' . $object_like_keys[0] . '\'';
|
||||||
} else {
|
} else {
|
||||||
$last_key = array_pop($object_like_keys);
|
$last_key = array_pop($object_like_keys);
|
||||||
$expected_keys_string = '\'' . implode('\', \'', $object_like_keys) .
|
$expected_keys_string = '\'' . implode('\', \'', $object_like_keys) .
|
||||||
'\' or \'' . $last_key . '\'';
|
'\' or \'' . $last_key . '\'';
|
||||||
|
}
|
||||||
|
|
||||||
|
$expected_offset_types[] = $expected_keys_string;
|
||||||
}
|
}
|
||||||
|
|
||||||
$expected_offset_types[] = $expected_keys_string;
|
|
||||||
|
|
||||||
$array_access_type = Type::getMixed();
|
$array_access_type = Type::getMixed();
|
||||||
}
|
}
|
||||||
} elseif (TypeChecker::isContainedBy(
|
} elseif (TypeChecker::isContainedBy(
|
||||||
@ -430,7 +457,7 @@ class ArrayFetchChecker
|
|||||||
|
|
||||||
if (!$stmt->dim && $property_count) {
|
if (!$stmt->dim && $property_count) {
|
||||||
++$property_count;
|
++$property_count;
|
||||||
$type->count = new Type\Atomic\TInt([$property_count => true]);
|
$type->count = new Type\Atomic\TLiteralInt([$property_count => true]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$array_access_type) {
|
if (!$array_access_type) {
|
||||||
@ -453,8 +480,8 @@ class ArrayFetchChecker
|
|||||||
}
|
}
|
||||||
|
|
||||||
$has_valid_offset = true;
|
$has_valid_offset = true;
|
||||||
} else {
|
} elseif (!$inside_isset || $type->sealed) {
|
||||||
$expected_offset_types[] = (string)$type->getGenericKeyType();
|
$expected_offset_types[] = (string)$type->getGenericKeyType()->getId();
|
||||||
|
|
||||||
$array_access_type = Type::getMixed();
|
$array_access_type = Type::getMixed();
|
||||||
}
|
}
|
||||||
@ -627,7 +654,7 @@ class ArrayFetchChecker
|
|||||||
if ($expected_offset_types) {
|
if ($expected_offset_types) {
|
||||||
$invalid_offset_type = $expected_offset_types[0];
|
$invalid_offset_type = $expected_offset_types[0];
|
||||||
|
|
||||||
$used_offset = 'using a ' . $offset_type . ' offset';
|
$used_offset = 'using a ' . $offset_type->getId() . ' offset';
|
||||||
|
|
||||||
if ($key_value !== null) {
|
if ($key_value !== null) {
|
||||||
$used_offset = 'using offset value of '
|
$used_offset = 'using offset value of '
|
||||||
@ -679,8 +706,7 @@ class ArrayFetchChecker
|
|||||||
$offset_atomic_types = $offset_type->getTypes();
|
$offset_atomic_types = $offset_type->getTypes();
|
||||||
|
|
||||||
if (isset($offset_atomic_types['string'])
|
if (isset($offset_atomic_types['string'])
|
||||||
&& $offset_atomic_types['string'] instanceof Type\Atomic\TString
|
&& $offset_atomic_types['string'] instanceof Type\Atomic\TLiteralString
|
||||||
&& $offset_atomic_types['string']->values
|
|
||||||
) {
|
) {
|
||||||
$strings = [];
|
$strings = [];
|
||||||
$ints = [];
|
$ints = [];
|
||||||
@ -697,7 +723,7 @@ class ArrayFetchChecker
|
|||||||
$offset_type = clone $offset_type;
|
$offset_type = clone $offset_type;
|
||||||
|
|
||||||
if ($strings) {
|
if ($strings) {
|
||||||
$offset_type->addType(new Type\Atomic\TString($strings));
|
$offset_type->addType(new Type\Atomic\TLiteralString($strings));
|
||||||
} else {
|
} else {
|
||||||
$offset_type->removeType('string');
|
$offset_type->removeType('string');
|
||||||
}
|
}
|
||||||
@ -705,13 +731,13 @@ class ArrayFetchChecker
|
|||||||
if (isset($offset_atomic_types['int'])
|
if (isset($offset_atomic_types['int'])
|
||||||
&& $offset_atomic_types['int'] instanceof Type\Atomic\TInt
|
&& $offset_atomic_types['int'] instanceof Type\Atomic\TInt
|
||||||
) {
|
) {
|
||||||
if ($offset_atomic_types['int']->values) {
|
if ($offset_atomic_types['int'] instanceof Type\Atomic\TLiteralInt) {
|
||||||
$offset_type->addType(new Type\Atomic\TInt(
|
$offset_type->addType(new Type\Atomic\TLiteralInt(
|
||||||
$offset_atomic_types['int']->values + $ints
|
$offset_atomic_types['int']->values + $ints
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$offset_type->addType(new Type\Atomic\TInt($ints));
|
$offset_type->addType(new Type\Atomic\TLiteralInt($ints));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -201,12 +201,12 @@ class ExpressionChecker
|
|||||||
$stmt->inferredType = clone $stmt->var->inferredType;
|
$stmt->inferredType = clone $stmt->var->inferredType;
|
||||||
$stmt->inferredType->from_calculation = true;
|
$stmt->inferredType->from_calculation = true;
|
||||||
|
|
||||||
foreach ($stmt->inferredType->getTypes() as $atomic_type) {
|
if ($context->inside_loop) {
|
||||||
if ($atomic_type instanceof Type\Atomic\TInt
|
foreach ($stmt->inferredType->getTypes() as $atomic_type) {
|
||||||
|| $atomic_type instanceof Type\Atomic\TFloat
|
if ($atomic_type instanceof Type\Atomic\TLiteralInt) {
|
||||||
) {
|
$stmt->inferredType->addType(new Type\Atomic\TInt);
|
||||||
if ($context->inside_loop) {
|
} elseif ($atomic_type instanceof Type\Atomic\TLiteralFloat) {
|
||||||
$atomic_type->values = null;
|
$stmt->inferredType->addType(new Type\Atomic\TFloat);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ namespace Psalm\Checker;
|
|||||||
use Psalm\Checker\Statements\ExpressionChecker;
|
use Psalm\Checker\Statements\ExpressionChecker;
|
||||||
use Psalm\Codebase;
|
use Psalm\Codebase;
|
||||||
use Psalm\Type;
|
use Psalm\Type;
|
||||||
|
use Psalm\Type\Atomic\LiteralType;
|
||||||
use Psalm\Type\Atomic\ObjectLike;
|
use Psalm\Type\Atomic\ObjectLike;
|
||||||
use Psalm\Type\Atomic\Scalar;
|
use Psalm\Type\Atomic\Scalar;
|
||||||
use Psalm\Type\Atomic\TArray;
|
use Psalm\Type\Atomic\TArray;
|
||||||
@ -823,12 +824,8 @@ class TypeChecker
|
|||||||
|| ($input_type_part instanceof TInt && $container_type_part instanceof TInt)
|
|| ($input_type_part instanceof TInt && $container_type_part instanceof TInt)
|
||||||
|| ($input_type_part instanceof TFloat && $container_type_part instanceof TFloat)
|
|| ($input_type_part instanceof TFloat && $container_type_part instanceof TFloat)
|
||||||
) {
|
) {
|
||||||
/**
|
if ($input_type_part instanceof LiteralType && $container_type_part instanceof LiteralType) {
|
||||||
* @psalm-suppress UndefinedPropertyFetch
|
$all_types_contain = !array_diff_key($input_type_part->getValues(), $container_type_part->getValues());
|
||||||
* @psalm-suppress MixedArgument
|
|
||||||
*/
|
|
||||||
if ($input_type_part->values !== null && $container_type_part->values !== null) {
|
|
||||||
$all_types_contain = !array_diff_key($input_type_part->values, $container_type_part->values);
|
|
||||||
$incompatible_values = !$all_types_contain;
|
$incompatible_values = !$all_types_contain;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,9 @@ use Psalm\Type\Atomic\TFalse;
|
|||||||
use Psalm\Type\Atomic\TFloat;
|
use Psalm\Type\Atomic\TFloat;
|
||||||
use Psalm\Type\Atomic\TGenericObject;
|
use Psalm\Type\Atomic\TGenericObject;
|
||||||
use Psalm\Type\Atomic\TInt;
|
use Psalm\Type\Atomic\TInt;
|
||||||
|
use Psalm\Type\Atomic\TLiteralFloat;
|
||||||
|
use Psalm\Type\Atomic\TLiteralInt;
|
||||||
|
use Psalm\Type\Atomic\TLiteralString;
|
||||||
use Psalm\Type\Atomic\TMixed;
|
use Psalm\Type\Atomic\TMixed;
|
||||||
use Psalm\Type\Atomic\TNamedObject;
|
use Psalm\Type\Atomic\TNamedObject;
|
||||||
use Psalm\Type\Atomic\TNull;
|
use Psalm\Type\Atomic\TNull;
|
||||||
@ -546,13 +549,18 @@ abstract class Type
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param bool $from_calculation
|
* @param bool $from_calculation
|
||||||
* @param array<string|int, bool>|null $values
|
* @param array<int, bool>|null $values
|
||||||
*
|
*
|
||||||
* @return Type\Union
|
* @return Type\Union
|
||||||
*/
|
*/
|
||||||
public static function getInt($from_calculation = false, array $values = null)
|
public static function getInt($from_calculation = false, array $values = null)
|
||||||
{
|
{
|
||||||
$union = new Union([new TInt($values)]);
|
if ($values) {
|
||||||
|
$union = new Union([new TLiteralInt($values)]);
|
||||||
|
} else {
|
||||||
|
$union = new Union([new TInt()]);
|
||||||
|
}
|
||||||
|
|
||||||
$union->from_calculation = $from_calculation;
|
$union->from_calculation = $from_calculation;
|
||||||
|
|
||||||
return $union;
|
return $union;
|
||||||
@ -575,7 +583,11 @@ abstract class Type
|
|||||||
*/
|
*/
|
||||||
public static function getString(array $values = null)
|
public static function getString(array $values = null)
|
||||||
{
|
{
|
||||||
$type = new TString($values);
|
if ($values) {
|
||||||
|
$type = new TLiteralString($values);
|
||||||
|
} else {
|
||||||
|
$type = new TString();
|
||||||
|
}
|
||||||
|
|
||||||
return new Union([$type]);
|
return new Union([$type]);
|
||||||
}
|
}
|
||||||
@ -631,7 +643,11 @@ abstract class Type
|
|||||||
*/
|
*/
|
||||||
public static function getFloat(array $values = null)
|
public static function getFloat(array $values = null)
|
||||||
{
|
{
|
||||||
$type = new TFloat($values);
|
if ($values) {
|
||||||
|
$type = new TLiteralFloat($values);
|
||||||
|
} else {
|
||||||
|
$type = new TFloat();
|
||||||
|
}
|
||||||
|
|
||||||
return new Union([$type]);
|
return new Union([$type]);
|
||||||
}
|
}
|
||||||
@ -686,7 +702,7 @@ abstract class Type
|
|||||||
/**
|
/**
|
||||||
* @psalm-suppress InvalidScalarArgument because of a bug
|
* @psalm-suppress InvalidScalarArgument because of a bug
|
||||||
*/
|
*/
|
||||||
$array_type->count = new TInt([0 => true]);
|
$array_type->count = new TLiteralInt([0 => true]);
|
||||||
|
|
||||||
return new Type\Union([
|
return new Type\Union([
|
||||||
$array_type,
|
$array_type,
|
||||||
@ -974,7 +990,8 @@ abstract class Type
|
|||||||
$array_type = new TArray($generic_type_params);
|
$array_type = new TArray($generic_type_params);
|
||||||
|
|
||||||
if ($combination->array_counts) {
|
if ($combination->array_counts) {
|
||||||
$array_type->count = new TInt($combination->array_counts);
|
/** @psalm-suppress InvalidScalarArgument */
|
||||||
|
$array_type->count = new TLiteralInt($combination->array_counts);
|
||||||
}
|
}
|
||||||
|
|
||||||
$new_types[] = $array_type;
|
$new_types[] = $array_type;
|
||||||
@ -989,11 +1006,24 @@ abstract class Type
|
|||||||
&& !count($new_types))
|
&& !count($new_types))
|
||||||
) {
|
) {
|
||||||
if ($type instanceof TString) {
|
if ($type instanceof TString) {
|
||||||
$type->values = $combination->strings ?: null;
|
if ($combination->strings) {
|
||||||
|
$type = new TLiteralString($combination->strings);
|
||||||
|
} elseif ($type instanceof TLiteralString) {
|
||||||
|
$type = new TString();
|
||||||
|
}
|
||||||
} elseif ($type instanceof TInt) {
|
} elseif ($type instanceof TInt) {
|
||||||
$type->values = $combination->ints ?: null;
|
if ($combination->ints) {
|
||||||
|
/** @psalm-suppress InvalidScalarArgument */
|
||||||
|
$type = new TLiteralInt($combination->ints);
|
||||||
|
} elseif ($type instanceof TLiteralInt) {
|
||||||
|
$type = new TInt();
|
||||||
|
}
|
||||||
} elseif ($type instanceof TFloat) {
|
} elseif ($type instanceof TFloat) {
|
||||||
$type->values = $combination->floats ?: null;
|
if ($combination->floats) {
|
||||||
|
$type = new TLiteralFloat($combination->floats);
|
||||||
|
} elseif ($type instanceof TLiteralFloat) {
|
||||||
|
$type = new TFloat();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$new_types[] = $type;
|
$new_types[] = $type;
|
||||||
@ -1032,11 +1062,11 @@ abstract class Type
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (get_class($type) === 'Psalm\\Type\\Atomic\\TBool' && isset($combination->value_types['false'])) {
|
if (get_class($type) === TBool::class && isset($combination->value_types['false'])) {
|
||||||
unset($combination->value_types['false']);
|
unset($combination->value_types['false']);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (get_class($type) === 'Psalm\\Type\\Atomic\\TBool' && isset($combination->value_types['true'])) {
|
if (get_class($type) === TBool::class && isset($combination->value_types['true'])) {
|
||||||
unset($combination->value_types['true']);
|
unset($combination->value_types['true']);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1102,22 +1132,22 @@ abstract class Type
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if ($type instanceof TString && $combination->strings !== null) {
|
if ($type instanceof TString && $combination->strings !== null) {
|
||||||
if ($type->values === null) {
|
if ($type instanceof TLiteralString) {
|
||||||
$combination->strings = null;
|
|
||||||
} else {
|
|
||||||
$combination->strings = $combination->strings + $type->values;
|
$combination->strings = $combination->strings + $type->values;
|
||||||
|
} else {
|
||||||
|
$combination->strings = null;
|
||||||
}
|
}
|
||||||
} elseif ($type instanceof TInt && $combination->ints !== null) {
|
} elseif ($type instanceof TInt && $combination->ints !== null) {
|
||||||
if ($type->values === null) {
|
if ($type instanceof TLiteralInt) {
|
||||||
$combination->ints = null;
|
|
||||||
} else {
|
|
||||||
$combination->ints = $combination->ints + $type->values;
|
$combination->ints = $combination->ints + $type->values;
|
||||||
|
} else {
|
||||||
|
$combination->ints = null;
|
||||||
}
|
}
|
||||||
} elseif ($type instanceof TFloat && $combination->floats !== null) {
|
} elseif ($type instanceof TFloat && $combination->floats !== null) {
|
||||||
if ($type->values === null) {
|
if ($type instanceof TLiteralFloat) {
|
||||||
$combination->ints = null;
|
$combination->floats = $combination->floats + $type->values;
|
||||||
} else {
|
} else {
|
||||||
$combination->ints = $combination->floats + $type->values;
|
$combination->floats = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
10
src/Psalm/Type/Atomic/LiteralType.php
Normal file
10
src/Psalm/Type/Atomic/LiteralType.php
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
namespace Psalm\Type\Atomic;
|
||||||
|
|
||||||
|
interface LiteralType
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return array<string|int, bool>
|
||||||
|
*/
|
||||||
|
public function getValues();
|
||||||
|
}
|
@ -153,9 +153,9 @@ class ObjectLike extends \Psalm\Type\Atomic
|
|||||||
|
|
||||||
foreach ($this->properties as $key => $_) {
|
foreach ($this->properties as $key => $_) {
|
||||||
if (is_int($key)) {
|
if (is_int($key)) {
|
||||||
$key_types[] = new Type\Atomic\TInt([$key => true]);
|
$key_types[] = new Type\Atomic\TLiteralInt([$key => true]);
|
||||||
} else {
|
} else {
|
||||||
$key_types[] = new Type\Atomic\TString([$key => true]);
|
$key_types[] = new Type\Atomic\TLiteralString([$key => true]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -196,9 +196,9 @@ class ObjectLike extends \Psalm\Type\Atomic
|
|||||||
|
|
||||||
foreach ($this->properties as $key => $property) {
|
foreach ($this->properties as $key => $property) {
|
||||||
if (is_int($key)) {
|
if (is_int($key)) {
|
||||||
$key_types[] = new Type\Atomic\TInt([$key => true]);
|
$key_types[] = new Type\Atomic\TLiteralInt([$key => true]);
|
||||||
} else {
|
} else {
|
||||||
$key_types[] = new Type\Atomic\TString([$key => true]);
|
$key_types[] = new Type\Atomic\TLiteralString([$key => true]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($value_type === null) {
|
if ($value_type === null) {
|
||||||
@ -215,7 +215,7 @@ class ObjectLike extends \Psalm\Type\Atomic
|
|||||||
$value_type->possibly_undefined = false;
|
$value_type->possibly_undefined = false;
|
||||||
|
|
||||||
$array_type = new TArray([Type::combineTypes($key_types), $value_type]);
|
$array_type = new TArray([Type::combineTypes($key_types), $value_type]);
|
||||||
$array_type->count = new TInt([count($this->properties) => true]);
|
$array_type->count = new TLiteralInt([count($this->properties) => true]);
|
||||||
|
|
||||||
return $array_type;
|
return $array_type;
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@ class TArray extends \Psalm\Type\Atomic
|
|||||||
public $value = 'array';
|
public $value = 'array';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var TInt|null
|
* @var TLiteralInt|null
|
||||||
*/
|
*/
|
||||||
public $count;
|
public $count;
|
||||||
|
|
||||||
|
@ -3,17 +3,6 @@ namespace Psalm\Type\Atomic;
|
|||||||
|
|
||||||
class TFloat extends Scalar
|
class TFloat extends Scalar
|
||||||
{
|
{
|
||||||
/** @var array<string, bool>|null */
|
|
||||||
public $values;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param array<string, bool>|null $values
|
|
||||||
*/
|
|
||||||
public function __construct(array $values = null)
|
|
||||||
{
|
|
||||||
$this->values = $values;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function __toString()
|
public function __toString()
|
||||||
{
|
{
|
||||||
return 'float';
|
return 'float';
|
||||||
@ -26,12 +15,4 @@ class TFloat extends Scalar
|
|||||||
{
|
{
|
||||||
return 'float';
|
return 'float';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function getId()
|
|
||||||
{
|
|
||||||
return $this->values ? 'float(' . implode(',', array_keys($this->values)) . ')' : 'float';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -3,17 +3,6 @@ namespace Psalm\Type\Atomic;
|
|||||||
|
|
||||||
class TInt extends Scalar
|
class TInt extends Scalar
|
||||||
{
|
{
|
||||||
/** @var array<string|int, bool>|null */
|
|
||||||
public $values;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param array<string|int, bool>|null $values
|
|
||||||
*/
|
|
||||||
public function __construct(array $values = null)
|
|
||||||
{
|
|
||||||
$this->values = $values;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function __toString()
|
public function __toString()
|
||||||
{
|
{
|
||||||
return 'int';
|
return 'int';
|
||||||
@ -26,12 +15,4 @@ class TInt extends Scalar
|
|||||||
{
|
{
|
||||||
return 'int';
|
return 'int';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function getId()
|
|
||||||
{
|
|
||||||
return $this->values ? 'int(' . implode(',', array_keys($this->values)) . ')' : 'int';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
32
src/Psalm/Type/Atomic/TLiteralFloat.php
Normal file
32
src/Psalm/Type/Atomic/TLiteralFloat.php
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
namespace Psalm\Type\Atomic;
|
||||||
|
|
||||||
|
class TLiteralFloat extends TFloat implements LiteralType
|
||||||
|
{
|
||||||
|
/** @var array<string, bool> */
|
||||||
|
public $values;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, bool> $values
|
||||||
|
*/
|
||||||
|
public function __construct(array $values)
|
||||||
|
{
|
||||||
|
$this->values = $values;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getId()
|
||||||
|
{
|
||||||
|
return $this->values ? 'float(' . implode(',', array_keys($this->values)) . ')' : 'float';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, bool>
|
||||||
|
*/
|
||||||
|
public function getValues()
|
||||||
|
{
|
||||||
|
return $this->values;
|
||||||
|
}
|
||||||
|
}
|
32
src/Psalm/Type/Atomic/TLiteralInt.php
Normal file
32
src/Psalm/Type/Atomic/TLiteralInt.php
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
namespace Psalm\Type\Atomic;
|
||||||
|
|
||||||
|
class TLiteralInt extends TInt implements LiteralType
|
||||||
|
{
|
||||||
|
/** @var array<int, bool> */
|
||||||
|
public $values;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<int, bool> $values
|
||||||
|
*/
|
||||||
|
public function __construct(array $values)
|
||||||
|
{
|
||||||
|
$this->values = $values;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getId()
|
||||||
|
{
|
||||||
|
return $this->values ? 'int(' . implode(',', array_keys($this->values)) . ')' : 'int';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<int, bool>
|
||||||
|
*/
|
||||||
|
public function getValues()
|
||||||
|
{
|
||||||
|
return $this->values;
|
||||||
|
}
|
||||||
|
}
|
32
src/Psalm/Type/Atomic/TLiteralString.php
Normal file
32
src/Psalm/Type/Atomic/TLiteralString.php
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
namespace Psalm\Type\Atomic;
|
||||||
|
|
||||||
|
class TLiteralString extends TString implements LiteralType
|
||||||
|
{
|
||||||
|
/** @var array<string|int, bool> */
|
||||||
|
public $values;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string|int, bool> $values
|
||||||
|
*/
|
||||||
|
public function __construct(array $values)
|
||||||
|
{
|
||||||
|
$this->values = $values;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getId()
|
||||||
|
{
|
||||||
|
return $this->values ? 'string(\'' . implode('\',\'', array_keys($this->values)) . '\')' : 'string';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string|int, bool>
|
||||||
|
*/
|
||||||
|
public function getValues()
|
||||||
|
{
|
||||||
|
return $this->values;
|
||||||
|
}
|
||||||
|
}
|
@ -3,17 +3,6 @@ namespace Psalm\Type\Atomic;
|
|||||||
|
|
||||||
class TString extends Scalar
|
class TString extends Scalar
|
||||||
{
|
{
|
||||||
/** @var array<string|int, bool>|null */
|
|
||||||
public $values;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param array<string|int, bool>|null $values
|
|
||||||
*/
|
|
||||||
public function __construct(array $values = null)
|
|
||||||
{
|
|
||||||
$this->values = $values;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function __toString()
|
public function __toString()
|
||||||
{
|
{
|
||||||
return 'string';
|
return 'string';
|
||||||
@ -26,12 +15,4 @@ class TString extends Scalar
|
|||||||
{
|
{
|
||||||
return 'string';
|
return 'string';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function getId()
|
|
||||||
{
|
|
||||||
return $this->values ? 'string(\'' . implode('\',\'', array_keys($this->values)) . '\')' : 'string';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -241,8 +241,7 @@ class Reconciler
|
|||||||
$ints = array_flip(explode(',', $bracketed));
|
$ints = array_flip(explode(',', $bracketed));
|
||||||
|
|
||||||
if (isset($existing_var_atomic_types['int'])
|
if (isset($existing_var_atomic_types['int'])
|
||||||
&& $existing_var_atomic_types['int'] instanceof Type\Atomic\TInt
|
&& $existing_var_atomic_types['int'] instanceof Type\Atomic\TLiteralInt
|
||||||
&& $existing_var_atomic_types['int']->values
|
|
||||||
) {
|
) {
|
||||||
$current_count = count($existing_var_atomic_types['int']->values);
|
$current_count = count($existing_var_atomic_types['int']->values);
|
||||||
|
|
||||||
@ -275,8 +274,7 @@ class Reconciler
|
|||||||
$strings = array_flip(explode('\',\'', substr($bracketed, 1, -1)));
|
$strings = array_flip(explode('\',\'', substr($bracketed, 1, -1)));
|
||||||
|
|
||||||
if (isset($existing_var_atomic_types['string'])
|
if (isset($existing_var_atomic_types['string'])
|
||||||
&& $existing_var_atomic_types['string'] instanceof Type\Atomic\TString
|
&& $existing_var_atomic_types['string'] instanceof Type\Atomic\TLiteralString
|
||||||
&& $existing_var_atomic_types['string']->values
|
|
||||||
) {
|
) {
|
||||||
$current_count = count($existing_var_atomic_types['string']->values);
|
$current_count = count($existing_var_atomic_types['string']->values);
|
||||||
|
|
||||||
@ -309,8 +307,7 @@ class Reconciler
|
|||||||
$floats = array_flip(explode(',', $bracketed));
|
$floats = array_flip(explode(',', $bracketed));
|
||||||
|
|
||||||
if (isset($existing_var_atomic_types['float'])
|
if (isset($existing_var_atomic_types['float'])
|
||||||
&& $existing_var_atomic_types['float'] instanceof Type\Atomic\TFloat
|
&& $existing_var_atomic_types['float'] instanceof Type\Atomic\TLiteralFloat
|
||||||
&& $existing_var_atomic_types['float']->values
|
|
||||||
) {
|
) {
|
||||||
$current_count = count($existing_var_atomic_types['float']->values);
|
$current_count = count($existing_var_atomic_types['float']->values);
|
||||||
|
|
||||||
@ -965,8 +962,7 @@ class Reconciler
|
|||||||
$ints = array_flip(explode(',', $bracketed));
|
$ints = array_flip(explode(',', $bracketed));
|
||||||
|
|
||||||
if (isset($existing_var_atomic_types['int'])
|
if (isset($existing_var_atomic_types['int'])
|
||||||
&& $existing_var_atomic_types['int'] instanceof Type\Atomic\TInt
|
&& $existing_var_atomic_types['int'] instanceof Type\Atomic\TLiteralInt
|
||||||
&& $existing_var_atomic_types['int']->values
|
|
||||||
) {
|
) {
|
||||||
$current_count = count($existing_var_atomic_types['int']->values);
|
$current_count = count($existing_var_atomic_types['int']->values);
|
||||||
|
|
||||||
@ -999,8 +995,7 @@ class Reconciler
|
|||||||
$strings = array_flip(explode('\',\'', substr($bracketed, 1, -1)));
|
$strings = array_flip(explode('\',\'', substr($bracketed, 1, -1)));
|
||||||
|
|
||||||
if (isset($existing_var_atomic_types['string'])
|
if (isset($existing_var_atomic_types['string'])
|
||||||
&& $existing_var_atomic_types['string'] instanceof Type\Atomic\TString
|
&& $existing_var_atomic_types['string'] instanceof Type\Atomic\TLiteralString
|
||||||
&& $existing_var_atomic_types['string']->values
|
|
||||||
) {
|
) {
|
||||||
$current_count = count($existing_var_atomic_types['string']->values);
|
$current_count = count($existing_var_atomic_types['string']->values);
|
||||||
|
|
||||||
@ -1033,8 +1028,7 @@ class Reconciler
|
|||||||
$floats = array_flip(explode(',', $bracketed));
|
$floats = array_flip(explode(',', $bracketed));
|
||||||
|
|
||||||
if (isset($existing_var_atomic_types['float'])
|
if (isset($existing_var_atomic_types['float'])
|
||||||
&& $existing_var_atomic_types['float'] instanceof Type\Atomic\TFloat
|
&& $existing_var_atomic_types['float'] instanceof Type\Atomic\TLiteralFloat
|
||||||
&& $existing_var_atomic_types['float']->values
|
|
||||||
) {
|
) {
|
||||||
$current_count = count($existing_var_atomic_types['float']->values);
|
$current_count = count($existing_var_atomic_types['float']->values);
|
||||||
|
|
||||||
|
@ -267,6 +267,26 @@ class IssetTest extends TestCase
|
|||||||
echo $arr["bar"];
|
echo $arr["bar"];
|
||||||
}',
|
}',
|
||||||
],
|
],
|
||||||
|
'issetAdditionalVar' => [
|
||||||
|
'<?php
|
||||||
|
class Example {
|
||||||
|
const FOO = "foo";
|
||||||
|
/**
|
||||||
|
* @param array{bar:string} $params
|
||||||
|
*/
|
||||||
|
public function test(array $params) : bool {
|
||||||
|
if (isset($params[self::FOO])) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($params["bat"])) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}'
|
||||||
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -291,7 +311,23 @@ class IssetTest extends TestCase
|
|||||||
$b = 1;
|
$b = 1;
|
||||||
echo $arr[$b][$c];
|
echo $arr[$b][$c];
|
||||||
}',
|
}',
|
||||||
'error_message' => 'PossiblyNullArrayAccess',
|
'error_message' => 'NullArrayAccess',
|
||||||
|
],
|
||||||
|
'issetAdditionalVarWithSealedObjectLike' => [
|
||||||
|
'<?php
|
||||||
|
class Example {
|
||||||
|
const FOO = "foo";
|
||||||
|
public function test() : bool {
|
||||||
|
$params = ["bar" => "bat"];
|
||||||
|
|
||||||
|
if (isset($params[self::FOO])) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}',
|
||||||
|
'error_message' => 'InvalidArrayOffset',
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -176,7 +176,7 @@ class MethodCallTest extends TestCase
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** @param A1|string $x */
|
/** @param A1|string $x */
|
||||||
function example($x, bool $isObject) {
|
function example($x, bool $isObject) : void {
|
||||||
if ($isObject) {
|
if ($isObject) {
|
||||||
$x->methodOfA();
|
$x->methodOfA();
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user