mirror of
https://github.com/danog/psalm.git
synced 2024-12-03 10:07:52 +01:00
Remove descendent types when assigning
This commit is contained in:
parent
dff23e9d74
commit
e9a30ca556
@ -212,7 +212,7 @@ class FunctionChecker extends FunctionLikeChecker
|
||||
|
||||
$subject_type = $call_args[2]->value->inferredType;
|
||||
|
||||
if (!$subject_type->isString() && $subject_type->isArray()) {
|
||||
if (!$subject_type->hasString() && $subject_type->hasArray()) {
|
||||
return Type::getArray();
|
||||
}
|
||||
|
||||
@ -263,7 +263,7 @@ class FunctionChecker extends FunctionLikeChecker
|
||||
}
|
||||
|
||||
if (in_array($call_map_key, ['array_filter', 'array_values'])) {
|
||||
if (isset($call_args[0]->value->inferredType) && $call_args[0]->value->inferredType->isArray()) {
|
||||
if (isset($call_args[0]->value->inferredType) && $call_args[0]->value->inferredType->hasArray()) {
|
||||
return clone $call_args[0]->value->inferredType;
|
||||
}
|
||||
}
|
||||
|
@ -167,7 +167,13 @@ class StatementsChecker
|
||||
}
|
||||
|
||||
} elseif ($stmt instanceof PhpParser\Node\Stmt\Unset_) {
|
||||
// do nothing
|
||||
foreach ($stmt->vars as $var) {
|
||||
$var_id = self::getArrayVarId($var);
|
||||
|
||||
if ($var_id) {
|
||||
$context->remove($var_id);
|
||||
}
|
||||
}
|
||||
|
||||
} elseif ($stmt instanceof PhpParser\Node\Stmt\Return_) {
|
||||
$has_returned = true;
|
||||
@ -1268,12 +1274,17 @@ class StatementsChecker
|
||||
$stmt->inferredType = Type::getNull();
|
||||
}
|
||||
|
||||
if ($stmt_var_type->isObjectType() && is_string($stmt->name)) {
|
||||
if (is_string($stmt->name)) {
|
||||
foreach ($stmt_var_type->types as $lhs_type_part) {
|
||||
if ($lhs_type_part->isNull()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$lhs_type_part->isObjectType()) {
|
||||
// @todo InvalidPropertyFetch
|
||||
continue;
|
||||
}
|
||||
|
||||
// stdClass and SimpleXMLElement are special cases where we cannot infer the return types
|
||||
// but we don't want to throw an error
|
||||
// Hack has a similar issue: https://github.com/facebook/hhvm/issues/5164
|
||||
@ -1282,10 +1293,7 @@ class StatementsChecker
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$lhs_type_part->isObjectType()) {
|
||||
// @todo InvalidPropertyFetch
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
if (method_exists((string) $lhs_type_part, '__get')) {
|
||||
continue;
|
||||
@ -1381,9 +1389,6 @@ class StatementsChecker
|
||||
|
||||
return;
|
||||
}
|
||||
else {
|
||||
// @todo ScalarPropertyFetch issue
|
||||
}
|
||||
}
|
||||
else {
|
||||
// @todo MixedPropertyFetch issue
|
||||
@ -2127,6 +2132,13 @@ class StatementsChecker
|
||||
{
|
||||
$var_id = self::getVarId($stmt->var);
|
||||
|
||||
$array_var_id = self::getArrayVarId($stmt->var);
|
||||
|
||||
if ($array_var_id) {
|
||||
// removes dependennt vars from $context
|
||||
$context->removeDescendents($array_var_id);
|
||||
}
|
||||
|
||||
if ($this->checkExpression($stmt->expr, $context) === false) {
|
||||
// if we're not exiting immediately, make everything mixed
|
||||
$context->vars_in_scope[$var_id] = Type::getMixed();
|
||||
@ -2296,7 +2308,7 @@ class StatementsChecker
|
||||
$array_type = $context_type;
|
||||
|
||||
for ($i = 0; $i < $nesting + 1; $i++) {
|
||||
if ($array_type->isArray()) {
|
||||
if ($array_type->hasArray()) {
|
||||
if ($i < $nesting) {
|
||||
if ($array_type->types['array']->type_params[1]->isEmpty()) {
|
||||
$array_type->types['array']->type_params[1] = $return_type;
|
||||
|
@ -2,6 +2,9 @@
|
||||
|
||||
namespace Psalm;
|
||||
|
||||
use PhpParser;
|
||||
use Psalm\Checker\StatementsChecker;
|
||||
|
||||
class Context
|
||||
{
|
||||
/** @var array<string, Type\Union> */
|
||||
@ -51,11 +54,11 @@ class Context
|
||||
{
|
||||
foreach ($this->vars_in_scope as $var => &$context_type) {
|
||||
$old_type = $start_context->vars_in_scope[$var];
|
||||
// if we're leaving, we're effectively deleting the possibility of the if types
|
||||
$new_type = !$has_leaving_statements ? $end_context->vars_in_scope[$var] : null;
|
||||
|
||||
// this is only true if there was some sort of type negation
|
||||
if (in_array($var, $vars_to_update)) {
|
||||
// if we're leaving, we're effectively deleting the possibility of the if types
|
||||
$new_type = !$has_leaving_statements ? $end_context->vars_in_scope[$var] : null;
|
||||
|
||||
// if the type changed within the block of statements, process the replacement
|
||||
if ((string)$old_type !== (string)$new_type) {
|
||||
@ -78,4 +81,39 @@ class Context
|
||||
|
||||
return $redefined_vars;
|
||||
}
|
||||
|
||||
public function remove($remove_var_id)
|
||||
{
|
||||
if (isset($this->vars_in_scope[$remove_var_id])) {
|
||||
$type = $this->vars_in_scope[$remove_var_id];
|
||||
unset($this->vars_in_scope[$remove_var_id]);
|
||||
|
||||
$this->removeDescendents($remove_var_id, $type);
|
||||
}
|
||||
}
|
||||
|
||||
public function removeDescendents($remove_var_id, \Psalm\Type\Union $type = null)
|
||||
{
|
||||
if (!$type && isset($this->vars_in_scope[$remove_var_id])) {
|
||||
$type = $this->vars_in_scope[$remove_var_id];
|
||||
}
|
||||
|
||||
if (!$type) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($type->hasArray() || $type->hasObjectType() || $type->isMixed()) {
|
||||
$vars_to_remove = [];
|
||||
|
||||
foreach ($this->vars_in_scope as $var_id => $context_type) {
|
||||
if (preg_match('/^' . preg_quote($var_id, '/') . '[\[\-]/', $var_id)) {
|
||||
$vars_to_remove[] = $var_id;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($vars_to_remove as $var_id) {
|
||||
unset($this->vars_in_scope[$var_id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -275,17 +275,6 @@ abstract class Type
|
||||
}
|
||||
}
|
||||
|
||||
public function isString()
|
||||
{
|
||||
if ($this instanceof Atomic) {
|
||||
return $this->value === 'string';
|
||||
}
|
||||
|
||||
if ($this instanceof Union) {
|
||||
return isset($this->types['string']);
|
||||
}
|
||||
}
|
||||
|
||||
public function isVoid()
|
||||
{
|
||||
if ($this instanceof Atomic) {
|
||||
@ -297,50 +286,6 @@ abstract class Type
|
||||
}
|
||||
}
|
||||
|
||||
public function isNumeric()
|
||||
{
|
||||
if ($this instanceof Atomic) {
|
||||
return $this->value === 'numeric';
|
||||
}
|
||||
|
||||
if ($this instanceof Union) {
|
||||
return isset($this->types['numeric']);
|
||||
}
|
||||
}
|
||||
|
||||
public function isScalar()
|
||||
{
|
||||
if ($this instanceof Atomic) {
|
||||
return $this->value === 'scalar';
|
||||
}
|
||||
|
||||
if ($this instanceof Union) {
|
||||
return isset($this->types['scalar']);
|
||||
}
|
||||
}
|
||||
|
||||
public function isResource()
|
||||
{
|
||||
if ($this instanceof Atomic) {
|
||||
return $this->value === 'resource';
|
||||
}
|
||||
|
||||
if ($this instanceof Union) {
|
||||
return isset($this->types['resource']);
|
||||
}
|
||||
}
|
||||
|
||||
public function isCallable()
|
||||
{
|
||||
if ($this instanceof Atomic) {
|
||||
return $this->value === 'callable';
|
||||
}
|
||||
|
||||
if ($this instanceof Union) {
|
||||
return isset($this->types['callable']);
|
||||
}
|
||||
}
|
||||
|
||||
public function isEmpty()
|
||||
{
|
||||
if ($this instanceof Atomic) {
|
||||
@ -352,85 +297,6 @@ abstract class Type
|
||||
}
|
||||
}
|
||||
|
||||
public function isObject()
|
||||
{
|
||||
if ($this instanceof Atomic) {
|
||||
return $this->value === 'object';
|
||||
}
|
||||
|
||||
if ($this instanceof Union) {
|
||||
return isset($this->types['object']);
|
||||
}
|
||||
}
|
||||
|
||||
public function isArray()
|
||||
{
|
||||
if ($this instanceof Atomic) {
|
||||
return $this->value === 'array';
|
||||
}
|
||||
|
||||
if ($this instanceof Union) {
|
||||
return isset($this->types['array']);
|
||||
}
|
||||
}
|
||||
|
||||
public function isNullable()
|
||||
{
|
||||
if ($this instanceof Atomic) {
|
||||
return $this->value === 'null';
|
||||
}
|
||||
|
||||
if ($this instanceof Union) {
|
||||
return isset($this->types['null']);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function hasGeneric()
|
||||
{
|
||||
if ($this instanceof Union) {
|
||||
foreach ($this->types as $type) {
|
||||
if ($type instanceof Generic) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this instanceof Generic;
|
||||
}
|
||||
|
||||
public function isScalarType()
|
||||
{
|
||||
if ($this instanceof Atomic) {
|
||||
return $this->value === 'int' ||
|
||||
$this->value === 'string' ||
|
||||
$this->value === 'float' ||
|
||||
$this->value === 'bool' ||
|
||||
$this->value === 'false' ||
|
||||
$this->value === 'numeric';
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function isNumericType()
|
||||
{
|
||||
if ($this instanceof Atomic) {
|
||||
return $this->value === 'int' ||
|
||||
$this->value === 'float';
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function isObjectType()
|
||||
{
|
||||
return $this->isObject() || (!$this->isScalarType() && !$this->isCallable() && !$this->isArray() && !$this->isMixed() && !$this->isNull() && !$this->isResource());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<Union> $redefined_vars
|
||||
* @param Context $context
|
||||
|
@ -66,4 +66,67 @@ class Atomic extends Type
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function isArray()
|
||||
{
|
||||
return $this->value === 'array';
|
||||
}
|
||||
|
||||
public function isObject()
|
||||
{
|
||||
return $this->value === 'object';
|
||||
}
|
||||
|
||||
public function isNumericType()
|
||||
{
|
||||
return $this->value === 'int' || $this->value === 'float';
|
||||
}
|
||||
|
||||
public function isScalarType()
|
||||
{
|
||||
return $this->value === 'int' ||
|
||||
$this->value === 'string' ||
|
||||
$this->value === 'float' ||
|
||||
$this->value === 'bool' ||
|
||||
$this->value === 'false' ||
|
||||
$this->value === 'numeric';
|
||||
}
|
||||
|
||||
public function isObjectType()
|
||||
{
|
||||
return $this->isObject()
|
||||
|| (
|
||||
!$this->isScalarType()
|
||||
&& !$this->isCallable()
|
||||
&& !$this->isArray()
|
||||
&& !$this->isMixed()
|
||||
&& !$this->isNull()
|
||||
&& !$this->isResource()
|
||||
);
|
||||
}
|
||||
|
||||
public function isString()
|
||||
{
|
||||
return $this->value === 'string';
|
||||
}
|
||||
|
||||
public function isNumeric()
|
||||
{
|
||||
return $this->value === 'numeric';
|
||||
}
|
||||
|
||||
public function isScalar()
|
||||
{
|
||||
return $this->value === 'scalar';
|
||||
}
|
||||
|
||||
public function isResource()
|
||||
{
|
||||
return $this->value === 'resource';
|
||||
}
|
||||
|
||||
public function isCallable()
|
||||
{
|
||||
return $this->value === 'callable';
|
||||
}
|
||||
}
|
||||
|
@ -52,6 +52,68 @@ class Union extends Type
|
||||
return isset($this->types[$type_string]);
|
||||
}
|
||||
|
||||
public function hasGeneric()
|
||||
{
|
||||
foreach ($this->types as $type) {
|
||||
if ($type instanceof Generic) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function hasArray()
|
||||
{
|
||||
return isset($this->types['array']);
|
||||
}
|
||||
|
||||
public function hasObject()
|
||||
{
|
||||
return isset($this->types['object']);
|
||||
}
|
||||
|
||||
public function hasObjectType()
|
||||
{
|
||||
foreach ($this->types as $type) {
|
||||
if ($type->isObjectType()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function isNullable()
|
||||
{
|
||||
return isset($this->types['null']);
|
||||
}
|
||||
|
||||
public function hasString()
|
||||
{
|
||||
return isset($this->types['string']);
|
||||
}
|
||||
|
||||
public function hasNumeric()
|
||||
{
|
||||
return isset($this->types['numeric']);
|
||||
}
|
||||
|
||||
public function hasScalar()
|
||||
{
|
||||
return isset($this->types['scalar']);
|
||||
}
|
||||
|
||||
public function hasResource()
|
||||
{
|
||||
return isset($this->types['resource']);
|
||||
}
|
||||
|
||||
public function hasCallable()
|
||||
{
|
||||
return isset($this->types['callable']);
|
||||
}
|
||||
|
||||
public function removeObjects()
|
||||
{
|
||||
foreach ($this->types as $key => $type) {
|
||||
|
Loading…
Reference in New Issue
Block a user