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

Add more useful return type data

This commit is contained in:
Matthew Brown 2016-05-16 16:12:02 -04:00
parent 316f2714b7
commit 2ed9d2bff7
4 changed files with 64 additions and 20 deletions

View File

@ -359,6 +359,8 @@ class ClassChecker implements StatementsSource
public static function setThisClass($this_class)
{
self::$_this_class = $this_class;
self::$_class_checkers = [];
}
public static function getThisClass()

View File

@ -96,7 +96,6 @@ class FileChecker implements StatementsSource
public function checkWithClass($class_name, $method_vars = [])
{
$stmts = self::getStatements($this->_file_name);
$this->_class_name = $class_name;
$class_method = new PhpParser\Node\Stmt\ClassMethod($class_name, ['stmts' => $stmts]);

View File

@ -16,9 +16,10 @@ class FunctionChecker implements StatementsSource
protected $_absolute_class;
protected $_statements_checker;
protected $_source;
protected $_return_vars_in_scope = [];
protected $_return_vars_possibly_in_scope = [];
protected $_function_params = [];
protected $_function_return_types = [];
public function __construct(PhpParser\Node\FunctionLike $function, StatementsSource $source)
{
@ -32,12 +33,9 @@ class FunctionChecker implements StatementsSource
$this->_source = $source;
}
public function check(&$extra_scope_vars = [])
public function check(&$vars_in_scope = [], &$vars_possibly_in_scope = [])
{
if ($this->_function->stmts) {
$vars_in_scope = $extra_scope_vars;
$vars_possibly_in_scope = $extra_scope_vars;
$statements_checker = new StatementsChecker($this, !empty($this->_function->params));
foreach ($this->_function->params as $param) {
@ -74,7 +72,42 @@ class FunctionChecker implements StatementsSource
$statements_checker->check($this->_function->stmts, $vars_in_scope, $vars_possibly_in_scope);
$extra_scope_vars = $vars_in_scope;
if (isset($this->_return_vars_in_scope[''])) {
$vars_in_scope = TypeChecker::combineTypes($vars_in_scope, $this->_return_vars_in_scope['']);
}
if (isset($this->_return_vars_possibly_in_scope[''])) {
$vars_possibly_in_scope = TypeChecker::combineTypes($vars_possibly_in_scope, $this->_return_vars_possibly_in_scope['']);
}
foreach ($vars_in_scope as $var => $type) {
if (strpos($var, 'this->') !== 0) {
unset($vars_in_scope[$var]);
}
}
foreach ($vars_in_scope as $var => $type) {
if (strpos($var, 'this->') !== 0) {
unset($vars_possibly_in_scope[$var]);
}
}
}
}
public function addReturnTypes($return_type, $vars_in_scope, $vars_possibly_in_scope)
{
if (isset($this->_return_vars_in_scope[$return_type])) {
$this->_return_vars_in_scope[$return_type] = TypeChecker::combineTypes($vars_in_scope, $this->_return_vars_in_scope[$return_type]);
}
else {
$this->_return_vars_in_scope[$return_type] = $vars_in_scope;
}
if (isset($this->_return_vars_possibly_in_scope[$return_type])) {
$this->_return_vars_possibly_in_scope[$return_type] = TypeChecker::combineTypes($vars_possibly_in_scope, $this->_return_vars_possibly_in_scope[$return_type]);
}
else {
$this->_return_vars_possibly_in_scope[$return_type] = $vars_possibly_in_scope;
}
}

View File

@ -269,6 +269,7 @@ class StatementsChecker
if (!$has_ending_statments) {
$vars = array_diff_key($if_vars_possibly_in_scope, $vars_possibly_in_scope);
// if we're leaving this block, add vars to outer for loop scope
if ($has_leaving_statments) {
$for_vars_possibly_in_scope = array_merge($for_vars_possibly_in_scope, $vars);
}
@ -364,6 +365,7 @@ class StatementsChecker
if (!$has_ending_statments) {
$vars = array_diff_key($elseif_vars_possibly_in_scope, $vars_possibly_in_scope);
// if we're leaving this block, add vars to outer for loop scope
if ($has_leaving_statements) {
$for_vars_possibly_in_scope = array_merge($vars, $for_vars_possibly_in_scope);
}
@ -902,7 +904,7 @@ class StatementsChecker
if ($stmt->var instanceof PhpParser\Node\Expr\Variable) {
if ($stmt->var->name === 'this') {
if (is_string($stmt->name)) {
if (!FileChecker::shouldCheckClassProperties($this->_file_name)) {
if (!ClassChecker::getThisClass() && !FileChecker::shouldCheckClassProperties($this->_file_name)) {
// ignore this property
} else {
$class_checker = $this->_source->getClassChecker();
@ -915,7 +917,7 @@ class StatementsChecker
$property_id = $this->_absolute_class . '::' . $stmt->name;
$var_id = $stmt->var->name . '->' . $stmt->name;
$var_defined = isset($vars_in_scope[$var_id]);
$var_defined = isset($vars_in_scope[$var_id]) || isset($vars_possibly_in_scope[$var_id]);
if ((ClassChecker::getThisClass() && !$var_defined) || (!ClassChecker::getThisClass() && !$var_defined && !self::_propertyExists($property_id))) {
throw new UndefinedPropertyException('$' . $var_id . ' is not defined', $this->_file_name, $stmt->getLine());
@ -1277,6 +1279,8 @@ class StatementsChecker
$vars_in_scope[$var_id] = 'mixed';
}
$vars_possibly_in_scope[$var_id] = true;
// right now we have to settle for mixed
self::$_this_assignments[$method_id][$stmt->var->name] = 'mixed';
//self::$_this_assignments[$method_id][$stmt->var->name] = $vars_in_scope[$property_id];
@ -1338,24 +1342,26 @@ class StatementsChecker
$method_id = ClassChecker::getThisClass() . '::' . $stmt->name;
$this_vars_in_scope = [];
foreach ($vars_in_scope as $var => $type) {
if (strpos($var, 'this->') === 0) {
$this_vars_in_scope[$var] = $type;
}
}
$method_checker = ClassChecker::getMethodChecker($method_id);
if ($method_checker->getMethodId() !== $this->_source->getMethodId()) {
$method_checker->check($this_vars_in_scope);
$this_vars_in_scope = [];
foreach ($this_vars_in_scope as $var => $type) {
$this_vars_possibly_in_scope = [];
foreach ($vars_possibly_in_scope as $var => $type) {
if (strpos($var, 'this->') === 0) {
$vars_in_scope[$var] = $type;
$this_vars_possibly_in_scope[$var] = true;
}
}
foreach ($vars_in_scope as $var => $type) {
if (strpos($var, 'this->') === 0) {
$this_vars_in_scope[$var] = $type;
}
}
$method_checker->check($this_vars_in_scope, $this_vars_possibly_in_scope);
}
}
}
@ -1732,6 +1738,10 @@ class StatementsChecker
else {
$stmt->returnType = 'void';
}
if ($this->_source instanceof FunctionChecker) {
$this->_source->addReturnTypes($stmt->expr ? $stmt->returnType : '', $vars_in_scope, $vars_possibly_in_scope);
}
}
protected function _checkTernary(PhpParser\Node\Expr\Ternary $stmt, array &$vars_in_scope, array &$vars_possibly_in_scope)