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:
parent
01beb97413
commit
6b2bb7d917
@ -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
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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(
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
)
|
||||
);
|
||||
|
@ -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)
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user