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

Store first code location for all variables in a statement list

This commit is contained in:
Matt Brown 2017-02-07 18:09:12 -05:00
parent 01beb97413
commit 6b2bb7d917
7 changed files with 101 additions and 42 deletions

View File

@ -313,7 +313,7 @@ abstract class FunctionLikeChecker extends SourceChecker implements StatementsSo
$statements_checker->registerVariable(
$function_param->name,
$function_param->code_location->getLineNumber()
$function_param->code_location
);
}

View File

@ -151,9 +151,16 @@ class ForeachChecker
}
if ($stmt->keyVar && $stmt->keyVar instanceof PhpParser\Node\Expr\Variable && is_string($stmt->keyVar->name)) {
$foreach_context->vars_in_scope['$' . $stmt->keyVar->name] = $key_type ?: Type::getMixed();
$foreach_context->vars_possibly_in_scope['$' . $stmt->keyVar->name] = true;
$statements_checker->registerVariable('$' . $stmt->keyVar->name, $stmt->getLine());
$key_var_id = '$' . $stmt->keyVar->name;
$foreach_context->vars_in_scope[$key_var_id] = $key_type ?: Type::getMixed();
$foreach_context->vars_possibly_in_scope[$key_var_id] = true;
if (!$statements_checker->hasVariable($key_var_id)) {
$statements_checker->registerVariable(
$key_var_id,
new CodeLocation($statements_checker, $stmt)
);
}
}
AssignmentChecker::analyze(

View File

@ -56,7 +56,9 @@ class TryChecker
$fq_catch_classes[] = $fq_catch_class;
}
$catch_context->vars_in_scope['$' . $catch->var] = new Type\Union(
$catch_var_id = '$' . $catch->var;
$catch_context->vars_in_scope[$catch_var_id] = new Type\Union(
array_map(
/**
* @param string $fq_catch_class
@ -69,9 +71,15 @@ class TryChecker
)
);
$catch_context->vars_possibly_in_scope['$' . $catch->var] = true;
$catch_context->vars_possibly_in_scope[$catch_var_id] = true;
if (!$statements_checker->hasVariable($catch_var_id)) {
$statements_checker->registerVariable(
$catch_var_id,
new CodeLocation($statements_checker, $catch)
);
}
$statements_checker->registerVariable('$' . $catch->var, $catch->getLine());
$statements_checker->analyze($catch->stmts, $catch_context, $loop_context);

View File

@ -138,7 +138,10 @@ class AssignmentChecker
if ($assign_var instanceof PhpParser\Node\Expr\Variable && is_string($assign_var->name) && $var_id) {
$context->vars_in_scope[$var_id] = $assign_value_type;
$context->vars_possibly_in_scope[$var_id] = true;
$statements_checker->registerVariable($var_id, $assign_var->getLine());
if (!$statements_checker->hasVariable($var_id)) {
$statements_checker->registerVariable($var_id, new CodeLocation($statements_checker, $assign_var));
}
} elseif ($assign_var instanceof PhpParser\Node\Expr\List_
|| $assign_var instanceof PhpParser\Node\Expr\Array_
) {
@ -189,7 +192,10 @@ class AssignmentChecker
if ($list_var_id) {
$context->vars_possibly_in_scope[$list_var_id] = true;
$statements_checker->registerVariable($list_var_id, $var->getLine());
if (!$statements_checker->hasVariable($list_var_id)) {
$statements_checker->registerVariable($list_var_id, new CodeLocation($statements_checker, $var));
}
if (isset($assign_value_type->types['array'])) {
if ($assign_value_type->types['array'] instanceof Type\Atomic\TArray) {
@ -365,9 +371,13 @@ class AssignmentChecker
if ($stmt->var instanceof PhpParser\Node\Expr\Variable) {
if (is_string($stmt->var->name)) {
$context->vars_in_scope['$' . $stmt->var->name] = $type_in_comments ?: Type::getMixed();
$context->vars_possibly_in_scope['$' . $stmt->var->name] = true;
$statements_checker->registerVariable('$' . $stmt->var->name, $stmt->var->getLine());
$var_id = '$' . $stmt->var->name;
$context->vars_in_scope[$var_id] = $type_in_comments ?: Type::getMixed();
$context->vars_possibly_in_scope[$var_id] = true;
if (!$statements_checker->hasVariable($var_id)) {
$statements_checker->registerVariable($var_id, new CodeLocation($statements_checker, $stmt->var));
}
} else {
if (ExpressionChecker::analyze($statements_checker, $stmt->var->name, $context) === false) {
return false;

View File

@ -1088,7 +1088,9 @@ class CallChecker
// we don't know if it exists, assume it's passed by reference
$context->vars_in_scope[$var_id] = Type::getMixed();
$context->vars_possibly_in_scope[$var_id] = true;
$statements_checker->registerVariable('$' . $var_id, $arg->value->getLine());
if (!$statements_checker->hasVariable($var_id)) {
$statements_checker->registerVariable($var_id, new CodeLocation($statements_checker, $arg->value));
}
}
}
} elseif ($arg->value instanceof PhpParser\Node\Expr\Variable) {
@ -1115,14 +1117,20 @@ class CallChecker
return false;
}
} elseif (is_string($arg->value->name)) {
if (false ||
!isset($context->vars_in_scope['$' . $arg->value->name]) ||
$context->vars_in_scope['$' . $arg->value->name]->isNull()
$var_id = '$' . $arg->value->name;
if (!isset($context->vars_in_scope[$var_id]) ||
$context->vars_in_scope[$var_id]->isNull()
) {
// we don't know if it exists, assume it's passed by reference
$context->vars_in_scope['$' . $arg->value->name] = Type::getMixed();
$context->vars_possibly_in_scope['$' . $arg->value->name] = true;
$statements_checker->registerVariable('$' . $arg->value->name, $arg->value->getLine());
$context->vars_in_scope[$var_id] = Type::getMixed();
$context->vars_possibly_in_scope[$var_id] = true;
if (!$statements_checker->hasVariable($var_id)) {
$statements_checker->registerVariable(
$var_id,
new CodeLocation($statements_checker, $arg->value)
);
}
}
}
} else {

View File

@ -519,7 +519,15 @@ class ExpressionChecker
$context->vars_in_scope[$var_name] = Type::getArray();
$context->vars_possibly_in_scope[$var_name] = true;
$statements_checker->registerVariable($var_name, $stmt->getLine());
// it might have been defined first in another if/else branch
if (!$statements_checker->hasVariable($var_name)) {
$statements_checker->registerVariable(
$var_name,
new CodeLocation($statements_checker, $stmt)
);
}
} else {
IssueBuffer::add(
new UndefinedVariable(
@ -532,11 +540,13 @@ class ExpressionChecker
}
}
if ($statements_checker->getFirstAppearance($var_name)) {
$first_appearance = $statements_checker->getFirstAppearance($var_name);
if ($first_appearance) {
if (IssueBuffer::accepts(
new PossiblyUndefinedVariable(
'Possibly undefined variable ' . $var_name .', first seen on line ' .
$statements_checker->getFirstAppearance($var_name),
$first_appearance->getLineNumber(),
new CodeLocation($statements_checker->getSource(), $stmt)
),
$statements_checker->getSuppressedIssues()
@ -573,7 +583,10 @@ class ExpressionChecker
if ($var_id) {
if (!$context->hasVariable($var_id)) {
$context->vars_possibly_in_scope[$var_id] = true;
$statements_checker->registerVariable($var_id, $stmt->getLine());
if (!$statements_checker->hasVariable($var_id)) {
$statements_checker->registerVariable($var_id, new CodeLocation($statements_checker, $stmt));
}
} else {
$existing_type = $context->vars_in_scope[$var_id];
if (TypeChecker::isContainedBy(
@ -1363,19 +1376,23 @@ class ExpressionChecker
Context $context
) {
foreach ($stmt->uses as $use) {
if (!$context->hasVariable('$' . $use->var)) {
$use_var_id = '$' . $use->var;
if (!$context->hasVariable($use_var_id)) {
if ($use->byRef) {
$context->vars_in_scope['$' . $use->var] = Type::getMixed();
$context->vars_possibly_in_scope['$' . $use->var] = true;
$statements_checker->registerVariable('$' . $use->var, $use->getLine());
$context->vars_in_scope[$use_var_id] = Type::getMixed();
$context->vars_possibly_in_scope[$use_var_id] = true;
if (!$statements_checker->hasVariable($use_var_id)) {
$statements_checker->registerVariable($use_var_id, new CodeLocation($statements_checker, $use));
}
return;
}
if (!isset($context->vars_possibly_in_scope['$' . $use->var])) {
if (!isset($context->vars_possibly_in_scope[$use_var_id])) {
if ($context->check_variables) {
IssueBuffer::add(
new UndefinedVariable(
'Cannot find referenced variable $' . $use->var,
'Cannot find referenced variable ' . $use_var_id,
new CodeLocation($statements_checker->getSource(), $use)
)
);
@ -1384,11 +1401,13 @@ class ExpressionChecker
}
}
if ($statements_checker->getFirstAppearance('$' . $use->var)) {
$first_appearance = $statements_checker->getFirstAppearance($use_var_id);
if ($first_appearance) {
if (IssueBuffer::accepts(
new PossiblyUndefinedVariable(
'Possibly undefined variable $' . $use->var . ', first seen on line ' .
$statements_checker->getFirstAppearance('$' . $use->var),
'Possibly undefined variable ' . $use_var_id . ', first seen on line ' .
$first_appearance->getLineNumber(),
new CodeLocation($statements_checker->getSource(), $use)
),
$statements_checker->getSuppressedIssues()
@ -1402,7 +1421,7 @@ class ExpressionChecker
if ($context->check_variables) {
IssueBuffer::add(
new UndefinedVariable(
'Cannot find referenced variable $' . $use->var,
'Cannot find referenced variable ' . $use_var_id,
new CodeLocation($statements_checker->getSource(), $use)
)
);

View File

@ -31,7 +31,7 @@ class StatementsChecker extends SourceChecker implements StatementsSource
protected $source;
/**
* @var array<string, int>
* @var array<string, CodeLocation>
*/
protected $all_vars = [];
@ -334,7 +334,7 @@ class StatementsChecker extends SourceChecker implements StatementsSource
: Type::getMixed();
$context->vars_possibly_in_scope['$' . $var->name] = true;
$this->registerVariable('$' . $var->name, $var->getLine());
$this->registerVariable('$' . $var->name, new CodeLocation($this, $stmt));
}
}
@ -535,15 +535,22 @@ class StatementsChecker extends SourceChecker implements StatementsSource
}
/**
* @param string $var_name
* @param int $line_number
* @param string $var_name
* @return bool
*/
public function hasVariable($var_name)
{
return isset($this->all_vars[$var_name]);
}
/**
* @param string $var_name
* @param CodeLocation $location
* @return void
*/
public function registerVariable($var_name, $line_number)
public function registerVariable($var_name, CodeLocation $location)
{
if (!isset($this->all_vars[$var_name])) {
$this->all_vars[$var_name] = $line_number;
}
$this->all_vars[$var_name] = $location;
}
/**
@ -712,7 +719,7 @@ class StatementsChecker extends SourceChecker implements StatementsSource
* The first appearance of the variable in this set of statements being evaluated
*
* @param string $var_name
* @return int|null
* @return CodeLocation|null
*/
public function getFirstAppearance($var_name)
{