mirror of
https://github.com/danog/psalm.git
synced 2024-11-26 20:34:47 +01:00
Break up StatementsAnalyzer
This commit is contained in:
parent
588be3b269
commit
0b2da18f1e
@ -3,7 +3,6 @@ namespace Psalm\Internal\Analyzer;
|
||||
|
||||
use PhpParser;
|
||||
use Psalm\Aliases;
|
||||
use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\Expression\Call\ClassTemplateParamCollector;
|
||||
use Psalm\Internal\Type\UnionTemplateHandler;
|
||||
use Psalm\Codebase;
|
||||
|
@ -2,64 +2,18 @@
|
||||
namespace Psalm\Internal\Analyzer;
|
||||
|
||||
use PhpParser;
|
||||
use Psalm\Codebase;
|
||||
use Psalm\Internal\Analyzer\ClassAnalyzer;
|
||||
use Psalm\Internal\Analyzer\ClassLikeAnalyzer;
|
||||
use Psalm\Internal\Analyzer\CommentAnalyzer;
|
||||
use Psalm\Internal\Analyzer\FunctionLikeAnalyzer;
|
||||
use Psalm\Internal\Analyzer\TraitAnalyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\Expression\Call\FunctionCallAnalyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\Expression\Call\MethodCallAnalyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\Expression\Call\NewAnalyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\Expression\Call\StaticCallAnalyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\Expression;
|
||||
use Psalm\Internal\Analyzer\StatementsAnalyzer;
|
||||
use Psalm\CodeLocation;
|
||||
use Psalm\Config;
|
||||
use Psalm\Context;
|
||||
use Psalm\Exception\DocblockParseException;
|
||||
use Psalm\Internal\FileManipulation\FileManipulationBuffer;
|
||||
use Psalm\FileSource;
|
||||
use Psalm\Issue\DuplicateParam;
|
||||
use Psalm\Issue\ForbiddenCode;
|
||||
use Psalm\Issue\ImpurePropertyAssignment;
|
||||
use Psalm\Issue\InvalidCast;
|
||||
use Psalm\Issue\InvalidClone;
|
||||
use Psalm\Issue\InvalidDocblock;
|
||||
use Psalm\Issue\InvalidOperand;
|
||||
use Psalm\Issue\PossiblyInvalidCast;
|
||||
use Psalm\Issue\PossiblyInvalidOperand;
|
||||
use Psalm\Issue\PossiblyUndefinedVariable;
|
||||
use Psalm\Issue\UndefinedConstant;
|
||||
use Psalm\Issue\UndefinedVariable;
|
||||
use Psalm\Issue\UnnecessaryVarAnnotation;
|
||||
use Psalm\Issue\UnrecognizedExpression;
|
||||
use Psalm\IssueBuffer;
|
||||
use Psalm\Storage\FunctionLikeParameter;
|
||||
use Psalm\Type;
|
||||
use Psalm\Type\Atomic\ObjectLike;
|
||||
use Psalm\Type\Atomic\Scalar;
|
||||
use Psalm\Type\Atomic\TArray;
|
||||
use Psalm\Type\Atomic\TFloat;
|
||||
use Psalm\Type\Atomic\TTemplateParam;
|
||||
use Psalm\Type\Atomic\TInt;
|
||||
use Psalm\Type\Atomic\TList;
|
||||
use Psalm\Type\Atomic\TMixed;
|
||||
use Psalm\Type\Atomic\TNamedObject;
|
||||
use Psalm\Type\Atomic\TNull;
|
||||
use Psalm\Type\Atomic\TObject;
|
||||
use Psalm\Type\Atomic\TString;
|
||||
use Psalm\Internal\Type\TypeCombination;
|
||||
use function strpos;
|
||||
use function is_string;
|
||||
use function in_array;
|
||||
use function strtolower;
|
||||
use function get_class;
|
||||
use function count;
|
||||
use function implode;
|
||||
use function is_array;
|
||||
use function array_merge;
|
||||
use function array_values;
|
||||
use function array_map;
|
||||
use function current;
|
||||
|
||||
|
@ -8,6 +8,7 @@ use Psalm\Type;
|
||||
use function strtolower;
|
||||
use function array_values;
|
||||
use function count;
|
||||
use function is_string;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
@ -302,4 +303,75 @@ class FunctionAnalyzer extends FunctionLikeAnalyzer
|
||||
/** @var non-empty-lowercase-string */
|
||||
return ($namespace ? strtolower($namespace) . '\\' : '') . strtolower($this->function->name->name);
|
||||
}
|
||||
|
||||
public static function analyzeStatement(
|
||||
StatementsAnalyzer $statements_analyzer,
|
||||
PhpParser\Node\Stmt\Function_ $stmt,
|
||||
Context $context
|
||||
) : void {
|
||||
foreach ($stmt->stmts as $function_stmt) {
|
||||
if ($function_stmt instanceof PhpParser\Node\Stmt\Global_) {
|
||||
foreach ($function_stmt->vars as $var) {
|
||||
if ($var instanceof PhpParser\Node\Expr\Variable) {
|
||||
if (is_string($var->name)) {
|
||||
$var_id = '$' . $var->name;
|
||||
|
||||
// registers variable in global context
|
||||
$context->hasVariable($var_id, $statements_analyzer);
|
||||
}
|
||||
}
|
||||
}
|
||||
} elseif (!$function_stmt instanceof PhpParser\Node\Stmt\Nop) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$codebase = $statements_analyzer->getCodebase();
|
||||
|
||||
if (!$codebase->register_stub_files
|
||||
&& !$codebase->register_autoload_files
|
||||
) {
|
||||
$function_name = strtolower($stmt->name->name);
|
||||
|
||||
if ($ns = $statements_analyzer->getNamespace()) {
|
||||
$fq_function_name = strtolower($ns) . '\\' . $function_name;
|
||||
} else {
|
||||
$fq_function_name = $function_name;
|
||||
}
|
||||
|
||||
$function_context = new Context($context->self);
|
||||
$function_context->strict_types = $context->strict_types;
|
||||
$config = \Psalm\Config::getInstance();
|
||||
$function_context->collect_exceptions = $config->check_for_throws_docblock;
|
||||
|
||||
if ($function_analyzer = $statements_analyzer->getFunctionAnalyzer($fq_function_name)) {
|
||||
$function_analyzer->analyze(
|
||||
$function_context,
|
||||
$statements_analyzer->node_data,
|
||||
$context
|
||||
);
|
||||
|
||||
if ($config->reportIssueInFile('InvalidReturnType', $statements_analyzer->getFilePath())) {
|
||||
$method_id = $function_analyzer->getId();
|
||||
|
||||
$function_storage = $codebase->functions->getStorage(
|
||||
$statements_analyzer,
|
||||
strtolower($method_id)
|
||||
);
|
||||
|
||||
$return_type = $function_storage->return_type;
|
||||
$return_type_location = $function_storage->return_type_location;
|
||||
|
||||
$function_analyzer->verifyReturnType(
|
||||
$stmt->getStmts(),
|
||||
$statements_analyzer,
|
||||
$return_type,
|
||||
$statements_analyzer->getFQCLN(),
|
||||
$return_type_location,
|
||||
$function_context->has_returned
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,6 @@ use Psalm\Internal\Analyzer\InterfaceAnalyzer;
|
||||
use Psalm\Internal\Analyzer\ProjectAnalyzer;
|
||||
use Psalm\Internal\Analyzer\ScopeAnalyzer;
|
||||
use Psalm\Internal\Analyzer\SourceAnalyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\Expression\Call\ClassTemplateParamCollector;
|
||||
use Psalm\Internal\Analyzer\StatementsAnalyzer;
|
||||
use Psalm\Internal\Analyzer\TypeAnalyzer;
|
||||
|
@ -3,7 +3,6 @@ namespace Psalm\Internal\Analyzer;
|
||||
|
||||
use Psalm\Codebase;
|
||||
use Psalm\CodeLocation;
|
||||
use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer;
|
||||
use Psalm\Internal\MethodIdentifier;
|
||||
use Psalm\Issue\ImplementedParamTypeMismatch;
|
||||
use Psalm\Issue\ImplementedReturnTypeMismatch;
|
||||
|
@ -3,7 +3,6 @@ namespace Psalm\Internal\Analyzer\Statements\Block;
|
||||
|
||||
use PhpParser;
|
||||
use Psalm\Internal\Analyzer\ScopeAnalyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer;
|
||||
use Psalm\Internal\Analyzer\StatementsAnalyzer;
|
||||
use Psalm\Internal\Clause;
|
||||
use Psalm\Context;
|
||||
|
@ -9,6 +9,7 @@ use Psalm\Internal\Analyzer\ScopeAnalyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\Expression\AssignmentAnalyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\Expression\ExpressionIdentifier;
|
||||
use Psalm\Internal\Analyzer\Statements\Expression\Fetch\VariableFetchAnalyzer;
|
||||
use Psalm\Internal\Analyzer\StatementsAnalyzer;
|
||||
use Psalm\Internal\Analyzer\TypeAnalyzer;
|
||||
use Psalm\Internal\FileManipulation\FileManipulationBuffer;
|
||||
@ -149,7 +150,7 @@ class ForeachAnalyzer
|
||||
}
|
||||
|
||||
if (isset($context->vars_in_scope[$var_comment->var_id])
|
||||
|| $statements_analyzer->isSuperGlobal($var_comment->var_id)
|
||||
|| VariableFetchAnalyzer::isSuperGlobal($var_comment->var_id)
|
||||
) {
|
||||
if ($codebase->find_unused_variables
|
||||
&& $doc_comment
|
||||
|
@ -2,18 +2,11 @@
|
||||
namespace Psalm\Internal\Analyzer\Statements\Block;
|
||||
|
||||
use PhpParser;
|
||||
use Psalm\Codebase;
|
||||
use Psalm\Internal\Analyzer\AlgebraAnalyzer;
|
||||
use Psalm\Internal\Analyzer\ScopeAnalyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\Expression\ExpressionIdentifier;
|
||||
use Psalm\Internal\Analyzer\StatementsAnalyzer;
|
||||
use Psalm\CodeLocation;
|
||||
use Psalm\Context;
|
||||
use Psalm\Issue\ContinueOutsideLoop;
|
||||
use Psalm\Issue\ParadoxicalCondition;
|
||||
use Psalm\IssueBuffer;
|
||||
use Psalm\Internal\Scope\CaseScope;
|
||||
use Psalm\Internal\Scope\SwitchScope;
|
||||
use Psalm\Type;
|
||||
use Psalm\Type\Algebra;
|
||||
@ -21,10 +14,6 @@ use Psalm\Type\Reconciler;
|
||||
use function count;
|
||||
use function in_array;
|
||||
use function array_merge;
|
||||
use function is_string;
|
||||
use function substr;
|
||||
use function array_intersect_key;
|
||||
use function array_diff_key;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
|
116
src/Psalm/Internal/Analyzer/Statements/BreakAnalyzer.php
Normal file
116
src/Psalm/Internal/Analyzer/Statements/BreakAnalyzer.php
Normal file
@ -0,0 +1,116 @@
|
||||
<?php
|
||||
namespace Psalm\Internal\Analyzer\Statements;
|
||||
|
||||
use PhpParser;
|
||||
use Psalm\Internal\Analyzer\ScopeAnalyzer;
|
||||
use Psalm\Internal\Analyzer\StatementsAnalyzer;
|
||||
use Psalm\Context;
|
||||
use Psalm\Type;
|
||||
|
||||
class BreakAnalyzer
|
||||
{
|
||||
public static function analyze(
|
||||
StatementsAnalyzer $statements_analyzer,
|
||||
PhpParser\Node\Stmt\Break_ $stmt,
|
||||
Context $context
|
||||
) : void {
|
||||
$loop_scope = $context->loop_scope;
|
||||
|
||||
$leaving_switch = true;
|
||||
|
||||
$codebase = $statements_analyzer->getCodebase();
|
||||
|
||||
if ($loop_scope) {
|
||||
if ($context->break_types
|
||||
&& \end($context->break_types) === 'switch'
|
||||
&& (!$stmt->num
|
||||
|| !$stmt->num instanceof PhpParser\Node\Scalar\LNumber
|
||||
|| $stmt->num->value < 2
|
||||
)
|
||||
) {
|
||||
$loop_scope->final_actions[] = ScopeAnalyzer::ACTION_LEAVE_SWITCH;
|
||||
} else {
|
||||
$leaving_switch = false;
|
||||
|
||||
$loop_scope->final_actions[] = ScopeAnalyzer::ACTION_BREAK;
|
||||
}
|
||||
|
||||
$redefined_vars = $context->getRedefinedVars($loop_scope->loop_parent_context->vars_in_scope);
|
||||
|
||||
if ($loop_scope->possibly_redefined_loop_parent_vars === null) {
|
||||
$loop_scope->possibly_redefined_loop_parent_vars = $redefined_vars;
|
||||
} else {
|
||||
foreach ($redefined_vars as $var => $type) {
|
||||
if ($type->hasMixed()) {
|
||||
$loop_scope->possibly_redefined_loop_parent_vars[$var] = $type;
|
||||
} elseif (isset($loop_scope->possibly_redefined_loop_parent_vars[$var])) {
|
||||
$loop_scope->possibly_redefined_loop_parent_vars[$var] = Type::combineUnionTypes(
|
||||
$type,
|
||||
$loop_scope->possibly_redefined_loop_parent_vars[$var]
|
||||
);
|
||||
} else {
|
||||
$loop_scope->possibly_redefined_loop_parent_vars[$var] = $type;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($loop_scope->iteration_count === 0) {
|
||||
foreach ($context->vars_in_scope as $var_id => $type) {
|
||||
if (!isset($loop_scope->loop_parent_context->vars_in_scope[$var_id])) {
|
||||
if (isset($loop_scope->possibly_defined_loop_parent_vars[$var_id])) {
|
||||
$loop_scope->possibly_defined_loop_parent_vars[$var_id] = Type::combineUnionTypes(
|
||||
$type,
|
||||
$loop_scope->possibly_defined_loop_parent_vars[$var_id]
|
||||
);
|
||||
} else {
|
||||
$loop_scope->possibly_defined_loop_parent_vars[$var_id] = $type;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($codebase->find_unused_variables && !$leaving_switch) {
|
||||
foreach ($context->unreferenced_vars as $var_id => $locations) {
|
||||
if (isset($loop_scope->unreferenced_vars[$var_id])) {
|
||||
$loop_scope->unreferenced_vars[$var_id] += $locations;
|
||||
} else {
|
||||
$loop_scope->unreferenced_vars[$var_id] = $locations;
|
||||
}
|
||||
}
|
||||
|
||||
$loop_scope->referenced_var_ids += $context->referenced_var_ids;
|
||||
}
|
||||
}
|
||||
|
||||
$case_scope = $context->case_scope;
|
||||
if ($case_scope && $leaving_switch) {
|
||||
foreach ($context->vars_in_scope as $var_id => $type) {
|
||||
if ($case_scope->parent_context !== $context) {
|
||||
if ($case_scope->break_vars === null) {
|
||||
$case_scope->break_vars = [];
|
||||
}
|
||||
|
||||
if (isset($case_scope->break_vars[$var_id])) {
|
||||
$case_scope->break_vars[$var_id] = Type::combineUnionTypes(
|
||||
$type,
|
||||
$case_scope->break_vars[$var_id]
|
||||
);
|
||||
} else {
|
||||
$case_scope->break_vars[$var_id] = $type;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($codebase->find_unused_variables) {
|
||||
foreach ($context->unreferenced_vars as $var_id => $locations) {
|
||||
if (isset($case_scope->unreferenced_vars[$var_id])) {
|
||||
$case_scope->unreferenced_vars[$var_id] += $locations;
|
||||
} else {
|
||||
$case_scope->unreferenced_vars[$var_id] = $locations;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$context->has_returned = true;
|
||||
}
|
||||
}
|
117
src/Psalm/Internal/Analyzer/Statements/ContinueAnalyzer.php
Normal file
117
src/Psalm/Internal/Analyzer/Statements/ContinueAnalyzer.php
Normal file
@ -0,0 +1,117 @@
|
||||
<?php
|
||||
namespace Psalm\Internal\Analyzer\Statements;
|
||||
|
||||
use PhpParser;
|
||||
use Psalm\Internal\Analyzer\ScopeAnalyzer;
|
||||
use Psalm\Internal\Analyzer\StatementsAnalyzer;
|
||||
use Psalm\CodeLocation;
|
||||
use Psalm\Context;
|
||||
use Psalm\Issue\ContinueOutsideLoop;
|
||||
use Psalm\IssueBuffer;
|
||||
use Psalm\Type;
|
||||
|
||||
class ContinueAnalyzer
|
||||
{
|
||||
/**
|
||||
* @return false|null
|
||||
*/
|
||||
public static function analyze(
|
||||
StatementsAnalyzer $statements_analyzer,
|
||||
PhpParser\Node\Stmt\Continue_ $stmt,
|
||||
Context $context
|
||||
) {
|
||||
$loop_scope = $context->loop_scope;
|
||||
|
||||
$leaving_switch = true;
|
||||
|
||||
$codebase = $statements_analyzer->getCodebase();
|
||||
|
||||
if ($loop_scope === null) {
|
||||
if (!$context->break_types) {
|
||||
if (IssueBuffer::accepts(
|
||||
new ContinueOutsideLoop(
|
||||
'Continue call outside loop context',
|
||||
new CodeLocation($statements_analyzer, $stmt)
|
||||
),
|
||||
$statements_analyzer->getSource()->getSuppressedIssues()
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ($context->break_types
|
||||
&& \end($context->break_types) === 'switch'
|
||||
&& (!$stmt->num
|
||||
|| !$stmt->num instanceof PhpParser\Node\Scalar\LNumber
|
||||
|| $stmt->num->value < 2
|
||||
)
|
||||
) {
|
||||
$loop_scope->final_actions[] = ScopeAnalyzer::ACTION_LEAVE_SWITCH;
|
||||
} else {
|
||||
$leaving_switch = false;
|
||||
$loop_scope->final_actions[] = ScopeAnalyzer::ACTION_CONTINUE;
|
||||
}
|
||||
|
||||
$redefined_vars = $context->getRedefinedVars($loop_scope->loop_parent_context->vars_in_scope);
|
||||
|
||||
if ($loop_scope->redefined_loop_vars === null) {
|
||||
$loop_scope->redefined_loop_vars = $redefined_vars;
|
||||
} else {
|
||||
foreach ($loop_scope->redefined_loop_vars as $redefined_var => $type) {
|
||||
if (!isset($redefined_vars[$redefined_var])) {
|
||||
unset($loop_scope->redefined_loop_vars[$redefined_var]);
|
||||
} else {
|
||||
$loop_scope->redefined_loop_vars[$redefined_var] = Type::combineUnionTypes(
|
||||
$redefined_vars[$redefined_var],
|
||||
$type
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($redefined_vars as $var => $type) {
|
||||
if ($type->hasMixed()) {
|
||||
$loop_scope->possibly_redefined_loop_vars[$var] = $type;
|
||||
} elseif (isset($loop_scope->possibly_redefined_loop_vars[$var])) {
|
||||
$loop_scope->possibly_redefined_loop_vars[$var] = Type::combineUnionTypes(
|
||||
$type,
|
||||
$loop_scope->possibly_redefined_loop_vars[$var]
|
||||
);
|
||||
} else {
|
||||
$loop_scope->possibly_redefined_loop_vars[$var] = $type;
|
||||
}
|
||||
}
|
||||
|
||||
if ($codebase->find_unused_variables && (!$context->case_scope || $stmt->num)) {
|
||||
foreach ($context->unreferenced_vars as $var_id => $locations) {
|
||||
if (isset($loop_scope->unreferenced_vars[$var_id])) {
|
||||
$loop_scope->unreferenced_vars[$var_id] += $locations;
|
||||
} else {
|
||||
$loop_scope->unreferenced_vars[$var_id] = $locations;
|
||||
}
|
||||
|
||||
if (isset($loop_scope->possibly_unreferenced_vars[$var_id])) {
|
||||
$loop_scope->possibly_unreferenced_vars[$var_id] += $locations;
|
||||
} else {
|
||||
$loop_scope->possibly_unreferenced_vars[$var_id] = $locations;
|
||||
}
|
||||
}
|
||||
|
||||
$loop_scope->referenced_var_ids += $context->referenced_var_ids;
|
||||
}
|
||||
}
|
||||
|
||||
$case_scope = $context->case_scope;
|
||||
if ($case_scope && $codebase->find_unused_variables && $leaving_switch) {
|
||||
foreach ($context->unreferenced_vars as $var_id => $locations) {
|
||||
if (isset($case_scope->unreferenced_vars[$var_id])) {
|
||||
$case_scope->unreferenced_vars[$var_id] += $locations;
|
||||
} else {
|
||||
$case_scope->unreferenced_vars[$var_id] = $locations;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$context->has_returned = true;
|
||||
}
|
||||
}
|
103
src/Psalm/Internal/Analyzer/Statements/EchoAnalyzer.php
Normal file
103
src/Psalm/Internal/Analyzer/Statements/EchoAnalyzer.php
Normal file
@ -0,0 +1,103 @@
|
||||
<?php
|
||||
namespace Psalm\Internal\Analyzer\Statements;
|
||||
|
||||
use PhpParser;
|
||||
use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\Expression\Call\ArgumentAnalyzer;
|
||||
use Psalm\Internal\Analyzer\StatementsAnalyzer;
|
||||
use Psalm\CodeLocation;
|
||||
use Psalm\Context;
|
||||
use Psalm\Issue\ForbiddenCode;
|
||||
use Psalm\Issue\ForbiddenEcho;
|
||||
use Psalm\Issue\ImpureFunctionCall;
|
||||
use Psalm\IssueBuffer;
|
||||
use Psalm\Storage\FunctionLikeParameter;
|
||||
use Psalm\Type;
|
||||
|
||||
class EchoAnalyzer
|
||||
{
|
||||
public static function analyze(
|
||||
StatementsAnalyzer $statements_analyzer,
|
||||
PhpParser\Node\Stmt\Echo_ $stmt,
|
||||
Context $context
|
||||
) : bool {
|
||||
$echo_param = new FunctionLikeParameter(
|
||||
'var',
|
||||
false
|
||||
);
|
||||
|
||||
$echo_param->sink = Type\Union::TAINTED_INPUT_HTML
|
||||
| Type\Union::TAINTED_USER_SECRET
|
||||
| Type\Union::TAINTED_SYSTEM_SECRET;
|
||||
|
||||
foreach ($stmt->exprs as $i => $expr) {
|
||||
$context->inside_call = true;
|
||||
ExpressionAnalyzer::analyze($statements_analyzer, $expr, $context);
|
||||
$context->inside_call = false;
|
||||
|
||||
if ($expr_type = $statements_analyzer->node_data->getType($expr)) {
|
||||
if (ArgumentAnalyzer::verifyType(
|
||||
$statements_analyzer,
|
||||
$expr_type,
|
||||
Type::getString(),
|
||||
null,
|
||||
'echo',
|
||||
(int)$i,
|
||||
new CodeLocation($statements_analyzer->getSource(), $expr),
|
||||
$expr,
|
||||
$context,
|
||||
$echo_param,
|
||||
false,
|
||||
null,
|
||||
false,
|
||||
true,
|
||||
new CodeLocation($statements_analyzer, $stmt)
|
||||
) === false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$codebase = $statements_analyzer->getCodebase();
|
||||
|
||||
if ($codebase->config->forbid_echo) {
|
||||
if (IssueBuffer::accepts(
|
||||
new ForbiddenEcho(
|
||||
'Use of echo',
|
||||
new CodeLocation($statements_analyzer, $stmt)
|
||||
),
|
||||
$statements_analyzer->getSource()->getSuppressedIssues()
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
} elseif (isset($codebase->config->forbidden_functions['echo'])) {
|
||||
if (IssueBuffer::accepts(
|
||||
new ForbiddenCode(
|
||||
'Use of echo',
|
||||
new CodeLocation($statements_analyzer, $stmt)
|
||||
),
|
||||
$statements_analyzer->getSource()->getSuppressedIssues()
|
||||
)) {
|
||||
// continue
|
||||
}
|
||||
}
|
||||
|
||||
if (!$context->collect_initializations
|
||||
&& !$context->collect_mutations
|
||||
&& ($context->mutation_free
|
||||
|| $context->external_mutation_free)
|
||||
) {
|
||||
if (IssueBuffer::accepts(
|
||||
new ImpureFunctionCall(
|
||||
'Cannot call echo from a mutation-free context',
|
||||
new CodeLocation($statements_analyzer, $stmt)
|
||||
),
|
||||
$statements_analyzer->getSuppressedIssues()
|
||||
)) {
|
||||
// fall through
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@ -1029,6 +1029,31 @@ class PropertyAssignmentAnalyzer
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function analyzeStatement(
|
||||
StatementsAnalyzer $statements_analyzer,
|
||||
PhpParser\Node\Stmt\Property $stmt,
|
||||
Context $context
|
||||
): void {
|
||||
foreach ($stmt->props as $prop) {
|
||||
if ($prop->default) {
|
||||
ExpressionAnalyzer::analyze($statements_analyzer, $prop->default, $context);
|
||||
|
||||
if ($prop_default_type = $statements_analyzer->node_data->getType($prop->default)) {
|
||||
if (self::analyzeInstance(
|
||||
$statements_analyzer,
|
||||
$prop,
|
||||
$prop->name->name,
|
||||
$prop->default,
|
||||
$prop_default_type,
|
||||
$context
|
||||
) === false) {
|
||||
// fall through
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static function taintProperty(
|
||||
StatementsAnalyzer $statements_analyzer,
|
||||
PhpParser\Node\Expr\PropertyFetch $stmt,
|
||||
|
@ -7,6 +7,7 @@ use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\Expression\CallAnalyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\Expression\AssertionFinder;
|
||||
use Psalm\Internal\Analyzer\Statements\Expression\ExpressionIdentifier;
|
||||
use Psalm\Internal\Analyzer\Statements\Expression\Fetch\ConstFetchAnalyzer;
|
||||
use Psalm\Internal\Analyzer\StatementsAnalyzer;
|
||||
use Psalm\Internal\Analyzer\TypeAnalyzer;
|
||||
use Psalm\Internal\Codebase\CallMap;
|
||||
@ -1288,7 +1289,7 @@ class FunctionCallAnalyzer extends CallAnalyzer
|
||||
}
|
||||
} elseif ($function_name->parts === ['define']) {
|
||||
if ($first_arg) {
|
||||
$fq_const_name = StatementsAnalyzer::getConstName(
|
||||
$fq_const_name = ConstFetchAnalyzer::getConstName(
|
||||
$first_arg->value,
|
||||
$statements_analyzer->node_data,
|
||||
$codebase,
|
||||
@ -1302,7 +1303,8 @@ class FunctionCallAnalyzer extends CallAnalyzer
|
||||
ExpressionAnalyzer::analyze($statements_analyzer, $second_arg->value, $context);
|
||||
$context->inside_call = $was_in_call;
|
||||
|
||||
$statements_analyzer->setConstType(
|
||||
ConstFetchAnalyzer::setConstType(
|
||||
$statements_analyzer,
|
||||
$fq_const_name,
|
||||
$statements_analyzer->node_data->getType($second_arg->value) ?: Type::getMixed(),
|
||||
$context
|
||||
@ -1313,7 +1315,7 @@ class FunctionCallAnalyzer extends CallAnalyzer
|
||||
}
|
||||
} elseif ($function_name->parts === ['constant']) {
|
||||
if ($first_arg) {
|
||||
$fq_const_name = StatementsAnalyzer::getConstName(
|
||||
$fq_const_name = ConstFetchAnalyzer::getConstName(
|
||||
$first_arg->value,
|
||||
$statements_analyzer->node_data,
|
||||
$codebase,
|
||||
@ -1321,7 +1323,8 @@ class FunctionCallAnalyzer extends CallAnalyzer
|
||||
);
|
||||
|
||||
if ($fq_const_name !== null) {
|
||||
$const_type = $statements_analyzer->getConstType(
|
||||
$const_type = ConstFetchAnalyzer::getConstType(
|
||||
$statements_analyzer,
|
||||
$fq_const_name,
|
||||
true,
|
||||
$context
|
||||
|
@ -386,4 +386,37 @@ class ClassConstFetchAnalyzer
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function analyzeClassConstAssignment(
|
||||
StatementsAnalyzer $statements_analyzer,
|
||||
PhpParser\Node\Stmt\ClassConst $stmt,
|
||||
Context $context
|
||||
): void {
|
||||
$const_visibility = \ReflectionProperty::IS_PUBLIC;
|
||||
|
||||
if ($stmt->isProtected()) {
|
||||
$const_visibility = \ReflectionProperty::IS_PROTECTED;
|
||||
}
|
||||
|
||||
if ($stmt->isPrivate()) {
|
||||
$const_visibility = \ReflectionProperty::IS_PRIVATE;
|
||||
}
|
||||
|
||||
$codebase = $statements_analyzer->getCodebase();
|
||||
|
||||
foreach ($stmt->consts as $const) {
|
||||
ExpressionAnalyzer::analyze($statements_analyzer, $const->value, $context);
|
||||
|
||||
if (($const_type = $statements_analyzer->node_data->getType($const->value))
|
||||
&& !$const_type->hasMixed()
|
||||
) {
|
||||
$codebase->classlikes->setConstantType(
|
||||
(string)$statements_analyzer->getFQCLN(),
|
||||
$const->name->name,
|
||||
$const_type,
|
||||
$const_visibility
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,8 +2,12 @@
|
||||
namespace Psalm\Internal\Analyzer\Statements\Expression\Fetch;
|
||||
|
||||
use PhpParser;
|
||||
use Psalm\Aliases;
|
||||
use Psalm\Internal\Analyzer\ClassLikeAnalyzer;
|
||||
use Psalm\Internal\Analyzer\NamespaceAnalyzer;
|
||||
use Psalm\Internal\Analyzer\StatementsAnalyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\Expression\SimpleTypeInferer;
|
||||
use Psalm\Codebase;
|
||||
use Psalm\CodeLocation;
|
||||
use Psalm\Context;
|
||||
@ -13,6 +17,8 @@ use Psalm\Type;
|
||||
use function array_key_exists;
|
||||
use function implode;
|
||||
use function strtolower;
|
||||
use function explode;
|
||||
use function array_pop;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
@ -52,7 +58,8 @@ class ConstFetchAnalyzer
|
||||
break;
|
||||
|
||||
default:
|
||||
$const_type = $statements_analyzer->getConstType(
|
||||
$const_type = self::getConstType(
|
||||
$statements_analyzer,
|
||||
$const_name,
|
||||
$stmt->name instanceof PhpParser\Node\Name\FullyQualified,
|
||||
$context
|
||||
@ -168,4 +175,139 @@ class ConstFetchAnalyzer
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $const_name
|
||||
* @param bool $is_fully_qualified
|
||||
* @param Context $context
|
||||
*
|
||||
* @return Type\Union|null
|
||||
*/
|
||||
public static function getConstType(
|
||||
StatementsAnalyzer $statements_analyzer,
|
||||
string $const_name,
|
||||
bool $is_fully_qualified,
|
||||
?Context $context
|
||||
) {
|
||||
$aliased_constants = $statements_analyzer->getAliases()->constants;
|
||||
|
||||
if (isset($aliased_constants[$const_name])) {
|
||||
$fq_const_name = $aliased_constants[$const_name];
|
||||
} elseif ($is_fully_qualified) {
|
||||
$fq_const_name = $const_name;
|
||||
} else {
|
||||
$fq_const_name = Type::getFQCLNFromString($const_name, $statements_analyzer->getAliases());
|
||||
}
|
||||
|
||||
if ($fq_const_name) {
|
||||
$const_name_parts = explode('\\', $fq_const_name);
|
||||
$const_name = array_pop($const_name_parts);
|
||||
$namespace_name = implode('\\', $const_name_parts);
|
||||
$namespace_constants = NamespaceAnalyzer::getConstantsForNamespace(
|
||||
$namespace_name,
|
||||
\ReflectionProperty::IS_PUBLIC
|
||||
);
|
||||
|
||||
if (isset($namespace_constants[$const_name])) {
|
||||
return $namespace_constants[$const_name];
|
||||
}
|
||||
}
|
||||
|
||||
if ($context && $context->hasVariable($fq_const_name, $statements_analyzer)) {
|
||||
return $context->vars_in_scope[$fq_const_name];
|
||||
}
|
||||
|
||||
$file_path = $statements_analyzer->getRootFilePath();
|
||||
$codebase = $statements_analyzer->getCodebase();
|
||||
|
||||
$file_storage_provider = $codebase->file_storage_provider;
|
||||
|
||||
$file_storage = $file_storage_provider->get($file_path);
|
||||
|
||||
if (isset($file_storage->declaring_constants[$const_name])) {
|
||||
$constant_file_path = $file_storage->declaring_constants[$const_name];
|
||||
|
||||
return $file_storage_provider->get($constant_file_path)->constants[$const_name];
|
||||
}
|
||||
|
||||
if (isset($file_storage->declaring_constants[$fq_const_name])) {
|
||||
$constant_file_path = $file_storage->declaring_constants[$fq_const_name];
|
||||
|
||||
return $file_storage_provider->get($constant_file_path)->constants[$fq_const_name];
|
||||
}
|
||||
|
||||
return ConstFetchAnalyzer::getGlobalConstType($codebase, $fq_const_name, $const_name)
|
||||
?? ConstFetchAnalyzer::getGlobalConstType($codebase, $const_name, $const_name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $const_name
|
||||
* @param Type\Union $const_type
|
||||
* @param Context $context
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function setConstType(
|
||||
StatementsAnalyzer $statements_analyzer,
|
||||
string $const_name,
|
||||
Type\Union $const_type,
|
||||
Context $context
|
||||
) {
|
||||
$context->vars_in_scope[$const_name] = $const_type;
|
||||
$context->constants[$const_name] = $const_type;
|
||||
|
||||
$source = $statements_analyzer->getSource();
|
||||
|
||||
if ($source instanceof NamespaceAnalyzer) {
|
||||
$source->setConstType($const_name, $const_type);
|
||||
}
|
||||
}
|
||||
|
||||
public static function getConstName(
|
||||
PhpParser\Node\Expr $first_arg_value,
|
||||
\Psalm\Internal\Provider\NodeDataProvider $type_provider,
|
||||
Codebase $codebase,
|
||||
Aliases $aliases
|
||||
) : ?string {
|
||||
$const_name = null;
|
||||
|
||||
if ($first_arg_value instanceof PhpParser\Node\Scalar\String_) {
|
||||
$const_name = $first_arg_value->value;
|
||||
} elseif ($first_arg_type = $type_provider->getType($first_arg_value)) {
|
||||
if ($first_arg_type->isSingleStringLiteral()) {
|
||||
$const_name = $first_arg_type->getSingleStringLiteral()->value;
|
||||
}
|
||||
} else {
|
||||
$simple_type = SimpleTypeInferer::infer($codebase, $type_provider, $first_arg_value, $aliases);
|
||||
|
||||
if ($simple_type && $simple_type->isSingleStringLiteral()) {
|
||||
$const_name = $simple_type->getSingleStringLiteral()->value;
|
||||
}
|
||||
}
|
||||
|
||||
return $const_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param PhpParser\Node\Stmt\Const_ $stmt
|
||||
* @param Context $context
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function analyzeConstAssignment(
|
||||
StatementsAnalyzer $statements_analyzer,
|
||||
PhpParser\Node\Stmt\Const_ $stmt,
|
||||
Context $context
|
||||
) {
|
||||
foreach ($stmt->consts as $const) {
|
||||
ExpressionAnalyzer::analyze($statements_analyzer, $const->value, $context);
|
||||
|
||||
self::setConstType(
|
||||
$statements_analyzer,
|
||||
$const->name->name,
|
||||
$statements_analyzer->node_data->getType($const->value) ?: Type::getMixed(),
|
||||
$context
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,12 +16,26 @@ use Psalm\Issue\UndefinedVariable;
|
||||
use Psalm\IssueBuffer;
|
||||
use Psalm\Type;
|
||||
use function is_string;
|
||||
use function in_array;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class VariableFetchAnalyzer
|
||||
{
|
||||
const SUPER_GLOBALS = [
|
||||
'$GLOBALS',
|
||||
'$_SERVER',
|
||||
'$_GET',
|
||||
'$_POST',
|
||||
'$_FILES',
|
||||
'$_COOKIE',
|
||||
'$_SESSION',
|
||||
'$_REQUEST',
|
||||
'$_ENV',
|
||||
'$http_response_header',
|
||||
];
|
||||
|
||||
/**
|
||||
* @param StatementsAnalyzer $statements_analyzer
|
||||
* @param PhpParser\Node\Expr\Variable $stmt
|
||||
@ -110,7 +124,7 @@ class VariableFetchAnalyzer
|
||||
return true;
|
||||
}
|
||||
|
||||
if (is_string($stmt->name) && $statements_analyzer->isSuperGlobal('$' . $stmt->name)) {
|
||||
if (is_string($stmt->name) && self::isSuperGlobal('$' . $stmt->name)) {
|
||||
$var_name = '$' . $stmt->name;
|
||||
|
||||
if (isset($context->vars_in_scope[$var_name])) {
|
||||
@ -119,7 +133,7 @@ class VariableFetchAnalyzer
|
||||
return true;
|
||||
}
|
||||
|
||||
$type = $statements_analyzer->getGlobalType($var_name);
|
||||
$type = self::getGlobalType($var_name);
|
||||
|
||||
$statements_analyzer->node_data->setType($stmt, $type);
|
||||
$context->vars_in_scope[$var_name] = clone $type;
|
||||
@ -330,4 +344,40 @@ class VariableFetchAnalyzer
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function isSuperGlobal(string $var_id) : bool
|
||||
{
|
||||
return in_array(
|
||||
$var_id,
|
||||
self::SUPER_GLOBALS,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
public static function getGlobalType(string $var_id) : Type\Union
|
||||
{
|
||||
$config = \Psalm\Config::getInstance();
|
||||
|
||||
if (isset($config->globals[$var_id])) {
|
||||
return Type::parseString($config->globals[$var_id]);
|
||||
}
|
||||
|
||||
if ($var_id === '$argv') {
|
||||
return new Type\Union([
|
||||
new Type\Atomic\TArray([Type::getInt(), Type::getString()]),
|
||||
]);
|
||||
}
|
||||
|
||||
if ($var_id === '$argc') {
|
||||
return Type::getInt();
|
||||
}
|
||||
|
||||
if (self::isSuperGlobal($var_id)) {
|
||||
$type = Type::getArray();
|
||||
|
||||
return $type;
|
||||
}
|
||||
|
||||
return Type::getMixed();
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,498 @@
|
||||
<?php
|
||||
namespace Psalm\Internal\Analyzer\Statements\Expression;
|
||||
|
||||
use PhpParser;
|
||||
use Psalm\Internal\Analyzer\ClassLikeAnalyzer;
|
||||
use Psalm\Internal\Analyzer\StatementsAnalyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\Expression\BinaryOp\NonDivArithmeticOpAnalyzer;
|
||||
use Psalm\StatementsSource;
|
||||
use Psalm\Type;
|
||||
use function strtolower;
|
||||
use function count;
|
||||
use function array_shift;
|
||||
|
||||
class SimpleTypeInferer
|
||||
{
|
||||
/**
|
||||
* @param PhpParser\Node\Expr $stmt
|
||||
* @param ?array<string, Type\Union> $existing_class_constants
|
||||
* @param string $fq_classlike_name
|
||||
*
|
||||
* @return Type\Union|null
|
||||
*/
|
||||
public static function infer(
|
||||
\Psalm\Codebase $codebase,
|
||||
\Psalm\Internal\Provider\NodeDataProvider $nodes,
|
||||
PhpParser\Node\Expr $stmt,
|
||||
\Psalm\Aliases $aliases,
|
||||
\Psalm\FileSource $file_source = null,
|
||||
array $existing_class_constants = null,
|
||||
$fq_classlike_name = null
|
||||
) {
|
||||
if ($stmt instanceof PhpParser\Node\Expr\BinaryOp) {
|
||||
if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\Concat) {
|
||||
$left = self::infer(
|
||||
$codebase,
|
||||
$nodes,
|
||||
$stmt->left,
|
||||
$aliases,
|
||||
$file_source,
|
||||
$existing_class_constants,
|
||||
$fq_classlike_name
|
||||
);
|
||||
$right = self::infer(
|
||||
$codebase,
|
||||
$nodes,
|
||||
$stmt->right,
|
||||
$aliases,
|
||||
$file_source,
|
||||
$existing_class_constants,
|
||||
$fq_classlike_name
|
||||
);
|
||||
|
||||
if ($left
|
||||
&& $right
|
||||
&& $left->isSingleStringLiteral()
|
||||
&& $right->isSingleStringLiteral()
|
||||
) {
|
||||
$result = $left->getSingleStringLiteral()->value . $right->getSingleStringLiteral()->value;
|
||||
|
||||
return Type::getString($result);
|
||||
}
|
||||
|
||||
return Type::getString();
|
||||
}
|
||||
|
||||
if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\BooleanAnd
|
||||
|| $stmt instanceof PhpParser\Node\Expr\BinaryOp\BooleanOr
|
||||
|| $stmt instanceof PhpParser\Node\Expr\BinaryOp\LogicalAnd
|
||||
|| $stmt instanceof PhpParser\Node\Expr\BinaryOp\LogicalOr
|
||||
|| $stmt instanceof PhpParser\Node\Expr\BinaryOp\Equal
|
||||
|| $stmt instanceof PhpParser\Node\Expr\BinaryOp\NotEqual
|
||||
|| $stmt instanceof PhpParser\Node\Expr\BinaryOp\Identical
|
||||
|| $stmt instanceof PhpParser\Node\Expr\BinaryOp\NotIdentical
|
||||
|| $stmt instanceof PhpParser\Node\Expr\BinaryOp\Greater
|
||||
|| $stmt instanceof PhpParser\Node\Expr\BinaryOp\GreaterOrEqual
|
||||
|| $stmt instanceof PhpParser\Node\Expr\BinaryOp\Smaller
|
||||
|| $stmt instanceof PhpParser\Node\Expr\BinaryOp\SmallerOrEqual
|
||||
) {
|
||||
return Type::getBool();
|
||||
}
|
||||
|
||||
if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\Coalesce) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\Spaceship) {
|
||||
return Type::getInt();
|
||||
}
|
||||
|
||||
$stmt_left_type = self::infer(
|
||||
$codebase,
|
||||
$nodes,
|
||||
$stmt->left,
|
||||
$aliases,
|
||||
$file_source,
|
||||
$existing_class_constants,
|
||||
$fq_classlike_name
|
||||
);
|
||||
|
||||
$stmt_right_type = self::infer(
|
||||
$codebase,
|
||||
$nodes,
|
||||
$stmt->right,
|
||||
$aliases,
|
||||
$file_source,
|
||||
$existing_class_constants,
|
||||
$fq_classlike_name
|
||||
);
|
||||
|
||||
if (!$stmt_left_type || !$stmt_right_type) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$nodes->setType(
|
||||
$stmt->left,
|
||||
$stmt_left_type
|
||||
);
|
||||
|
||||
$nodes->setType(
|
||||
$stmt->right,
|
||||
$stmt_right_type
|
||||
);
|
||||
|
||||
if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\Plus
|
||||
|| $stmt instanceof PhpParser\Node\Expr\BinaryOp\Minus
|
||||
|| $stmt instanceof PhpParser\Node\Expr\BinaryOp\Mod
|
||||
|| $stmt instanceof PhpParser\Node\Expr\BinaryOp\Mul
|
||||
|| $stmt instanceof PhpParser\Node\Expr\BinaryOp\Pow
|
||||
) {
|
||||
NonDivArithmeticOpAnalyzer::analyze(
|
||||
$file_source instanceof StatementsSource ? $file_source : null,
|
||||
$nodes,
|
||||
$stmt->left,
|
||||
$stmt->right,
|
||||
$stmt,
|
||||
$result_type
|
||||
);
|
||||
|
||||
if ($result_type) {
|
||||
return $result_type;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\Div
|
||||
&& ($stmt_left_type->hasInt() || $stmt_left_type->hasFloat())
|
||||
&& ($stmt_right_type->hasInt() || $stmt_right_type->hasFloat())
|
||||
) {
|
||||
return Type::combineUnionTypes(Type::getFloat(), Type::getInt());
|
||||
}
|
||||
}
|
||||
|
||||
if ($stmt instanceof PhpParser\Node\Expr\ConstFetch) {
|
||||
if (strtolower($stmt->name->parts[0]) === 'false') {
|
||||
return Type::getFalse();
|
||||
} elseif (strtolower($stmt->name->parts[0]) === 'true') {
|
||||
return Type::getTrue();
|
||||
} elseif (strtolower($stmt->name->parts[0]) === 'null') {
|
||||
return Type::getNull();
|
||||
} elseif ($stmt->name->parts[0] === '__NAMESPACE__') {
|
||||
return Type::getString($aliases->namespace);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($stmt instanceof PhpParser\Node\Scalar\MagicConst\Namespace_) {
|
||||
return Type::getString($aliases->namespace);
|
||||
}
|
||||
|
||||
if ($stmt instanceof PhpParser\Node\Expr\ClassConstFetch) {
|
||||
if ($stmt->class instanceof PhpParser\Node\Name
|
||||
&& $stmt->name instanceof PhpParser\Node\Identifier
|
||||
&& $fq_classlike_name
|
||||
&& $stmt->class->parts !== ['static']
|
||||
&& $stmt->class->parts !== ['parent']
|
||||
) {
|
||||
if (isset($existing_class_constants[$stmt->name->name])) {
|
||||
if ($stmt->class->parts === ['self']) {
|
||||
return clone $existing_class_constants[$stmt->name->name];
|
||||
}
|
||||
}
|
||||
|
||||
if ($stmt->class->parts === ['self']) {
|
||||
$const_fq_class_name = $fq_classlike_name;
|
||||
} else {
|
||||
$const_fq_class_name = ClassLikeAnalyzer::getFQCLNFromNameObject(
|
||||
$stmt->class,
|
||||
$aliases
|
||||
);
|
||||
}
|
||||
|
||||
if (strtolower($const_fq_class_name) === strtolower($fq_classlike_name)
|
||||
&& isset($existing_class_constants[$stmt->name->name])
|
||||
) {
|
||||
return clone $existing_class_constants[$stmt->name->name];
|
||||
}
|
||||
|
||||
if (strtolower($stmt->name->name) === 'class') {
|
||||
return Type::getLiteralClassString($const_fq_class_name);
|
||||
}
|
||||
|
||||
if ($existing_class_constants === null
|
||||
&& $file_source instanceof StatementsAnalyzer
|
||||
) {
|
||||
try {
|
||||
$foreign_class_constant = $codebase->classlikes->getConstantForClass(
|
||||
$const_fq_class_name,
|
||||
$stmt->name->name,
|
||||
\ReflectionProperty::IS_PRIVATE,
|
||||
$file_source
|
||||
);
|
||||
|
||||
if ($foreign_class_constant) {
|
||||
return clone $foreign_class_constant;
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
return null;
|
||||
} catch (\Psalm\Exception\CircularReferenceException $e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($stmt->name instanceof PhpParser\Node\Identifier && strtolower($stmt->name->name) === 'class') {
|
||||
return Type::getClassString();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($stmt instanceof PhpParser\Node\Scalar\String_) {
|
||||
return Type::getString($stmt->value);
|
||||
}
|
||||
|
||||
if ($stmt instanceof PhpParser\Node\Scalar\LNumber) {
|
||||
return Type::getInt(false, $stmt->value);
|
||||
}
|
||||
|
||||
if ($stmt instanceof PhpParser\Node\Scalar\DNumber) {
|
||||
return Type::getFloat($stmt->value);
|
||||
}
|
||||
|
||||
if ($stmt instanceof PhpParser\Node\Expr\Array_) {
|
||||
if (count($stmt->items) === 0) {
|
||||
return Type::getEmptyArray();
|
||||
}
|
||||
|
||||
$item_key_type = null;
|
||||
$item_value_type = null;
|
||||
|
||||
$property_types = [];
|
||||
$class_strings = [];
|
||||
|
||||
$can_create_objectlike = true;
|
||||
|
||||
$is_list = true;
|
||||
|
||||
foreach ($stmt->items as $int_offset => $item) {
|
||||
if ($item === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$single_item_key_type = null;
|
||||
|
||||
if ($item->key) {
|
||||
$single_item_key_type = self::infer(
|
||||
$codebase,
|
||||
$nodes,
|
||||
$item->key,
|
||||
$aliases,
|
||||
$file_source,
|
||||
$existing_class_constants,
|
||||
$fq_classlike_name
|
||||
);
|
||||
|
||||
if ($single_item_key_type) {
|
||||
if ($item_key_type) {
|
||||
$item_key_type = Type::combineUnionTypes(
|
||||
$single_item_key_type,
|
||||
$item_key_type,
|
||||
null,
|
||||
false,
|
||||
true,
|
||||
30
|
||||
);
|
||||
} else {
|
||||
$item_key_type = $single_item_key_type;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$item_key_type = Type::getInt();
|
||||
}
|
||||
|
||||
$single_item_value_type = self::infer(
|
||||
$codebase,
|
||||
$nodes,
|
||||
$item->value,
|
||||
$aliases,
|
||||
$file_source,
|
||||
$existing_class_constants,
|
||||
$fq_classlike_name
|
||||
);
|
||||
|
||||
if (!$single_item_value_type) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($item->key instanceof PhpParser\Node\Scalar\String_
|
||||
|| $item->key instanceof PhpParser\Node\Scalar\LNumber
|
||||
|| !$item->key
|
||||
) {
|
||||
if (count($property_types) <= 50) {
|
||||
$property_types[$item->key ? $item->key->value : $int_offset] = $single_item_value_type;
|
||||
} else {
|
||||
$can_create_objectlike = false;
|
||||
}
|
||||
|
||||
if ($item->key
|
||||
&& (!$item->key instanceof PhpParser\Node\Scalar\LNumber
|
||||
|| $item->key->value !== $int_offset)
|
||||
) {
|
||||
$is_list = false;
|
||||
}
|
||||
} else {
|
||||
$is_list = false;
|
||||
$dim_type = $single_item_key_type;
|
||||
|
||||
if (!$dim_type) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$dim_atomic_types = $dim_type->getAtomicTypes();
|
||||
|
||||
if (count($dim_atomic_types) > 1 || $dim_type->hasMixed() || count($property_types) > 50) {
|
||||
$can_create_objectlike = false;
|
||||
} else {
|
||||
$atomic_type = array_shift($dim_atomic_types);
|
||||
|
||||
if ($atomic_type instanceof Type\Atomic\TLiteralInt
|
||||
|| $atomic_type instanceof Type\Atomic\TLiteralString
|
||||
) {
|
||||
if ($atomic_type instanceof Type\Atomic\TLiteralClassString) {
|
||||
$class_strings[$atomic_type->value] = true;
|
||||
}
|
||||
|
||||
$property_types[$atomic_type->value] = $single_item_value_type;
|
||||
} else {
|
||||
$can_create_objectlike = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($item_value_type) {
|
||||
$item_value_type = Type::combineUnionTypes(
|
||||
$single_item_value_type,
|
||||
$item_value_type,
|
||||
null,
|
||||
false,
|
||||
true,
|
||||
30
|
||||
);
|
||||
} else {
|
||||
$item_value_type = $single_item_value_type;
|
||||
}
|
||||
}
|
||||
|
||||
// if this array looks like an object-like array, let's return that instead
|
||||
if ($item_value_type
|
||||
&& $item_key_type
|
||||
&& ($item_key_type->hasString() || $item_key_type->hasInt())
|
||||
&& $can_create_objectlike
|
||||
&& $property_types
|
||||
) {
|
||||
$objectlike = new Type\Atomic\ObjectLike($property_types, $class_strings);
|
||||
$objectlike->sealed = true;
|
||||
$objectlike->is_list = $is_list;
|
||||
return new Type\Union([$objectlike]);
|
||||
}
|
||||
|
||||
if (!$item_key_type || !$item_value_type) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Type\Union([
|
||||
new Type\Atomic\TNonEmptyArray([
|
||||
$item_key_type,
|
||||
$item_value_type,
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
if ($stmt instanceof PhpParser\Node\Expr\Cast\Int_) {
|
||||
return Type::getInt();
|
||||
}
|
||||
|
||||
if ($stmt instanceof PhpParser\Node\Expr\Cast\Double) {
|
||||
return Type::getFloat();
|
||||
}
|
||||
|
||||
if ($stmt instanceof PhpParser\Node\Expr\Cast\Bool_) {
|
||||
return Type::getBool();
|
||||
}
|
||||
|
||||
if ($stmt instanceof PhpParser\Node\Expr\Cast\String_) {
|
||||
return Type::getString();
|
||||
}
|
||||
|
||||
if ($stmt instanceof PhpParser\Node\Expr\Cast\Object_) {
|
||||
return Type::getObject();
|
||||
}
|
||||
|
||||
if ($stmt instanceof PhpParser\Node\Expr\Cast\Array_) {
|
||||
return Type::getArray();
|
||||
}
|
||||
|
||||
if ($stmt instanceof PhpParser\Node\Expr\UnaryMinus || $stmt instanceof PhpParser\Node\Expr\UnaryPlus) {
|
||||
$type_to_invert = self::infer(
|
||||
$codebase,
|
||||
$nodes,
|
||||
$stmt->expr,
|
||||
$aliases,
|
||||
$file_source,
|
||||
$existing_class_constants,
|
||||
$fq_classlike_name
|
||||
);
|
||||
|
||||
if (!$type_to_invert) {
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach ($type_to_invert->getAtomicTypes() as $type_part) {
|
||||
if ($type_part instanceof Type\Atomic\TLiteralInt
|
||||
&& $stmt instanceof PhpParser\Node\Expr\UnaryMinus
|
||||
) {
|
||||
$type_part->value = -$type_part->value;
|
||||
} elseif ($type_part instanceof Type\Atomic\TLiteralFloat
|
||||
&& $stmt instanceof PhpParser\Node\Expr\UnaryMinus
|
||||
) {
|
||||
$type_part->value = -$type_part->value;
|
||||
}
|
||||
}
|
||||
|
||||
return $type_to_invert;
|
||||
}
|
||||
|
||||
if ($stmt instanceof PhpParser\Node\Expr\ArrayDimFetch) {
|
||||
if ($stmt->var instanceof PhpParser\Node\Expr\ClassConstFetch
|
||||
&& $stmt->dim
|
||||
) {
|
||||
$array_type = self::infer(
|
||||
$codebase,
|
||||
$nodes,
|
||||
$stmt->var,
|
||||
$aliases,
|
||||
$file_source,
|
||||
$existing_class_constants,
|
||||
$fq_classlike_name
|
||||
);
|
||||
|
||||
$dim_type = self::infer(
|
||||
$codebase,
|
||||
$nodes,
|
||||
$stmt->dim,
|
||||
$aliases,
|
||||
$file_source,
|
||||
$existing_class_constants,
|
||||
$fq_classlike_name
|
||||
);
|
||||
|
||||
if ($array_type !== null && $dim_type !== null) {
|
||||
if ($dim_type->isSingleStringLiteral()) {
|
||||
$dim_value = $dim_type->getSingleStringLiteral()->value;
|
||||
} elseif ($dim_type->isSingleIntLiteral()) {
|
||||
$dim_value = $dim_type->getSingleIntLiteral()->value;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach ($array_type->getAtomicTypes() as $array_atomic_type) {
|
||||
if ($array_atomic_type instanceof Type\Atomic\ObjectLike) {
|
||||
if (isset($array_atomic_type->properties[$dim_value])) {
|
||||
return clone $array_atomic_type->properties[$dim_value];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
63
src/Psalm/Internal/Analyzer/Statements/GlobalAnalyzer.php
Normal file
63
src/Psalm/Internal/Analyzer/Statements/GlobalAnalyzer.php
Normal file
@ -0,0 +1,63 @@
|
||||
<?php
|
||||
namespace Psalm\Internal\Analyzer\Statements;
|
||||
|
||||
use PhpParser;
|
||||
use Psalm\Internal\Analyzer\FunctionLikeAnalyzer;
|
||||
use Psalm\Internal\Analyzer\StatementsAnalyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\Expression\Fetch\VariableFetchAnalyzer;
|
||||
use Psalm\CodeLocation;
|
||||
use Psalm\Context;
|
||||
use Psalm\Issue\InvalidGlobal;
|
||||
use Psalm\IssueBuffer;
|
||||
use function is_string;
|
||||
|
||||
class GlobalAnalyzer
|
||||
{
|
||||
public static function analyze(
|
||||
StatementsAnalyzer $statements_analyzer,
|
||||
PhpParser\Node\Stmt\Global_ $stmt,
|
||||
Context $context,
|
||||
?Context $global_context
|
||||
) : void {
|
||||
if (!$context->collect_initializations && !$global_context) {
|
||||
if (IssueBuffer::accepts(
|
||||
new InvalidGlobal(
|
||||
'Cannot use global scope here',
|
||||
new CodeLocation($statements_analyzer, $stmt)
|
||||
),
|
||||
$statements_analyzer->getSource()->getSuppressedIssues()
|
||||
)) {
|
||||
// fall through
|
||||
}
|
||||
}
|
||||
|
||||
$source = $statements_analyzer->getSource();
|
||||
$function_storage = $source instanceof FunctionLikeAnalyzer
|
||||
? $source->getFunctionLikeStorage($statements_analyzer)
|
||||
: null;
|
||||
|
||||
foreach ($stmt->vars as $var) {
|
||||
if ($var instanceof PhpParser\Node\Expr\Variable) {
|
||||
if (is_string($var->name)) {
|
||||
$var_id = '$' . $var->name;
|
||||
|
||||
if ($var->name === 'argv' || $var->name === 'argc') {
|
||||
$context->vars_in_scope[$var_id] = VariableFetchAnalyzer::getGlobalType($var_id);
|
||||
} elseif (isset($function_storage->global_types[$var_id])) {
|
||||
$context->vars_in_scope[$var_id] = clone $function_storage->global_types[$var_id];
|
||||
$context->vars_possibly_in_scope[$var_id] = true;
|
||||
} else {
|
||||
$context->vars_in_scope[$var_id] =
|
||||
$global_context && $global_context->hasVariable($var_id, $statements_analyzer)
|
||||
? clone $global_context->vars_in_scope[$var_id]
|
||||
: VariableFetchAnalyzer::getGlobalType($var_id);
|
||||
|
||||
$context->vars_possibly_in_scope[$var_id] = true;
|
||||
|
||||
$context->byref_constraints[$var_id] = new \Psalm\Internal\ReferenceConstraint();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
61
src/Psalm/Internal/Analyzer/Statements/NopAnalyzer.php
Normal file
61
src/Psalm/Internal/Analyzer/Statements/NopAnalyzer.php
Normal file
@ -0,0 +1,61 @@
|
||||
<?php
|
||||
namespace Psalm\Internal\Analyzer\Statements;
|
||||
|
||||
use PhpParser;
|
||||
use Psalm\Internal\Analyzer\CommentAnalyzer;
|
||||
use Psalm\Internal\Analyzer\StatementsAnalyzer;
|
||||
use Psalm\CodeLocation;
|
||||
use Psalm\Context;
|
||||
use Psalm\Exception\DocblockParseException;
|
||||
use Psalm\Issue\InvalidDocblock;
|
||||
use Psalm\IssueBuffer;
|
||||
|
||||
class NopAnalyzer
|
||||
{
|
||||
public static function analyze(
|
||||
StatementsAnalyzer $statements_analyzer,
|
||||
PhpParser\Node\Stmt\Nop $stmt,
|
||||
Context $context
|
||||
) : void {
|
||||
if (($doc_comment = $stmt->getDocComment()) && $parsed_docblock = $statements_analyzer->getParsedDocblock()) {
|
||||
$var_comments = [];
|
||||
|
||||
try {
|
||||
$var_comments = CommentAnalyzer::arrayToDocblocks(
|
||||
$doc_comment,
|
||||
$parsed_docblock,
|
||||
$statements_analyzer->getSource(),
|
||||
$statements_analyzer->getSource()->getAliases(),
|
||||
$statements_analyzer->getSource()->getTemplateTypeMap()
|
||||
);
|
||||
} catch (DocblockParseException $e) {
|
||||
if (IssueBuffer::accepts(
|
||||
new InvalidDocblock(
|
||||
(string)$e->getMessage(),
|
||||
new CodeLocation($statements_analyzer->getSource(), $stmt, null, true)
|
||||
)
|
||||
)) {
|
||||
// fall through
|
||||
}
|
||||
}
|
||||
|
||||
$codebase = $statements_analyzer->getCodebase();
|
||||
|
||||
foreach ($var_comments as $var_comment) {
|
||||
if (!$var_comment->var_id || !$var_comment->type) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$comment_type = \Psalm\Internal\Type\TypeExpander::expandUnion(
|
||||
$codebase,
|
||||
$var_comment->type,
|
||||
$context->self,
|
||||
$context->self,
|
||||
$statements_analyzer->getParentFQCLN()
|
||||
);
|
||||
|
||||
$context->vars_in_scope[$var_comment->var_id] = $comment_type;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -14,7 +14,6 @@ use Psalm\Internal\Analyzer\TypeAnalyzer;
|
||||
use Psalm\CodeLocation;
|
||||
use Psalm\Context;
|
||||
use Psalm\Exception\DocblockParseException;
|
||||
use Psalm\Internal\Analyzer\TypeComparisonResult;
|
||||
use Psalm\Internal\Taint\Sink;
|
||||
use Psalm\Internal\Taint\Source;
|
||||
use Psalm\Internal\Type\TemplateResult;
|
||||
@ -27,8 +26,6 @@ use Psalm\Issue\MixedReturnTypeCoercion;
|
||||
use Psalm\Issue\NoValue;
|
||||
use Psalm\Issue\NullableReturnStatement;
|
||||
use Psalm\IssueBuffer;
|
||||
use Psalm\Storage\FunctionLikeParameter;
|
||||
use Psalm\Storage\FunctionLikeStorage;
|
||||
use Psalm\Type;
|
||||
use function explode;
|
||||
use function strtolower;
|
||||
|
198
src/Psalm/Internal/Analyzer/Statements/StaticAnalyzer.php
Normal file
198
src/Psalm/Internal/Analyzer/Statements/StaticAnalyzer.php
Normal file
@ -0,0 +1,198 @@
|
||||
<?php
|
||||
namespace Psalm\Internal\Analyzer\Statements;
|
||||
|
||||
use PhpParser;
|
||||
use Psalm\CodeLocation;
|
||||
use Psalm\Context;
|
||||
use Psalm\Exception\DocblockParseException;
|
||||
use Psalm\Internal\Analyzer\CommentAnalyzer;
|
||||
use Psalm\Internal\Analyzer\StatementsAnalyzer;
|
||||
use Psalm\Internal\Analyzer\TypeAnalyzer;
|
||||
use Psalm\Issue\InvalidDocblock;
|
||||
use Psalm\IssueBuffer;
|
||||
use Psalm\Type;
|
||||
use function is_string;
|
||||
|
||||
class StaticAnalyzer
|
||||
{
|
||||
/**
|
||||
* @param PhpParser\Node\Stmt\Static_ $stmt
|
||||
* @param Context $context
|
||||
*
|
||||
* @return false|null
|
||||
*/
|
||||
public static function analyze(
|
||||
StatementsAnalyzer $statements_analyzer,
|
||||
PhpParser\Node\Stmt\Static_ $stmt,
|
||||
Context $context
|
||||
) {
|
||||
$codebase = $statements_analyzer->getCodebase();
|
||||
|
||||
if ($context->mutation_free) {
|
||||
if (IssueBuffer::accepts(
|
||||
new \Psalm\Issue\ImpureStaticVariable(
|
||||
'Cannot use a static variable in a mutation-free context',
|
||||
new CodeLocation($statements_analyzer, $stmt)
|
||||
),
|
||||
$statements_analyzer->getSuppressedIssues()
|
||||
)) {
|
||||
// fall through
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($stmt->vars as $var) {
|
||||
if (!is_string($var->var->name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$var_id = '$' . $var->var->name;
|
||||
|
||||
$doc_comment = $stmt->getDocComment();
|
||||
|
||||
$comment_type = null;
|
||||
|
||||
if ($doc_comment && ($parsed_docblock = $statements_analyzer->getParsedDocblock())) {
|
||||
$var_comments = [];
|
||||
|
||||
try {
|
||||
$var_comments = CommentAnalyzer::arrayToDocblocks(
|
||||
$doc_comment,
|
||||
$parsed_docblock,
|
||||
$statements_analyzer->getSource(),
|
||||
$statements_analyzer->getSource()->getAliases(),
|
||||
$statements_analyzer->getSource()->getTemplateTypeMap()
|
||||
);
|
||||
} catch (\Psalm\Exception\IncorrectDocblockException $e) {
|
||||
if (IssueBuffer::accepts(
|
||||
new \Psalm\Issue\MissingDocblockType(
|
||||
(string)$e->getMessage(),
|
||||
new CodeLocation($statements_analyzer, $var)
|
||||
)
|
||||
)) {
|
||||
// fall through
|
||||
}
|
||||
} catch (DocblockParseException $e) {
|
||||
if (IssueBuffer::accepts(
|
||||
new InvalidDocblock(
|
||||
(string)$e->getMessage(),
|
||||
new CodeLocation($statements_analyzer->getSource(), $var)
|
||||
)
|
||||
)) {
|
||||
// fall through
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($var_comments as $var_comment) {
|
||||
if (!$var_comment->type) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$var_comment_type = \Psalm\Internal\Type\TypeExpander::expandUnion(
|
||||
$codebase,
|
||||
$var_comment->type,
|
||||
$context->self,
|
||||
$context->self,
|
||||
$statements_analyzer->getParentFQCLN()
|
||||
);
|
||||
|
||||
$var_comment_type->setFromDocblock();
|
||||
|
||||
$var_comment_type->check(
|
||||
$statements_analyzer,
|
||||
new CodeLocation($statements_analyzer->getSource(), $var),
|
||||
$statements_analyzer->getSuppressedIssues()
|
||||
);
|
||||
|
||||
if ($codebase->alter_code
|
||||
&& $var_comment->type_start
|
||||
&& $var_comment->type_end
|
||||
&& $var_comment->line_number
|
||||
) {
|
||||
$type_location = new CodeLocation\DocblockTypeLocation(
|
||||
$statements_analyzer,
|
||||
$var_comment->type_start,
|
||||
$var_comment->type_end,
|
||||
$var_comment->line_number
|
||||
);
|
||||
|
||||
$codebase->classlikes->handleDocblockTypeInMigration(
|
||||
$codebase,
|
||||
$statements_analyzer,
|
||||
$var_comment_type,
|
||||
$type_location,
|
||||
$context->calling_method_id
|
||||
);
|
||||
}
|
||||
|
||||
if (!$var_comment->var_id || $var_comment->var_id === $var_id) {
|
||||
$comment_type = $var_comment_type;
|
||||
continue;
|
||||
}
|
||||
|
||||
$context->vars_in_scope[$var_comment->var_id] = $var_comment_type;
|
||||
} catch (\UnexpectedValueException $e) {
|
||||
if (IssueBuffer::accepts(
|
||||
new InvalidDocblock(
|
||||
(string)$e->getMessage(),
|
||||
new CodeLocation($statements_analyzer, $var)
|
||||
)
|
||||
)) {
|
||||
// fall through
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($comment_type) {
|
||||
$context->byref_constraints[$var_id] = new \Psalm\Internal\ReferenceConstraint($comment_type);
|
||||
}
|
||||
}
|
||||
|
||||
if ($var->default) {
|
||||
if (ExpressionAnalyzer::analyze($statements_analyzer, $var->default, $context) === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($comment_type
|
||||
&& ($var_default_type = $statements_analyzer->node_data->getType($var->default))
|
||||
&& !TypeAnalyzer::isContainedBy(
|
||||
$codebase,
|
||||
$var_default_type,
|
||||
$comment_type
|
||||
)
|
||||
) {
|
||||
if (IssueBuffer::accepts(
|
||||
new \Psalm\Issue\ReferenceConstraintViolation(
|
||||
$var_id . ' of type ' . $comment_type->getId() . ' cannot be assigned type '
|
||||
. $var_default_type->getId(),
|
||||
new CodeLocation($statements_analyzer, $var)
|
||||
)
|
||||
)) {
|
||||
// fall through
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($context->check_variables) {
|
||||
$context->vars_in_scope[$var_id] = $comment_type ? clone $comment_type : Type::getMixed();
|
||||
$context->vars_possibly_in_scope[$var_id] = true;
|
||||
$context->assigned_var_ids[$var_id] = true;
|
||||
$statements_analyzer->byref_uses[$var_id] = true;
|
||||
|
||||
$location = new CodeLocation($statements_analyzer, $var);
|
||||
|
||||
if ($codebase->find_unused_variables) {
|
||||
$context->unreferenced_vars[$var_id] = [$location->getHash() => $location];
|
||||
}
|
||||
|
||||
$statements_analyzer->registerVariable(
|
||||
$var_id,
|
||||
$location,
|
||||
$context->branch_point
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
111
src/Psalm/Internal/Analyzer/Statements/UnsetAnalyzer.php
Normal file
111
src/Psalm/Internal/Analyzer/Statements/UnsetAnalyzer.php
Normal file
@ -0,0 +1,111 @@
|
||||
<?php
|
||||
namespace Psalm\Internal\Analyzer\Statements;
|
||||
|
||||
use PhpParser;
|
||||
use Psalm\Internal\Analyzer\StatementsAnalyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\Expression\ExpressionIdentifier;
|
||||
use Psalm\Context;
|
||||
use Psalm\Type;
|
||||
|
||||
class UnsetAnalyzer
|
||||
{
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public static function analyze(
|
||||
StatementsAnalyzer $statements_analyzer,
|
||||
PhpParser\Node\Stmt\Unset_ $stmt,
|
||||
Context $context
|
||||
) {
|
||||
$context->inside_unset = true;
|
||||
|
||||
foreach ($stmt->vars as $var) {
|
||||
ExpressionAnalyzer::analyze($statements_analyzer, $var, $context);
|
||||
|
||||
$var_id = ExpressionIdentifier::getArrayVarId(
|
||||
$var,
|
||||
$statements_analyzer->getFQCLN(),
|
||||
$statements_analyzer
|
||||
);
|
||||
|
||||
if ($var_id) {
|
||||
$context->remove($var_id);
|
||||
}
|
||||
|
||||
if ($var instanceof PhpParser\Node\Expr\ArrayDimFetch && $var->dim) {
|
||||
$root_var_id = ExpressionIdentifier::getArrayVarId(
|
||||
$var->var,
|
||||
$statements_analyzer->getFQCLN(),
|
||||
$statements_analyzer
|
||||
);
|
||||
|
||||
if ($root_var_id && isset($context->vars_in_scope[$root_var_id])) {
|
||||
$root_type = clone $context->vars_in_scope[$root_var_id];
|
||||
|
||||
foreach ($root_type->getAtomicTypes() as $atomic_root_type) {
|
||||
if ($atomic_root_type instanceof Type\Atomic\ObjectLike) {
|
||||
if ($var->dim instanceof PhpParser\Node\Scalar\String_
|
||||
|| $var->dim instanceof PhpParser\Node\Scalar\LNumber
|
||||
) {
|
||||
if (isset($atomic_root_type->properties[$var->dim->value])) {
|
||||
unset($atomic_root_type->properties[$var->dim->value]);
|
||||
}
|
||||
|
||||
if (!$atomic_root_type->properties) {
|
||||
if ($atomic_root_type->previous_value_type) {
|
||||
$root_type->addType(
|
||||
new Type\Atomic\TArray([
|
||||
$atomic_root_type->previous_key_type
|
||||
? clone $atomic_root_type->previous_key_type
|
||||
: new Type\Union([new Type\Atomic\TArrayKey]),
|
||||
clone $atomic_root_type->previous_value_type,
|
||||
])
|
||||
);
|
||||
} else {
|
||||
$root_type->addType(
|
||||
new Type\Atomic\TArray([
|
||||
new Type\Union([new Type\Atomic\TEmpty]),
|
||||
new Type\Union([new Type\Atomic\TEmpty]),
|
||||
])
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$atomic_root_type->sealed = false;
|
||||
|
||||
$root_type->addType(
|
||||
$atomic_root_type->getGenericArrayType()
|
||||
);
|
||||
}
|
||||
} elseif ($atomic_root_type instanceof Type\Atomic\TNonEmptyArray) {
|
||||
$root_type->addType(
|
||||
new Type\Atomic\TArray($atomic_root_type->type_params)
|
||||
);
|
||||
} elseif ($atomic_root_type instanceof Type\Atomic\TNonEmptyMixed) {
|
||||
$root_type->addType(
|
||||
new Type\Atomic\TMixed()
|
||||
);
|
||||
} elseif ($atomic_root_type instanceof Type\Atomic\TList) {
|
||||
$root_type->addType(
|
||||
new Type\Atomic\TArray([
|
||||
Type::getInt(),
|
||||
$atomic_root_type->type_param
|
||||
])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$context->vars_in_scope[$root_var_id] = $root_type;
|
||||
|
||||
$context->removeVarFromConflictingClauses(
|
||||
$root_var_id,
|
||||
$context->vars_in_scope[$root_var_id],
|
||||
$statements_analyzer
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$context->inside_unset = false;
|
||||
}
|
||||
}
|
@ -0,0 +1,378 @@
|
||||
<?php
|
||||
namespace Psalm\Internal\Analyzer\Statements;
|
||||
|
||||
use PhpParser;
|
||||
use Psalm\Internal\PhpVisitor\CheckTrivialExprVisitor;
|
||||
use Psalm\Codebase;
|
||||
use Psalm\CodeLocation;
|
||||
use Psalm\FileManipulation;
|
||||
use Psalm\Internal\FileManipulation\FileManipulationBuffer;
|
||||
use function is_string;
|
||||
use function strlen;
|
||||
use function substr;
|
||||
use function array_key_exists;
|
||||
use function count;
|
||||
use function token_get_all;
|
||||
use function array_slice;
|
||||
use function is_array;
|
||||
use function trim;
|
||||
use function is_null;
|
||||
|
||||
class UnusedAssignmentRemover
|
||||
{
|
||||
/**
|
||||
* @var array<string, CodeLocation>
|
||||
*/
|
||||
private $removed_unref_vars = [];
|
||||
|
||||
/**
|
||||
* @param array<PhpParser\Node\Stmt> $stmts
|
||||
* @param array<string, CodeLocation> $var_loc_map
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function findUnusedAssignment(
|
||||
Codebase $codebase,
|
||||
array $stmts,
|
||||
array $var_loc_map,
|
||||
string $var_id,
|
||||
CodeLocation $original_location
|
||||
): void {
|
||||
$search_result = $this->findAssignStmt($stmts, $var_id, $original_location);
|
||||
$assign_stmt = $search_result[0];
|
||||
$assign_exp = $search_result[1];
|
||||
$chain_assignment = false;
|
||||
|
||||
if (!is_null($assign_stmt) && !is_null($assign_exp)) {
|
||||
// Check if we have to remove assignment statemnt as expression (i.e. just "$var = ")
|
||||
|
||||
// Consider chain of assignments
|
||||
$rhs_exp = $assign_exp->expr;
|
||||
if ($rhs_exp instanceof PhpParser\Node\Expr\Assign
|
||||
|| $rhs_exp instanceof PhpParser\Node\Expr\AssignOp
|
||||
|| $rhs_exp instanceof PhpParser\Node\Expr\AssignRef
|
||||
) {
|
||||
$chain_assignment = true;
|
||||
$removable_stmt = $this->checkRemovableChainAssignment($assign_exp, $var_loc_map);
|
||||
} else {
|
||||
$removable_stmt = true;
|
||||
}
|
||||
|
||||
if ($removable_stmt) {
|
||||
$traverser = new PhpParser\NodeTraverser();
|
||||
$visitor = new CheckTrivialExprVisitor();
|
||||
$traverser->addVisitor($visitor);
|
||||
$traverser->traverse([$rhs_exp]);
|
||||
|
||||
$rhs_exp_trivial = (count($visitor->getNonTrivialExpr()) == 0);
|
||||
|
||||
if ($rhs_exp_trivial) {
|
||||
$treat_as_expr = false;
|
||||
} else {
|
||||
$treat_as_expr = true;
|
||||
}
|
||||
} else {
|
||||
$treat_as_expr = true;
|
||||
}
|
||||
|
||||
if ($treat_as_expr) {
|
||||
$is_assign_ref = $assign_exp instanceof PhpParser\Node\Expr\AssignRef;
|
||||
$new_file_manipulation = self::getPartialRemovalBounds(
|
||||
$codebase,
|
||||
$original_location,
|
||||
$assign_stmt->getEndFilePos(),
|
||||
$is_assign_ref
|
||||
);
|
||||
$this->removed_unref_vars[$var_id] = $original_location;
|
||||
} else {
|
||||
// Remove whole assignment statement
|
||||
$new_file_manipulation = new FileManipulation(
|
||||
$assign_stmt->getStartFilePos(),
|
||||
$assign_stmt->getEndFilePos() + 1,
|
||||
"",
|
||||
false,
|
||||
true
|
||||
);
|
||||
|
||||
// If statement we are removing is a chain of assignments, mark other variables as removed
|
||||
if ($chain_assignment) {
|
||||
$this->markRemovedChainAssignVar($assign_exp, $var_loc_map);
|
||||
} else {
|
||||
$this->removed_unref_vars[$var_id] = $original_location;
|
||||
}
|
||||
}
|
||||
|
||||
FileManipulationBuffer::add($original_location->file_path, [$new_file_manipulation]);
|
||||
} elseif (!is_null($assign_exp)) {
|
||||
$is_assign_ref = $assign_exp instanceof PhpParser\Node\Expr\AssignRef;
|
||||
$new_file_manipulation = self::getPartialRemovalBounds(
|
||||
$codebase,
|
||||
$original_location,
|
||||
$assign_exp->getEndFilePos(),
|
||||
$is_assign_ref
|
||||
);
|
||||
|
||||
FileManipulationBuffer::add($original_location->file_path, [$new_file_manipulation]);
|
||||
$this->removed_unref_vars[$var_id] = $original_location;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param CodeLocation $var_loc
|
||||
* @param int $end_bound
|
||||
* @param bool $assign_ref
|
||||
* @return FileManipulation
|
||||
*/
|
||||
private function getPartialRemovalBounds(
|
||||
Codebase $codebase,
|
||||
CodeLocation $var_loc,
|
||||
int $end_bound,
|
||||
bool $assign_ref = false
|
||||
): FileManipulation {
|
||||
$var_start_loc= $var_loc->raw_file_start;
|
||||
$stmt_content = $codebase->file_provider->getContents(
|
||||
$var_loc->file_path
|
||||
);
|
||||
$str_for_token = "<?php\n" . substr($stmt_content, $var_start_loc, $end_bound - $var_start_loc + 1);
|
||||
$token_list = array_slice(token_get_all($str_for_token), 1); //Ignore "<?php"
|
||||
|
||||
$offset_count = strlen($token_list[0][1]);
|
||||
$iter = 1;
|
||||
|
||||
// Check if second token is just whitespace
|
||||
if (is_array($token_list[$iter]) && strlen(trim($token_list[$iter][1])) == 0) {
|
||||
$offset_count += strlen($token_list[1][1]);
|
||||
$iter++;
|
||||
}
|
||||
|
||||
// Add offset for assignment operator
|
||||
if (is_string($token_list[$iter])) {
|
||||
$offset_count += 1;
|
||||
} else {
|
||||
$offset_count += strlen($token_list[$iter][1]);
|
||||
}
|
||||
$iter++;
|
||||
|
||||
// Remove any whitespace following assignment operator token (e.g "=", "+=")
|
||||
if (is_array($token_list[$iter]) && strlen(trim($token_list[$iter][1])) == 0) {
|
||||
$offset_count += strlen($token_list[$iter][1]);
|
||||
$iter++;
|
||||
}
|
||||
|
||||
// If we are dealing with assignment by reference, we need to handle "&" and any whitespace after
|
||||
if ($assign_ref) {
|
||||
$offset_count += 1;
|
||||
$iter++;
|
||||
// Handle any whitespace after "&"
|
||||
if (is_array($token_list[$iter]) && strlen(trim($token_list[$iter][1])) == 0) {
|
||||
$offset_count += strlen($token_list[$iter][1]);
|
||||
}
|
||||
}
|
||||
|
||||
$file_man_start = $var_start_loc;
|
||||
$file_man_end = $var_start_loc + $offset_count;
|
||||
|
||||
return new FileManipulation($file_man_start, $file_man_end, "", false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param PhpParser\Node\Expr\Assign|PhpParser\Node\Expr\AssignOp|PhpParser\Node\Expr\AssignRef $cur_assign
|
||||
* @param array<string, CodeLocation> $var_loc_map
|
||||
* @return void
|
||||
*/
|
||||
private function markRemovedChainAssignVar(PhpParser\Node\Expr $cur_assign, array $var_loc_map): void
|
||||
{
|
||||
$var = $cur_assign->var;
|
||||
if ($var instanceof PhpParser\Node\Expr\Variable && is_string($var->name)) {
|
||||
$var_name = "$" . $var->name;
|
||||
$var_loc = $var_loc_map[$var_name];
|
||||
$this->removed_unref_vars[$var_name] = $var_loc;
|
||||
|
||||
$rhs_exp = $cur_assign->expr;
|
||||
if ($rhs_exp instanceof PhpParser\Node\Expr\Assign
|
||||
|| $rhs_exp instanceof PhpParser\Node\Expr\AssignOp
|
||||
|| $rhs_exp instanceof PhpParser\Node\Expr\AssignRef
|
||||
) {
|
||||
$this->markRemovedChainAssignVar($rhs_exp, $var_loc_map);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param PhpParser\Node\Expr\Assign|PhpParser\Node\Expr\AssignOp|PhpParser\Node\Expr\AssignRef $cur_assign
|
||||
* @param array<string, CodeLocation> $var_loc_map
|
||||
* @return bool
|
||||
*/
|
||||
private function checkRemovableChainAssignment(PhpParser\Node\Expr $cur_assign, array $var_loc_map): bool
|
||||
{
|
||||
// Check if current assignment expr's variable is removable
|
||||
$var = $cur_assign->var;
|
||||
if ($var instanceof PhpParser\Node\Expr\Variable && is_string($var->name)) {
|
||||
$var_loc = $cur_assign->var->getStartFilePos();
|
||||
$var_name = "$" . $var->name;
|
||||
|
||||
if (array_key_exists($var_name, $var_loc_map) &&
|
||||
$var_loc_map[$var_name]->raw_file_start === $var_loc) {
|
||||
$curr_removable = true;
|
||||
} else {
|
||||
$curr_removable = false;
|
||||
}
|
||||
|
||||
if ($curr_removable) {
|
||||
$rhs_exp = $cur_assign->expr;
|
||||
|
||||
if ($rhs_exp instanceof PhpParser\Node\Expr\Assign
|
||||
|| $rhs_exp instanceof PhpParser\Node\Expr\AssignOp
|
||||
|| $rhs_exp instanceof PhpParser\Node\Expr\AssignRef
|
||||
) {
|
||||
$rhs_removable = $this->checkRemovableChainAssignment($rhs_exp, $var_loc_map);
|
||||
return $rhs_removable;
|
||||
}
|
||||
}
|
||||
return $curr_removable;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<PhpParser\Node\Stmt> $stmts
|
||||
* @param string $var_id
|
||||
* @param CodeLocation $original_location
|
||||
* @return array{
|
||||
* 0: PhpParser\Node\Stmt|null,
|
||||
* 1: PhpParser\Node\Expr\Assign|PhpParser\Node\Expr\AssignOp|PhpParser\Node\Expr\AssignRef|null
|
||||
* }
|
||||
*/
|
||||
private function findAssignStmt(array $stmts, string $var_id, CodeLocation $original_location)
|
||||
{
|
||||
$assign_stmt = null;
|
||||
$assign_exp = null;
|
||||
$assign_exp_found = false;
|
||||
|
||||
$i = 0;
|
||||
|
||||
while ($i < count($stmts) && !$assign_exp_found) {
|
||||
$stmt = $stmts[$i];
|
||||
if ($stmt instanceof PhpParser\Node\Stmt\Expression) {
|
||||
$search_result = $this->findAssignExp($stmt->expr, $var_id, $original_location->raw_file_start);
|
||||
|
||||
$target_exp = $search_result[0];
|
||||
$levels_taken = $search_result[1];
|
||||
|
||||
if (!is_null($target_exp)) {
|
||||
$assign_exp_found = true;
|
||||
$assign_exp = $target_exp;
|
||||
$assign_stmt = $levels_taken === 1 ? $stmt : null;
|
||||
}
|
||||
} elseif ($stmt instanceof PhpParser\Node\Stmt\TryCatch) {
|
||||
$search_result = $this->findAssignStmt($stmt->stmts, $var_id, $original_location);
|
||||
|
||||
if ($search_result[0] && $search_result[1]) {
|
||||
return $search_result;
|
||||
}
|
||||
|
||||
foreach ($stmt->catches as $catch_stmt) {
|
||||
$search_result = $this->findAssignStmt($catch_stmt->stmts, $var_id, $original_location);
|
||||
|
||||
if ($search_result[0] && $search_result[1]) {
|
||||
return $search_result;
|
||||
}
|
||||
}
|
||||
} elseif ($stmt instanceof PhpParser\Node\Stmt\Do_
|
||||
|| $stmt instanceof PhpParser\Node\Stmt\While_
|
||||
) {
|
||||
$search_result = $this->findAssignStmt($stmt->stmts, $var_id, $original_location);
|
||||
|
||||
if ($search_result[0] && $search_result[1]) {
|
||||
return $search_result;
|
||||
}
|
||||
} elseif ($stmt instanceof PhpParser\Node\Stmt\Foreach_) {
|
||||
$search_result = $this->findAssignStmt($stmt->stmts, $var_id, $original_location);
|
||||
|
||||
if ($search_result[0] && $search_result[1]) {
|
||||
return $search_result;
|
||||
}
|
||||
} elseif ($stmt instanceof PhpParser\Node\Stmt\For_) {
|
||||
$search_result = $this->findAssignStmt($stmt->stmts, $var_id, $original_location);
|
||||
|
||||
if ($search_result[0] && $search_result[1]) {
|
||||
return $search_result;
|
||||
}
|
||||
} elseif ($stmt instanceof PhpParser\Node\Stmt\If_) {
|
||||
$search_result = $this->findAssignStmt($stmt->stmts, $var_id, $original_location);
|
||||
|
||||
if ($search_result[0] && $search_result[1]) {
|
||||
return $search_result;
|
||||
}
|
||||
|
||||
foreach ($stmt->elseifs as $elseif_stmt) {
|
||||
$search_result = $this->findAssignStmt($elseif_stmt->stmts, $var_id, $original_location);
|
||||
|
||||
if ($search_result[0] && $search_result[1]) {
|
||||
return $search_result;
|
||||
}
|
||||
}
|
||||
|
||||
if ($stmt->else) {
|
||||
$search_result = $this->findAssignStmt($stmt->else->stmts, $var_id, $original_location);
|
||||
|
||||
if ($search_result[0] && $search_result[1]) {
|
||||
return $search_result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$i++;
|
||||
}
|
||||
|
||||
return [$assign_stmt, $assign_exp];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param PhpParser\Node\Expr $current_node
|
||||
* @param string $var_id
|
||||
* @param int $var_start_loc
|
||||
* @param int $search_level
|
||||
* @return array{
|
||||
* 0: PhpParser\Node\Expr\Assign|PhpParser\Node\Expr\AssignOp|PhpParser\Node\Expr\AssignRef|null,
|
||||
* 1: int
|
||||
* }
|
||||
*/
|
||||
private function findAssignExp(
|
||||
PhpParser\Node\Expr $current_node,
|
||||
string $var_id,
|
||||
int $var_start_loc,
|
||||
int $search_level = 1
|
||||
) {
|
||||
if ($current_node instanceof PhpParser\Node\Expr\Assign
|
||||
|| $current_node instanceof PhpPArser\Node\Expr\AssignOp
|
||||
|| $current_node instanceof PhpParser\Node\Expr\AssignRef
|
||||
) {
|
||||
$var = $current_node->var;
|
||||
|
||||
if ($var instanceof PhpParser\Node\Expr\Variable
|
||||
&& $var->name === substr($var_id, 1)
|
||||
&& $var->getStartFilePos() === $var_start_loc
|
||||
) {
|
||||
return [$current_node, $search_level];
|
||||
}
|
||||
|
||||
$rhs_exp = $current_node->expr;
|
||||
$rhs_search_result = $this->findAssignExp($rhs_exp, $var_id, $var_start_loc, $search_level + 1);
|
||||
return [$rhs_search_result[0], $rhs_search_result[1]];
|
||||
} else {
|
||||
return [null, $search_level];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param CodeLocation $var_loc
|
||||
* @return bool
|
||||
*/
|
||||
public function checkIfVarRemoved(string $var_id, CodeLocation $var_loc): bool
|
||||
{
|
||||
return array_key_exists($var_id, $this->removed_unref_vars)
|
||||
&& $this->removed_unref_vars[$var_id] === $var_loc;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -4,8 +4,6 @@ namespace Psalm\Internal\Analyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer;
|
||||
use Psalm\Codebase;
|
||||
use Psalm\Internal\Codebase\CallMap;
|
||||
use Psalm\Internal\Codebase\Methods;
|
||||
use Psalm\Internal\Type\TemplateResult;
|
||||
use Psalm\Type;
|
||||
use Psalm\Type\Atomic\ObjectLike;
|
||||
use Psalm\Type\Atomic\TObjectWithProperties;
|
||||
@ -57,7 +55,6 @@ use function array_values;
|
||||
use function count;
|
||||
use function is_string;
|
||||
use function array_fill;
|
||||
use function array_search;
|
||||
use function array_keys;
|
||||
use function array_reduce;
|
||||
use function end;
|
||||
|
@ -18,6 +18,7 @@ use Psalm\CodeLocation;
|
||||
use Psalm\Config;
|
||||
use Psalm\Exception\UnpopulatedClasslikeException;
|
||||
use Psalm\Internal\Analyzer\ClassLikeAnalyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\Expression\Fetch\ConstFetchAnalyzer;
|
||||
use Psalm\Internal\FileManipulation\FileManipulationBuffer;
|
||||
use Psalm\Internal\Provider\ClassLikeStorageProvider;
|
||||
use Psalm\Internal\Provider\FileReferenceProvider;
|
||||
@ -1777,7 +1778,8 @@ class ClassLikes
|
||||
|
||||
if ($c instanceof UnresolvedConstant\Constant) {
|
||||
if ($statements_analyzer) {
|
||||
$found_type = $statements_analyzer->getConstType(
|
||||
$found_type = ConstFetchAnalyzer::getConstType(
|
||||
$statements_analyzer,
|
||||
$c->name,
|
||||
$c->is_fully_qualified,
|
||||
null
|
||||
|
@ -34,7 +34,9 @@ use Psalm\Internal\Analyzer\ClassAnalyzer;
|
||||
use Psalm\Internal\Analyzer\ClassLikeAnalyzer;
|
||||
use Psalm\Internal\Analyzer\CommentAnalyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\Expression\CallAnalyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\Expression\Fetch\ConstFetchAnalyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\Expression\IncludeAnalyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\Expression\SimpleTypeInferer;
|
||||
use Psalm\Internal\Analyzer\StatementsAnalyzer;
|
||||
use Psalm\Internal\Codebase\CallMap;
|
||||
use Psalm\Internal\Codebase\PropertyMap;
|
||||
@ -491,7 +493,7 @@ class ReflectorVisitor extends PhpParser\NodeVisitorAbstract implements PhpParse
|
||||
}
|
||||
} elseif ($node instanceof PhpParser\Node\Stmt\Const_) {
|
||||
foreach ($node->consts as $const) {
|
||||
$const_type = StatementsAnalyzer::getSimpleType(
|
||||
$const_type = SimpleTypeInferer::infer(
|
||||
$this->codebase,
|
||||
new \Psalm\Internal\Provider\NodeDataProvider(),
|
||||
$const->value,
|
||||
@ -829,7 +831,7 @@ class ReflectorVisitor extends PhpParser\NodeVisitorAbstract implements PhpParse
|
||||
$second_arg_value = isset($node->args[1]) ? $node->args[1]->value : null;
|
||||
if ($first_arg_value && $second_arg_value) {
|
||||
$type_provider = new \Psalm\Internal\Provider\NodeDataProvider();
|
||||
$const_name = StatementsAnalyzer::getConstName(
|
||||
$const_name = ConstFetchAnalyzer::getConstName(
|
||||
$first_arg_value,
|
||||
$type_provider,
|
||||
$this->codebase,
|
||||
@ -837,7 +839,7 @@ class ReflectorVisitor extends PhpParser\NodeVisitorAbstract implements PhpParse
|
||||
);
|
||||
|
||||
if ($const_name !== null) {
|
||||
$const_type = StatementsAnalyzer::getSimpleType(
|
||||
$const_type = SimpleTypeInferer::infer(
|
||||
$this->codebase,
|
||||
$type_provider,
|
||||
$second_arg_value,
|
||||
@ -2945,7 +2947,7 @@ class ReflectorVisitor extends PhpParser\NodeVisitorAbstract implements PhpParse
|
||||
$is_nullable,
|
||||
$param->variadic,
|
||||
$param->default
|
||||
? StatementsAnalyzer::getSimpleType(
|
||||
? SimpleTypeInferer::infer(
|
||||
$this->codebase,
|
||||
new \Psalm\Internal\Provider\NodeDataProvider(),
|
||||
$param->default,
|
||||
@ -3303,7 +3305,7 @@ class ReflectorVisitor extends PhpParser\NodeVisitorAbstract implements PhpParse
|
||||
|
||||
if (!$signature_type && !$doc_var_group_type) {
|
||||
if ($property->default) {
|
||||
$property_storage->suggested_type = StatementsAnalyzer::getSimpleType(
|
||||
$property_storage->suggested_type = SimpleTypeInferer::infer(
|
||||
$this->codebase,
|
||||
new \Psalm\Internal\Provider\NodeDataProvider(),
|
||||
$property->default,
|
||||
@ -3422,7 +3424,7 @@ class ReflectorVisitor extends PhpParser\NodeVisitorAbstract implements PhpParse
|
||||
}
|
||||
|
||||
foreach ($stmt->consts as $const) {
|
||||
$const_type = StatementsAnalyzer::getSimpleType(
|
||||
$const_type = SimpleTypeInferer::infer(
|
||||
$this->codebase,
|
||||
new \Psalm\Internal\Provider\NodeDataProvider(),
|
||||
$const->value,
|
||||
|
@ -8,6 +8,7 @@ use function is_string;
|
||||
use Psalm\Codebase;
|
||||
use Psalm\CodeLocation;
|
||||
use Psalm\Internal\Analyzer\StatementsAnalyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\Expression\Fetch\VariableFetchAnalyzer;
|
||||
use Psalm\Internal\Analyzer\TraitAnalyzer;
|
||||
use Psalm\Internal\Analyzer\TypeAnalyzer;
|
||||
use Psalm\Issue\DocblockTypeContradiction;
|
||||
@ -82,9 +83,9 @@ class AssertionReconciler extends \Psalm\Type\Reconciler
|
||||
|
||||
if ($existing_var_type === null
|
||||
&& is_string($key)
|
||||
&& $statements_analyzer->isSuperGlobal($key)
|
||||
&& VariableFetchAnalyzer::isSuperGlobal($key)
|
||||
) {
|
||||
$existing_var_type = $statements_analyzer->getGlobalType($key);
|
||||
$existing_var_type = VariableFetchAnalyzer::getGlobalType($key);
|
||||
}
|
||||
|
||||
if ($existing_var_type === null) {
|
||||
|
Loading…
Reference in New Issue
Block a user