Skip to content

Commit

Permalink
Merge pull request #9301 from weirdan/allow-var-docblock-tags-on-global
Browse files Browse the repository at this point in the history
  • Loading branch information
weirdan committed Feb 15, 2023
2 parents 4ebef29 + 3f2ecae commit d9161c3
Show file tree
Hide file tree
Showing 4 changed files with 234 additions and 147 deletions.
132 changes: 132 additions & 0 deletions src/Psalm/Internal/Analyzer/CommentAnalyzer.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

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;
Expand All @@ -13,12 +16,18 @@
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;
Expand Down Expand Up @@ -381,4 +390,127 @@ public static function splitDocLine(string $return_block): array

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;
}
}
122 changes: 73 additions & 49 deletions src/Psalm/Internal/Analyzer/Statements/GlobalAnalyzer.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
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;
Expand Down Expand Up @@ -43,57 +44,80 @@ public static function analyze(
: 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, $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);

$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;
}
if (!$var instanceof PhpParser\Node\Expr\Variable) {
continue;
}

if (!is_string($var->name)) {
continue;
}

$var_id = '$' . $var->name;

$doc_comment = $stmt->getDocComment();
$comment_type = null;

if ($doc_comment) {
$var_comments = CommentAnalyzer::getVarComments($doc_comment, $statements_analyzer, $var);
$comment_type = CommentAnalyzer::populateVarTypesFromDocblock(
$var_comments,
$var,
$context,
$statements_analyzer,
);
}

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);

$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;
}
}
}
}

0 comments on commit d9161c3

Please sign in to comment.