1
0
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:
Matthew Brown 2016-09-17 11:57:44 -04:00
parent dff23e9d74
commit e9a30ca556
6 changed files with 189 additions and 148 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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