1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-27 04:45:20 +01:00

Add support for square bracket array declarations is_array checks

This commit is contained in:
Matthew Brown 2016-04-12 11:28:36 -04:00
parent d07e340d66
commit ff578f8468
2 changed files with 89 additions and 2 deletions

View File

@ -44,6 +44,8 @@ class StatementsChecker
protected static $_check_string_fn = null; protected static $_check_string_fn = null;
protected static $_mock_interfaces = []; protected static $_mock_interfaces = [];
const TYPE_REGEX = '(\\\?[A-Za-z0-9\<\>\[\]|\\\]+[A-Za-z0-9\<\>\[\]]|\$[a-zA-Z_0-9\<\>\[\]]+)';
public function __construct(StatementsSource $source, $check_variables = true) public function __construct(StatementsSource $source, $check_variables = true)
{ {
$this->_source = $source; $this->_source = $source;
@ -412,6 +414,9 @@ class StatementsChecker
$vars_in_scope[$var] = $redefined_vars[$var]; $vars_in_scope[$var] = $redefined_vars[$var];
} }
} }
elseif ($type === '!array' && $redefined_vars[$var] === 'array') {
$vars_in_scope[$var] = $redefined_vars[$var];
}
} }
} }
} }
@ -504,6 +509,10 @@ class StatementsChecker
$var_name = $this->_getVariable($conditional->expr->args[0]->value); $var_name = $this->_getVariable($conditional->expr->args[0]->value);
$if_types[$var_name] = '!null'; $if_types[$var_name] = '!null';
} }
else if (self::_hasArrayCheck($conditional->expr)) {
$var_name = $this->_getVariable($conditional->expr->args[0]->value);
$if_types[$var_name] = '!array';
}
} }
else if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical) { else if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical) {
if (self::_hasNullVariable($conditional)) { if (self::_hasNullVariable($conditional)) {
@ -525,6 +534,10 @@ class StatementsChecker
$var_name = $this->_getVariable($conditional->args[0]->value); $var_name = $this->_getVariable($conditional->args[0]->value);
$if_types[$var_name] = 'null'; $if_types[$var_name] = 'null';
} }
else if (self::_hasArrayCheck($conditional)) {
$var_name = $this->_getVariable($conditional->args[0]->value);
$if_types[$var_name] = 'array';
}
else if ($conditional instanceof PhpParser\Node\Expr\Empty_) { else if ($conditional instanceof PhpParser\Node\Expr\Empty_) {
$var_name = $this->_getVariable($conditional->expr); $var_name = $this->_getVariable($conditional->expr);
if ($var_name) { if ($var_name) {
@ -624,6 +637,15 @@ class StatementsChecker
return false; return false;
} }
protected static function _hasArrayCheck(PhpParser\Node\Expr $stmt)
{
if ($stmt instanceof PhpParser\Node\Expr\FuncCall && $stmt->name instanceof PhpParser\Node\Name && $stmt->name->parts === ['is_array']) {
return true;
}
return false;
}
protected function _checkStatic(PhpParser\Node\Stmt\Static_ $stmt, array &$vars_in_scope, array &$vars_possibly_in_scope = []) protected function _checkStatic(PhpParser\Node\Stmt\Static_ $stmt, array &$vars_in_scope, array &$vars_possibly_in_scope = [])
{ {
foreach ($stmt->vars as $var) { foreach ($stmt->vars as $var) {
@ -1846,7 +1868,7 @@ class StatementsChecker
$return_blocks = explode(' ', $comments['specials']['return'][0]); $return_blocks = explode(' ', $comments['specials']['return'][0]);
foreach ($return_blocks as $block) { foreach ($return_blocks as $block) {
if ($block) { if ($block) {
if ($block && preg_match('/^(\\\?[A-Za-z0-9\<\>|\\\]+[A-Za-z0-9\<\>]|\$[a-zA-Z_0-9\<\>]+)$/', $block)) { if ($block && preg_match('/^' . self::TYPE_REGEX . '$/', $block)) {
$return_types = explode('|', $block); $return_types = explode('|', $block);
break; break;
} }
@ -1906,6 +1928,10 @@ class StatementsChecker
protected static function _fixUpLocalReturnType($return_type, $method_id, $namespace, $aliased_classes) protected static function _fixUpLocalReturnType($return_type, $method_id, $namespace, $aliased_classes)
{ {
if (strpos($return_type, '[') !== false) {
$return_type = self::_convertSquareBrackets($return_type);
}
if ($return_type[0] === '\\') { if ($return_type[0] === '\\') {
return $return_type; return $return_type;
} }
@ -1925,6 +1951,10 @@ class StatementsChecker
protected static function _fixUpReturnType($return_type, $method_id) protected static function _fixUpReturnType($return_type, $method_id)
{ {
if (strpos($return_type, '[') !== false) {
$return_type = self::_convertSquareBrackets($return_type);
}
$return_type_parts = ['']; $return_type_parts = [''];
$was_char = false; $was_char = false;
@ -1963,6 +1993,25 @@ class StatementsChecker
return implode('', $return_type_parts); return implode('', $return_type_parts);
} }
public function _convertSquareBrackets($type)
{
return preg_replace_callback(
'/([a-zA-Z\<\>]+)((\[\])+)/',
function($matches) {
$inner_type = $matches[1];
$dimensionality = strlen($matches[2]) / 2;
for ($i = 0; $i < $dimensionality; $i++) {
$inner_type = 'array<' . $inner_type . '>';
}
return $inner_type;
},
$type
);
}
public function registerVariable($var_name, $line_number) public function registerVariable($var_name, $line_number)
{ {
if (!isset($this->_all_vars[$var_name])) { if (!isset($this->_all_vars[$var_name])) {
@ -2283,7 +2332,7 @@ class StatementsChecker
if (isset($comments['specials']['return'])) { if (isset($comments['specials']['return'])) {
$return_blocks = explode(' ', $comments['specials']['return'][0]); $return_blocks = explode(' ', $comments['specials']['return'][0]);
foreach ($return_blocks as $block) { foreach ($return_blocks as $block) {
if ($block && preg_match('/^(\\\?[A-Za-z0-9\<\>|\\\]+[A-Za-z0-9\<\>]|\$[a-zA-Z_0-9\<\>]+)$/', $block)) { if ($block && preg_match('/^' . self::TYPE_REGEX . '$/', $block)) {
$return_types = explode('|', $block); $return_types = explode('|', $block);
break; break;
} }

View File

@ -723,4 +723,42 @@ class TypeTest extends PHPUnit_Framework_TestCase
$file_checker = new \CodeInspector\FileChecker('somefile.php', $stmts); $file_checker = new \CodeInspector\FileChecker('somefile.php', $stmts);
$file_checker->check(); $file_checker->check();
} }
public function testArrayUnionTypeAssertion()
{
$stmts = self::$_parser->parse('<?php
/** @var array|null */
$ids = (1 + 1 === 2) ? [] : null;
if ($ids === null) {
$ids = [];
}
foreach ($ids as $id) {
}
');
$file_checker = new \CodeInspector\FileChecker('somefile.php', $stmts);
$file_checker->check();
}
public function testArrayUnionTypeAssertionWithIsArray()
{
$stmts = self::$_parser->parse('<?php
/** @var array|null */
$ids = (1 + 1 === 2) ? [] : null;
if (!is_array($ids)) {
$ids = [];
}
foreach ($ids as $id) {
}
');
$file_checker = new \CodeInspector\FileChecker('somefile.php', $stmts);
$file_checker->check();
}
} }