1
0
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:
Matthew Brown 2018-05-05 17:30:18 -04:00
parent 0181fce46f
commit 21261172a8
20 changed files with 273 additions and 135 deletions

View File

@ -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])
]); ]);
} }
} }

View File

@ -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,

View File

@ -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;

View File

@ -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;

View File

@ -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));
} }
} }
} }

View File

@ -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);
} }
} }
} }

View File

@ -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;
} }
} }

View File

@ -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;
} }
} }

View File

@ -0,0 +1,10 @@
<?php
namespace Psalm\Type\Atomic;
interface LiteralType
{
/**
* @return array<string|int, bool>
*/
public function getValues();
}

View File

@ -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;
} }

View File

@ -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;

View File

@ -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';
}
} }

View File

@ -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';
}
} }

View 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;
}
}

View 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;
}
}

View 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;
}
}

View File

@ -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';
}
} }

View File

@ -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);

View File

@ -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',
], ],
]; ];
} }

View File

@ -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();
} }