1
0
mirror of https://github.com/danog/psalm.git synced 2025-01-23 06:11:25 +01:00

Merge pull request #9301 from weirdan/allow-var-docblock-tags-on-global

This commit is contained in:
Bruce Weirdan 2023-02-15 02:28:00 -04:00 committed by GitHub
commit d9161c3439
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 228 additions and 141 deletions

View File

@ -4,6 +4,9 @@ namespace Psalm\Internal\Analyzer;
use PhpParser;
use Psalm\Aliases;
use Psalm\CodeLocation;
use Psalm\CodeLocation\DocblockTypeLocation;
use Psalm\Context;
use Psalm\DocComment;
use Psalm\Exception\DocblockParseException;
use Psalm\Exception\IncorrectDocblockException;
@ -13,12 +16,18 @@ use Psalm\Internal\Scanner\DocblockParser;
use Psalm\Internal\Scanner\ParsedDocblock;
use Psalm\Internal\Scanner\VarDocblockComment;
use Psalm\Internal\Type\TypeAlias;
use Psalm\Internal\Type\TypeExpander;
use Psalm\Internal\Type\TypeParser;
use Psalm\Internal\Type\TypeTokenizer;
use Psalm\Issue\InvalidDocblock;
use Psalm\Issue\MissingDocblockType;
use Psalm\IssueBuffer;
use Psalm\Type\Union;
use UnexpectedValueException;
use function array_merge;
use function count;
use function is_string;
use function preg_match;
use function preg_replace;
use function preg_split;
@ -381,4 +390,127 @@ class CommentAnalyzer
return [$type];
}
/** @return list<VarDocblockComment> */
public static function getVarComments(
PhpParser\Comment\Doc $doc_comment,
StatementsAnalyzer $statements_analyzer,
PhpParser\Node\Expr\Variable $var
): array {
$codebase = $statements_analyzer->getCodebase();
$parsed_docblock = $statements_analyzer->getParsedDocblock();
if (!$parsed_docblock) {
return [];
}
$var_comments = [];
try {
$var_comments = $codebase->config->disable_var_parsing
? []
: self::arrayToDocblocks(
$doc_comment,
$parsed_docblock,
$statements_analyzer->getSource(),
$statements_analyzer->getSource()->getAliases(),
$statements_analyzer->getSource()->getTemplateTypeMap(),
);
} catch (IncorrectDocblockException $e) {
IssueBuffer::maybeAdd(
new MissingDocblockType(
$e->getMessage(),
new CodeLocation($statements_analyzer, $var),
),
);
} catch (DocblockParseException $e) {
IssueBuffer::maybeAdd(
new InvalidDocblock(
$e->getMessage(),
new CodeLocation($statements_analyzer->getSource(), $var),
),
);
}
return $var_comments;
}
/**
* @param list<VarDocblockComment> $var_comments
*/
public static function populateVarTypesFromDocblock(
array $var_comments,
PhpParser\Node\Expr\Variable $var,
Context $context,
StatementsAnalyzer $statements_analyzer
): ?Union {
if (!is_string($var->name)) {
return null;
}
$codebase = $statements_analyzer->getCodebase();
$comment_type = null;
$var_id = '$' . $var->name;
foreach ($var_comments as $var_comment) {
if (!$var_comment->type) {
continue;
}
try {
$var_comment_type = TypeExpander::expandUnion(
$codebase,
$var_comment->type,
$context->self,
$context->self,
$statements_analyzer->getParentFQCLN(),
);
$var_comment_type = $var_comment_type->setFromDocblock();
/** @psalm-suppress UnusedMethodCall */
$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 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) {
IssueBuffer::maybeAdd(
new InvalidDocblock(
$e->getMessage(),
new CodeLocation($statements_analyzer, $var),
),
);
}
}
return $comment_type;
}
}

View File

