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:
parent
316f2714b7
commit
2ed9d2bff7
@ -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()
|
||||
|
@ -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]);
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user