mirror of
https://github.com/danog/psalm.git
synced 2025-01-21 21:31:13 +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
|
||||
) {
|
||||
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_
|
||||
&& 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) {
|
||||
@ -154,7 +154,7 @@ class ArrayChecker
|
||||
$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([
|
||||
$array_type,
|
||||
|
@ -323,7 +323,7 @@ class ArrayAssignmentChecker
|
||||
} elseif ($atomic_root_types['array'] instanceof ObjectLike
|
||||
&& $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
|
||||
]);
|
||||
$from_countable_object_like = true;
|
||||
@ -350,7 +350,7 @@ class ArrayAssignmentChecker
|
||||
$new_counts = [];
|
||||
|
||||
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;
|
||||
|
@ -169,10 +169,16 @@ class MethodCallChecker extends \Psalm\Checker\Statements\Expression\CallChecker
|
||||
break;
|
||||
|
||||
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\TTrue::class:
|
||||
case Type\Atomic\TArray::class:
|
||||
case Type\Atomic\TArray::class:
|
||||
case Type\Atomic\ObjectLike::class:
|
||||
case Type\Atomic\TString::class:
|
||||
case Type\Atomic\TLiteralString::class:
|
||||
case Type\Atomic\TNumericString::class:
|
||||
case Type\Atomic\TClassString::class:
|
||||
$invalid_method_call_types[] = (string)$class_type_part;
|
||||
|
@ -29,7 +29,12 @@ use Psalm\Type;
|
||||
use Psalm\Type\Atomic\ObjectLike;
|
||||
use Psalm\Type\Atomic\TArray;
|
||||
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\TInt;
|
||||
use Psalm\Type\Atomic\TMixed;
|
||||
use Psalm\Type\Atomic\TNamedObject;
|
||||
use Psalm\Type\Atomic\TNull;
|
||||
@ -194,6 +199,26 @@ class ArrayFetchChecker
|
||||
|| $stmt->dim instanceof PhpParser\Node\Scalar\LNumber
|
||||
) {
|
||||
$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;
|
||||
@ -305,7 +330,7 @@ class ArrayFetchChecker
|
||||
true,
|
||||
$offset_type->ignore_falsable_issues
|
||||
)) {
|
||||
$expected_offset_types[] = (string)$type->type_params[0];
|
||||
$expected_offset_types[] = $type->type_params[0]->getId();
|
||||
} else {
|
||||
$has_valid_offset = true;
|
||||
}
|
||||
@ -315,7 +340,7 @@ class ArrayFetchChecker
|
||||
$new_counts = [];
|
||||
|
||||
foreach ($type->count->values as $count => $_) {
|
||||
$new_counts[(string)((int)$count + 1)] = true;
|
||||
$new_counts[(int)$count + 1] = true;
|
||||
}
|
||||
|
||||
$type->count->values = $new_counts;
|
||||
@ -388,18 +413,20 @@ class ArrayFetchChecker
|
||||
);
|
||||
}
|
||||
} 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) {
|
||||
$expected_keys_string = '\'' . $object_like_keys[0] . '\'';
|
||||
} else {
|
||||
$last_key = array_pop($object_like_keys);
|
||||
$expected_keys_string = '\'' . implode('\', \'', $object_like_keys) .
|
||||
'\' or \'' . $last_key . '\'';
|
||||
if (count($object_like_keys) === 1) {
|
||||
$expected_keys_string = '\'' . $object_like_keys[0] . '\'';
|
||||
} else {
|
||||
$last_key = array_pop($object_like_keys);
|
||||
$expected_keys_string = '\'' . implode('\', \'', $object_like_keys) .
|
||||
'\' or \'' . $last_key . '\'';
|
||||
}
|
||||
|
||||
$expected_offset_types[] = $expected_keys_string;
|
||||
}
|
||||
|
||||
$expected_offset_types[] = $expected_keys_string;
|
||||
|
||||
$array_access_type = Type::getMixed();
|
||||
}
|
||||
} elseif (TypeChecker::isContainedBy(
|
||||
@ -430,7 +457,7 @@ class ArrayFetchChecker
|
||||
|
||||
if (!$stmt->dim && $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) {
|
||||
@ -453,8 +480,8 @@ class ArrayFetchChecker
|
||||
}
|
||||
|
||||
$has_valid_offset = true;
|
||||
} else {
|
||||
$expected_offset_types[] = (string)$type->getGenericKeyType();
|
||||
} elseif (!$inside_isset || $type->sealed) {
|
||||
$expected_offset_types[] = (string)$type->getGenericKeyType()->getId();
|
||||
|
||||
$array_access_type = Type::getMixed();
|
||||
}
|
||||
@ -627,7 +654,7 @@ class ArrayFetchChecker
|
||||
if ($expected_offset_types) {
|
||||
$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) {
|
||||
$used_offset = 'using offset value of '
|
||||
@ -679,8 +706,7 @@ class ArrayFetchChecker
|
||||
$offset_atomic_types = $offset_type->getTypes();
|
||||
|
||||
if (isset($offset_atomic_types['string'])
|
||||
&& $offset_atomic_types['string'] instanceof Type\Atomic\TString
|
||||
&& $offset_atomic_types['string']->values
|
||||
&& $offset_atomic_types['string'] instanceof Type\Atomic\TLiteralString
|
||||
) {
|
||||
$strings = [];
|
||||
$ints = [];
|
||||
@ -697,7 +723,7 @@ class ArrayFetchChecker
|
||||
$offset_type = clone $offset_type;
|
||||
|
||||
if ($strings) {
|
||||
$offset_type->addType(new Type\Atomic\TString($strings));
|
||||
$offset_type->addType(new Type\Atomic\TLiteralString($strings));
|
||||
} else {
|
||||
$offset_type->removeType('string');
|
||||
}
|
||||
@ -705,13 +731,13 @@ class ArrayFetchChecker
|
||||
if (isset($offset_atomic_types['int'])
|
||||
&& $offset_atomic_types['int'] instanceof Type\Atomic\TInt
|
||||
) {
|
||||
if ($offset_atomic_types['int']->values) {
|
||||
$offset_type->addType(new Type\Atomic\TInt(
|
||||
if ($offset_atomic_types['int'] instanceof Type\Atomic\TLiteralInt) {
|
||||
$offset_type->addType(new Type\Atomic\TLiteralInt(
|
||||
$offset_atomic_types['int']->values + $ints
|
||||
));
|
||||
}
|
||||
} 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->from_calculation = true;
|
||||
|
||||
foreach ($stmt->inferredType->getTypes() as $atomic_type) {
|
||||
if ($atomic_type instanceof Type\Atomic\TInt
|
||||
|| $atomic_type instanceof Type\Atomic\TFloat
|
||||
) {
|
||||
if ($context->inside_loop) {
|
||||
$atomic_type->values = null;
|
||||
if ($context->inside_loop) {
|
||||
foreach ($stmt->inferredType->getTypes() as $atomic_type) {
|
||||
if ($atomic_type instanceof Type\Atomic\TLiteralInt) {
|
||||
$stmt->inferredType->addType(new Type\Atomic\TInt);
|
||||
} elseif ($atomic_type instanceof Type\Atomic\TLiteralFloat) {
|
||||
$stmt->inferredType->addType(new Type\Atomic\TFloat);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ namespace Psalm\Checker;
|
||||
use Psalm\Checker\Statements\ExpressionChecker;
|
||||
use Psalm\Codebase;
|
||||
use Psalm\Type;
|
||||
use Psalm\Type\Atomic\LiteralType;
|
||||
use Psalm\Type\Atomic\ObjectLike;
|
||||
use Psalm\Type\Atomic\Scalar;
|
||||
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 TFloat && $container_type_part instanceof TFloat)
|
||||
) {
|
||||
/**
|
||||
* @psalm-suppress UndefinedPropertyFetch
|
||||
* @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);
|
||||
if ($input_type_part instanceof LiteralType && $container_type_part instanceof LiteralType) {
|
||||
$all_types_contain = !array_diff_key($input_type_part->getValues(), $container_type_part->getValues());
|
||||
$incompatible_values = !$all_types_contain;
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,9 @@ use Psalm\Type\Atomic\TFalse;
|
||||
use Psalm\Type\Atomic\TFloat;
|
||||
use Psalm\Type\Atomic\TGenericObject;
|
||||
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\TNamedObject;
|
||||
use Psalm\Type\Atomic\TNull;
|
||||
@ -546,13 +549,18 @@ abstract class Type
|
||||
|
||||
/**
|
||||
* @param bool $from_calculation
|
||||
* @param array<string|int, bool>|null $values
|
||||
* @param array<int, bool>|null $values
|
||||
*
|
||||
* @return Type\Union
|
||||
*/
|
||||
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;
|
||||
|
||||
return $union;
|
||||
@ -575,7 +583,11 @@ abstract class Type
|
||||
*/
|
||||
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]);
|
||||
}
|
||||
@ -631,7 +643,11 @@ abstract class Type
|
||||
*/
|
||||
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]);
|
||||
}
|
||||
@ -686,7 +702,7 @@ abstract class Type
|
||||
/**
|
||||
* @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([
|
||||
$array_type,
|
||||
@ -974,7 +990,8 @@ abstract class Type
|
||||
$array_type = new TArray($generic_type_params);
|
||||
|
||||
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;
|
||||
@ -989,11 +1006,24 @@ abstract class Type
|
||||
&& !count($new_types))
|
||||
) {
|
||||
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) {
|
||||
$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) {
|
||||
$type->values = $combination->floats ?: null;
|
||||
if ($combination->floats) {
|
||||
$type = new TLiteralFloat($combination->floats);
|
||||
} elseif ($type instanceof TLiteralFloat) {
|
||||
$type = new TFloat();
|
||||
}
|
||||
}
|
||||
|
||||
$new_types[] = $type;
|
||||
@ -1032,11 +1062,11 @@ abstract class Type
|
||||
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']);
|
||||
}
|
||||
|
||||
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']);
|
||||
}
|
||||
|
||||
@ -1102,22 +1132,22 @@ abstract class Type
|
||||
}
|
||||
} else {
|
||||
if ($type instanceof TString && $combination->strings !== null) {
|
||||
if ($type->values === null) {
|
||||
$combination->strings = null;
|
||||
} else {
|
||||
if ($type instanceof TLiteralString) {
|
||||
$combination->strings = $combination->strings + $type->values;
|
||||
} else {
|
||||
$combination->strings = null;
|
||||
}
|
||||
} elseif ($type instanceof TInt && $combination->ints !== null) {
|
||||
if ($type->values === null) {
|
||||
$combination->ints = null;
|
||||
} else {
|
||||
if ($type instanceof TLiteralInt) {
|
||||
$combination->ints = $combination->ints + $type->values;
|
||||
} else {
|
||||
$combination->ints = null;
|
||||
}
|
||||
} elseif ($type instanceof TFloat && $combination->floats !== null) {
|
||||
if ($type->values === null) {
|
||||
$combination->ints = null;
|
||||
if ($type instanceof TLiteralFloat) {
|
||||
$combination->floats = $combination->floats + $type->values;
|
||||
} 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 => $_) {
|
||||
if (is_int($key)) {
|
||||
$key_types[] = new Type\Atomic\TInt([$key => true]);
|
||||
$key_types[] = new Type\Atomic\TLiteralInt([$key => true]);
|
||||
} 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) {
|
||||
if (is_int($key)) {
|
||||
$key_types[] = new Type\Atomic\TInt([$key => true]);
|
||||
$key_types[] = new Type\Atomic\TLiteralInt([$key => true]);
|
||||
} else {
|
||||
$key_types[] = new Type\Atomic\TString([$key => true]);
|
||||
$key_types[] = new Type\Atomic\TLiteralString([$key => true]);
|
||||
}
|
||||
|
||||
if ($value_type === null) {
|
||||
@ -215,7 +215,7 @@ class ObjectLike extends \Psalm\Type\Atomic
|
||||
$value_type->possibly_undefined = false;
|
||||
|
||||
$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;
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ class TArray extends \Psalm\Type\Atomic
|
||||
public $value = 'array';
|
||||
|
||||
/**
|
||||
* @var TInt|null
|
||||
* @var TLiteralInt|null
|
||||
*/
|
||||
public $count;
|
||||
|
||||
|
@ -3,17 +3,6 @@ namespace Psalm\Type\Atomic;
|
||||
|
||||
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()
|
||||
{
|
||||
return 'float';
|
||||
@ -26,12 +15,4 @@ class TFloat extends Scalar
|
||||
{
|
||||
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
|
||||
{
|
||||
/** @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()
|
||||
{
|
||||
return 'int';
|
||||
@ -26,12 +15,4 @@ class TInt extends Scalar
|
||||
{
|
||||
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
|
||||
{
|
||||
/** @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()
|
||||
{
|
||||
return 'string';
|
||||
@ -26,12 +15,4 @@ class TString extends Scalar
|
||||
{
|
||||
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));
|
||||
|
||||
if (isset($existing_var_atomic_types['int'])
|
||||
&& $existing_var_atomic_types['int'] instanceof Type\Atomic\TInt
|
||||
&& $existing_var_atomic_types['int']->values
|
||||
&& $existing_var_atomic_types['int'] instanceof Type\Atomic\TLiteralInt
|
||||
) {
|
||||
$current_count = count($existing_var_atomic_types['int']->values);
|
||||
|
||||
@ -275,8 +274,7 @@ class Reconciler
|
||||
$strings = array_flip(explode('\',\'', substr($bracketed, 1, -1)));
|
||||
|
||||
if (isset($existing_var_atomic_types['string'])
|
||||
&& $existing_var_atomic_types['string'] instanceof Type\Atomic\TString
|
||||
&& $existing_var_atomic_types['string']->values
|
||||
&& $existing_var_atomic_types['string'] instanceof Type\Atomic\TLiteralString
|
||||
) {
|
||||
$current_count = count($existing_var_atomic_types['string']->values);
|
||||
|
||||
@ -309,8 +307,7 @@ class Reconciler
|
||||
$floats = array_flip(explode(',', $bracketed));
|
||||
|
||||
if (isset($existing_var_atomic_types['float'])
|
||||
&& $existing_var_atomic_types['float'] instanceof Type\Atomic\TFloat
|
||||
&& $existing_var_atomic_types['float']->values
|
||||
&& $existing_var_atomic_types['float'] instanceof Type\Atomic\TLiteralFloat
|
||||
) {
|
||||
$current_count = count($existing_var_atomic_types['float']->values);
|
||||
|
||||
@ -965,8 +962,7 @@ class Reconciler
|
||||
$ints = array_flip(explode(',', $bracketed));
|
||||
|
||||
if (isset($existing_var_atomic_types['int'])
|
||||
&& $existing_var_atomic_types['int'] instanceof Type\Atomic\TInt
|
||||
&& $existing_var_atomic_types['int']->values
|
||||
&& $existing_var_atomic_types['int'] instanceof Type\Atomic\TLiteralInt
|
||||
) {
|
||||
$current_count = count($existing_var_atomic_types['int']->values);
|
||||
|
||||
@ -999,8 +995,7 @@ class Reconciler
|
||||
$strings = array_flip(explode('\',\'', substr($bracketed, 1, -1)));
|
||||
|
||||
if (isset($existing_var_atomic_types['string'])
|
||||
&& $existing_var_atomic_types['string'] instanceof Type\Atomic\TString
|
||||
&& $existing_var_atomic_types['string']->values
|
||||
&& $existing_var_atomic_types['string'] instanceof Type\Atomic\TLiteralString
|
||||
) {
|
||||
$current_count = count($existing_var_atomic_types['string']->values);
|
||||
|
||||
@ -1033,8 +1028,7 @@ class Reconciler
|
||||
$floats = array_flip(explode(',', $bracketed));
|
||||
|
||||
if (isset($existing_var_atomic_types['float'])
|
||||
&& $existing_var_atomic_types['float'] instanceof Type\Atomic\TFloat
|
||||
&& $existing_var_atomic_types['float']->values
|
||||
&& $existing_var_atomic_types['float'] instanceof Type\Atomic\TLiteralFloat
|
||||
) {
|
||||
$current_count = count($existing_var_atomic_types['float']->values);
|
||||
|
||||
|
@ -267,6 +267,26 @@ class IssetTest extends TestCase
|
||||
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;
|
||||
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 */
|
||||
function example($x, bool $isObject) {
|
||||
function example($x, bool $isObject) : void {
|
||||
if ($isObject) {
|
||||
$x->methodOfA();
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user