1
0
mirror of https://github.com/danog/psalm.git synced 2024-12-16 03:17:02 +01:00
psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/VariableFetchAnalyzer.php

307 lines
11 KiB
PHP
Raw Normal View History

2018-01-14 18:09:40 +01:00
<?php
2018-11-06 03:57:36 +01:00
namespace Psalm\Internal\Analyzer\Statements\Expression\Fetch;
2018-01-14 18:09:40 +01:00
use PhpParser;
2018-11-06 03:57:36 +01:00
use Psalm\Internal\Analyzer\FunctionLikeAnalyzer;
use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer;
use Psalm\Internal\Analyzer\StatementsAnalyzer;
2018-01-14 18:09:40 +01:00
use Psalm\CodeLocation;
use Psalm\Context;
use Psalm\Issue\InvalidScope;
use Psalm\Issue\PossiblyUndefinedGlobalVariable;
use Psalm\Issue\PossiblyUndefinedVariable;
use Psalm\Issue\UndefinedGlobalVariable;
use Psalm\Issue\UndefinedVariable;
use Psalm\IssueBuffer;
use Psalm\Type;
use function is_string;
2018-01-14 18:09:40 +01:00
/**
* @internal
*/
2018-11-06 03:57:36 +01:00
class VariableFetchAnalyzer
2018-01-14 18:09:40 +01:00
{
/**
2018-11-11 18:01:14 +01:00
* @param StatementsAnalyzer $statements_analyzer
2018-01-14 18:09:40 +01:00
* @param PhpParser\Node\Expr\Variable $stmt
* @param Context $context
* @param bool $passed_by_reference
* @param Type\Union|null $by_ref_type
* @param bool $array_assignment
* @param bool $from_global - when used in a global keyword
2018-01-14 18:09:40 +01:00
*
* @return false|null
*/
public static function analyze(
2018-11-11 18:01:14 +01:00
StatementsAnalyzer $statements_analyzer,
2018-01-14 18:09:40 +01:00
PhpParser\Node\Expr\Variable $stmt,
Context $context,
$passed_by_reference = false,
Type\Union $by_ref_type = null,
$array_assignment = false,
$from_global = false
2018-01-14 18:09:40 +01:00
) {
2018-11-11 18:01:14 +01:00
$project_analyzer = $statements_analyzer->getFileAnalyzer()->project_analyzer;
$codebase = $statements_analyzer->getCodebase();
2018-10-07 02:11:19 +02:00
2018-01-14 18:09:40 +01:00
if ($stmt->name === 'this') {
2018-11-11 18:01:14 +01:00
if ($statements_analyzer->isStatic()) {
2018-01-14 18:09:40 +01:00
if (IssueBuffer::accepts(
new InvalidScope(
'Invalid reference to $this in a static context',
2018-11-11 18:01:14 +01:00
new CodeLocation($statements_analyzer->getSource(), $stmt)
2018-01-14 18:09:40 +01:00
),
2018-11-11 18:01:14 +01:00
$statements_analyzer->getSuppressedIssues()
2018-01-14 18:09:40 +01:00
)) {
return false;
}
return null;
}
if (!isset($context->vars_in_scope['$this'])) {
2018-01-14 18:09:40 +01:00
if (IssueBuffer::accepts(
new InvalidScope(
'Invalid reference to $this in a non-class context',
2018-11-11 18:01:14 +01:00
new CodeLocation($statements_analyzer->getSource(), $stmt)
2018-01-14 18:09:40 +01:00
),
2018-11-11 18:01:14 +01:00
$statements_analyzer->getSuppressedIssues()
2018-01-14 18:09:40 +01:00
)) {
return false;
}
$context->vars_in_scope['$this'] = Type::getMixed();
$context->vars_possibly_in_scope['$this'] = true;
2018-01-14 18:09:40 +01:00
return null;
}
$stmt->inferredType = clone $context->vars_in_scope['$this'];
2019-02-24 07:33:25 +01:00
if ($codebase->store_node_types
&& !$context->collect_initializations
&& !$context->collect_mutations
&& isset($stmt->inferredType)
) {
$codebase->analyzer->addNodeType(
2018-11-11 18:01:14 +01:00
$statements_analyzer->getFilePath(),
$stmt,
(string) $stmt->inferredType
);
}
2018-01-14 18:09:40 +01:00
return null;
}
if (!$context->check_variables) {
if (is_string($stmt->name)) {
$var_name = '$' . $stmt->name;
2018-11-11 18:01:14 +01:00
if (!$context->hasVariable($var_name, $statements_analyzer)) {
2018-01-14 18:09:40 +01:00
$context->vars_in_scope[$var_name] = Type::getMixed();
$context->vars_possibly_in_scope[$var_name] = true;
$stmt->inferredType = Type::getMixed();
} else {
$stmt->inferredType = clone $context->vars_in_scope[$var_name];
}
} else {
$stmt->inferredType = Type::getMixed();
}
return null;
}
2019-03-14 01:15:29 +01:00
if (is_string($stmt->name) && $statements_analyzer->isSuperGlobal('$' . $stmt->name)) {
2019-03-06 00:14:07 +01:00
$var_name = '$' . $stmt->name;
if (isset($context->vars_in_scope[$var_name])) {
$stmt->inferredType = clone $context->vars_in_scope[$var_name];
return null;
}
2019-03-14 01:15:29 +01:00
$type = $statements_analyzer->getGlobalType($var_name);
2019-03-06 00:14:07 +01:00
$stmt->inferredType = $type;
$context->vars_in_scope[$var_name] = clone $type;
$context->vars_possibly_in_scope[$var_name] = true;
2018-01-14 18:09:40 +01:00
return null;
}
if (!is_string($stmt->name)) {
2018-11-11 18:01:14 +01:00
return ExpressionAnalyzer::analyze($statements_analyzer, $stmt->name, $context);
2018-01-14 18:09:40 +01:00
}
if ($passed_by_reference && $by_ref_type) {
ExpressionAnalyzer::assignByRefParam(
$statements_analyzer,
$stmt,
$by_ref_type,
$by_ref_type,
$context
);
2018-01-14 18:09:40 +01:00
return null;
}
$var_name = '$' . $stmt->name;
2018-11-11 18:01:14 +01:00
if (!$context->hasVariable($var_name, $statements_analyzer)) {
2018-01-14 18:09:40 +01:00
if (!isset($context->vars_possibly_in_scope[$var_name]) ||
2018-11-11 18:01:14 +01:00
!$statements_analyzer->getFirstAppearance($var_name)
2018-01-14 18:09:40 +01:00
) {
if ($array_assignment) {
// if we're in an array assignment, let's assign the variable
// because PHP allows it
$context->vars_in_scope[$var_name] = Type::getArray();
$context->vars_possibly_in_scope[$var_name] = true;
// it might have been defined first in another if/else branch
2018-11-11 18:01:14 +01:00
if (!$statements_analyzer->hasVariable($var_name)) {
$statements_analyzer->registerVariable(
2018-01-14 18:09:40 +01:00
$var_name,
2018-11-11 18:01:14 +01:00
new CodeLocation($statements_analyzer, $stmt),
$context->branch_point
2018-01-14 18:09:40 +01:00
);
}
2018-05-01 04:13:13 +02:00
} elseif (!$context->inside_isset
2018-11-11 18:01:14 +01:00
|| $statements_analyzer->getSource() instanceof FunctionLikeAnalyzer
2018-05-01 04:13:13 +02:00
) {
if ($context->is_global || $from_global) {
2018-01-14 18:09:40 +01:00
if (IssueBuffer::accepts(
new UndefinedGlobalVariable(
'Cannot find referenced variable ' . $var_name . ' in global scope',
2018-11-11 18:01:14 +01:00
new CodeLocation($statements_analyzer->getSource(), $stmt)
2018-01-14 18:09:40 +01:00
),
2018-11-11 18:01:14 +01:00
$statements_analyzer->getSuppressedIssues()
2018-01-14 18:09:40 +01:00
)) {
return false;
}
$stmt->inferredType = Type::getMixed();
return null;
}
if (IssueBuffer::accepts(
2018-01-14 18:09:40 +01:00
new UndefinedVariable(
'Cannot find referenced variable ' . $var_name,
2018-11-11 18:01:14 +01:00
new CodeLocation($statements_analyzer->getSource(), $stmt)
),
2018-11-11 18:01:14 +01:00
$statements_analyzer->getSuppressedIssues()
)) {
// fall through
}
2018-01-14 18:09:40 +01:00
$stmt->inferredType = Type::getMixed();
return false;
}
}
2018-11-11 18:01:14 +01:00
$first_appearance = $statements_analyzer->getFirstAppearance($var_name);
2018-01-14 18:09:40 +01:00
if ($first_appearance && !$context->inside_isset && !$context->inside_unset) {
if ($context->is_global) {
2018-11-06 03:57:36 +01:00
if ($codebase->alter_code) {
2018-11-11 18:01:14 +01:00
if (!isset($project_analyzer->getIssuesToFix()['PossiblyUndefinedGlobalVariable'])) {
return;
}
2018-11-11 18:01:14 +01:00
$branch_point = $statements_analyzer->getBranchPoint($var_name);
if ($branch_point) {
2018-11-11 18:01:14 +01:00
$statements_analyzer->addVariableInitialization($var_name, $branch_point);
}
return;
}
2018-01-14 18:09:40 +01:00
if (IssueBuffer::accepts(
new PossiblyUndefinedGlobalVariable(
'Possibly undefined global variable ' . $var_name . ', first seen on line ' .
$first_appearance->getLineNumber(),
2018-11-11 18:01:14 +01:00
new CodeLocation($statements_analyzer->getSource(), $stmt)
2018-01-14 18:09:40 +01:00
),
2018-11-11 18:01:14 +01:00
$statements_analyzer->getSuppressedIssues()
2018-01-14 18:09:40 +01:00
)) {
return false;
}
} else {
2018-11-06 03:57:36 +01:00
if ($codebase->alter_code) {
2018-11-11 18:01:14 +01:00
if (!isset($project_analyzer->getIssuesToFix()['PossiblyUndefinedVariable'])) {
return;
}
2018-11-11 18:01:14 +01:00
$branch_point = $statements_analyzer->getBranchPoint($var_name);
if ($branch_point) {
2018-11-11 18:01:14 +01:00
$statements_analyzer->addVariableInitialization($var_name, $branch_point);
}
return;
}
2018-01-14 18:09:40 +01:00
if (IssueBuffer::accepts(
new PossiblyUndefinedVariable(
'Possibly undefined variable ' . $var_name . ', first seen on line ' .
$first_appearance->getLineNumber(),
2018-11-11 18:01:14 +01:00
new CodeLocation($statements_analyzer->getSource(), $stmt)
2018-01-14 18:09:40 +01:00
),
2018-11-11 18:01:14 +01:00
$statements_analyzer->getSuppressedIssues()
2018-01-14 18:09:40 +01:00
)) {
return false;
}
}
2019-07-02 00:48:33 +02:00
if ($codebase->store_node_types
&& !$context->collect_initializations
&& !$context->collect_mutations
) {
$codebase->analyzer->addNodeReference(
$statements_analyzer->getFilePath(),
$stmt,
$first_appearance->raw_file_start . '-' . $first_appearance->raw_file_end . ':mixed'
);
}
2018-11-11 18:01:14 +01:00
$statements_analyzer->registerVariableUses([$first_appearance->getHash() => $first_appearance]);
2018-01-14 18:09:40 +01:00
}
} else {
$stmt->inferredType = clone $context->vars_in_scope[$var_name];
2019-02-24 07:33:25 +01:00
if ($codebase->store_node_types
&& !$context->collect_initializations
&& !$context->collect_mutations
) {
$codebase->analyzer->addNodeType(
2018-11-11 18:01:14 +01:00
$statements_analyzer->getFilePath(),
$stmt,
(string) $stmt->inferredType
);
}
2019-07-02 00:48:33 +02:00
if ($codebase->store_node_types
&& !$context->collect_initializations
&& !$context->collect_mutations
) {
$first_appearance = $statements_analyzer->getFirstAppearance($var_name);
if ($first_appearance) {
$codebase->analyzer->addNodeReference(
$statements_analyzer->getFilePath(),
$stmt,
$first_appearance->raw_file_start
. '-' . $first_appearance->raw_file_end
. ':' . $stmt->inferredType->getId()
);
}
}
2018-01-14 18:09:40 +01:00
}
return null;
}
}