@ -5,6 +5,7 @@ namespace Psalm\Internal\Analyzer\Statements;
use PhpParser;
use Psalm\CodeLocation;
use Psalm\Context;
use Psalm\Internal\Analyzer\CommentAnalyzer;
use Psalm\Internal\Analyzer\FunctionLikeAnalyzer;
use Psalm\Internal\Analyzer\Statements\Expression\Fetch\VariableFetchAnalyzer;
use Psalm\Internal\Analyzer\StatementsAnalyzer;
@ -43,57 +44,80 @@ class GlobalAnalyzer
: null;
foreach ($stmt->vars as $var) {
if ($var instanceof PhpParser\Node\Expr\Variable) {
if (is_string($var->name)) {
$var_id = '$' . $var->name;
if (!$var instanceof PhpParser\Node\Expr\Variable) {
continue;
}
if ($var->name === 'argv' || $var->name === 'argc') {
$context->vars_in_scope[$var_id] =
VariableFetchAnalyzer::getGlobalType($var_id, $codebase->analysis_php_version_id);
} elseif (isset($function_storage->global_types[$var_id])) {
$context->vars_in_scope[$var_id] = $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)
? $global_context->vars_in_scope[$var_id]
: VariableFetchAnalyzer::getGlobalType($var_id, $codebase->analysis_php_version_id);
if (!is_string($var->name)) {
continue;
}
$context->vars_possibly_in_scope[$var_id] = true;
$var_id = '$' . $var->name;
$context->byref_constraints[$var_id] = new ReferenceConstraint();
}
$doc_comment = $stmt->getDocComment();
$comment_type = null;
$assignment_node = DataFlowNode::getForAssignment(
$var_id,
new CodeLocation($statements_analyzer, $var),
);
$context->vars_in_scope[$var_id] = $context->vars_in_scope[$var_id]->setParentNodes([
$assignment_node->id => $assignment_node,
]);
$context->references_to_external_scope[$var_id] = true;
if ($doc_comment) {
$var_comments = CommentAnalyzer::getVarComments($doc_comment, $statements_analyzer, $var);
$comment_type = CommentAnalyzer::populateVarTypesFromDocblock(
$var_comments,
$var,
$context,
$statements_analyzer,
);
}
if (isset($context->references_in_scope[$var_id])) {
// Global shadows existing reference
$context->decrementReferenceCount($var_id);
unset($context->references_in_scope[$var_id]);
}
$statements_analyzer->registerVariable(
$var_id,
new CodeLocation($statements_analyzer, $var),
$context->branch_point,
);
$statements_analyzer->getCodebase()->analyzer->addNodeReference(
$statements_analyzer->getFilePath(),
$var,
$var_id,
);
if ($comment_type) {
$context->vars_in_scope[$var_id] = $comment_type;
$context->vars_possibly_in_scope[$var_id] = true;
$context->byref_constraints[$var_id] = new ReferenceConstraint($comment_type);
} else {
if ($var->name === 'argv' || $var->name === 'argc') {
$context->vars_in_scope[$var_id] =
VariableFetchAnalyzer::getGlobalType($var_id, $codebase->analysis_php_version_id);
} elseif (isset($function_storage->global_types[$var_id])) {
$context->vars_in_scope[$var_id] = $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)
? $global_context->vars_in_scope[$var_id]
: VariableFetchAnalyzer::getGlobalType($var_id, $codebase->analysis_php_version_id);
if ($global_context !== null && $global_context->hasVariable($var_id)) {
$global_context->referenced_globals[$var_id] = true;
}
$context->vars_possibly_in_scope[$var_id] = true;
$context->byref_constraints[$var_id] = new ReferenceConstraint();
}
}
$assignment_node = DataFlowNode::getForAssignment(
$var_id,
new CodeLocation($statements_analyzer, $var),
);
$context->vars_in_scope[$var_id] = $context->vars_in_scope[$var_id]->setParentNodes([
$assignment_node->id => $assignment_node,
]);
$context->references_to_external_scope[$var_id] = true;
if (isset($context->references_in_scope[$var_id])) {
// Global shadows existing reference
$context->decrementReferenceCount($var_id);
unset($context->references_in_scope[$var_id]);
}
$statements_analyzer->registerVariable(
$var_id,
new CodeLocation($statements_analyzer, $var),
$context->branch_point,
);
$statements_analyzer->getCodebase()->analyzer->addNodeReference(
$statements_analyzer->getFilePath(),
$var,
$var_id,
);
if ($global_context !== null && $global_context->hasVariable($var_id)) {
$global_context->referenced_globals[$var_id] = true;
}
}
}
}

View File

@ -4,22 +4,15 @@ namespace Psalm\Internal\Analyzer\Statements;
use PhpParser;
use Psalm\CodeLocation;
use Psalm\CodeLocation\DocblockTypeLocation;
use Psalm\Context;
use Psalm\Exception\DocblockParseException;
use Psalm\Exception\IncorrectDocblockException;
use Psalm\Internal\Analyzer\CommentAnalyzer;
use Psalm\Internal\Analyzer\StatementsAnalyzer;
use Psalm\Internal\ReferenceConstraint;
use Psalm\Internal\Type\Comparator\UnionTypeComparator;
use Psalm\Internal\Type\TypeExpander;
use Psalm\Issue\ImpureStaticVariable;
use Psalm\Issue\InvalidDocblock;
use Psalm\Issue\MissingDocblockType;
use Psalm\Issue\ReferenceConstraintViolation;
use Psalm\IssueBuffer;
use Psalm\Type;
use UnexpectedValueException;
use function is_string;
@ -56,98 +49,18 @@ class StaticAnalyzer
$comment_type = null;
if ($doc_comment && ($parsed_docblock = $statements_analyzer->getParsedDocblock())) {
$var_comments = [];
if ($doc_comment) {
$var_comments = CommentAnalyzer::getVarComments($doc_comment, $statements_analyzer, $var->var);
$comment_type = CommentAnalyzer::populateVarTypesFromDocblock(
$var_comments,
$var->var,
$context,
$statements_analyzer,
);
}
try {
$var_comments = $codebase->config->disable_var_parsing
? []
: CommentAnalyzer::arrayToDocblocks(
$doc_comment,
$parsed_docblock,
$statements_analyzer->getSource(),
$statements_analyzer->getSource()->getAliases(),
$statements_analyzer->getSource()->getTemplateTypeMap(),
);
} catch (IncorrectDocblockException $e) {
IssueBuffer::maybeAdd(
new MissingDocblockType(
$e->getMessage(),
new CodeLocation($statements_analyzer, $var),
),
);
} catch (DocblockParseException $e) {
IssueBuffer::maybeAdd(
new InvalidDocblock(
$e->getMessage(),
new CodeLocation($statements_analyzer->getSource(), $var),
),
);
}
foreach ($var_comments as $var_comment) {
if (!$var_comment->type) {
continue;
}
try {
$var_comment_type = TypeExpander::expandUnion(
$codebase,
$var_comment->type,
$context->self,
$context->self,
$statements_analyzer->getParentFQCLN(),
);
$var_comment_type = $var_comment_type->setFromDocblock();
/** @psalm-suppress UnusedMethodCall */
$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 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) {
IssueBuffer::maybeAdd(
new InvalidDocblock(
$e->getMessage(),
new CodeLocation($statements_analyzer, $var),
),
);
}
}
if ($comment_type) {
$context->byref_constraints[$var_id] = new ReferenceConstraint($comment_type);
}
if ($comment_type) {
$context->byref_constraints[$var_id] = new ReferenceConstraint($comment_type);
}
if ($var->default) {

View File

@ -1248,6 +1248,24 @@ class AnnotationTest extends TestCase
}
}',
],
'globalDocBlock' => [
'code' => '<?php
function f(): string {
/** @var string $a */
global $a;
return $a;
}',
],
'globalDocBlockInGlobalScope' => [
'code' => '<?php
/**
* @psalm-suppress InvalidGlobal
* @var string $a
*/
global $a;
echo strlen($a);
',
],
];
}