Skip to content

Commit

Permalink
Support multiple doc comments above statement
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Jun 20, 2020
1 parent 261cf18 commit 428c8e5
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 78 deletions.
51 changes: 26 additions & 25 deletions src/Analyser/NodeScopeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace PHPStan\Analyser;

use PhpParser\Comment\Doc;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\Array_;
Expand Down Expand Up @@ -2431,38 +2432,38 @@ private function processAssignVar(

private function processStmtVarAnnotation(MutatingScope $scope, Node\Stmt $stmt, ?Expr $defaultExpr): MutatingScope
{
$comment = CommentHelper::getDocComment($stmt);
if ($comment === null) {
return $scope;
}

$function = $scope->getFunction();

$resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc(
$scope->getFile(),
$scope->isInClass() ? $scope->getClassReflection()->getName() : null,
$scope->isInTrait() ? $scope->getTraitReflection()->getName() : null,
$function !== null ? $function->getName() : null,
$comment
);

$variableLessTags = [];
foreach ($resolvedPhpDoc->getVarTags() as $name => $varTag) {
if (is_int($name)) {
$variableLessTags[] = $varTag;
continue;
}

$certainty = $scope->hasVariableType($name);
if ($certainty->no()) {
foreach ($stmt->getComments() as $comment) {
if (!$comment instanceof Doc) {
continue;
}

if ($scope->getFunction() === null && !$scope->isInAnonymousFunction()) {
$certainty = TrinaryLogic::createYes();
}
$resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc(
$scope->getFile(),
$scope->isInClass() ? $scope->getClassReflection()->getName() : null,
$scope->isInTrait() ? $scope->getTraitReflection()->getName() : null,
$function !== null ? $function->getName() : null,
$comment->getText()
);
foreach ($resolvedPhpDoc->getVarTags() as $name => $varTag) {
if (is_int($name)) {
$variableLessTags[] = $varTag;
continue;
}

$scope = $scope->assignVariable($name, $varTag->getType(), $certainty);
$certainty = $scope->hasVariableType($name);
if ($certainty->no()) {
continue;
}

if ($scope->getFunction() === null && !$scope->isInAnonymousFunction()) {
$certainty = TrinaryLogic::createYes();
}

$scope = $scope->assignVariable($name, $varTag->getType(), $certainty);
}
}

if (count($variableLessTags) === 1 && $defaultExpr !== null) {
Expand Down
111 changes: 58 additions & 53 deletions src/Type/FileTypeMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace PHPStan\Type;

use PhpParser\Comment\Doc;
use PhpParser\Node;
use PHPStan\Analyser\NameScope;
use PHPStan\Broker\AnonymousClassNameHelper;
Expand Down Expand Up @@ -388,67 +389,71 @@ function (\PhpParser\Node $node) use ($fileName, $lookForTrait, $traitMethodAlia
return null;
}

$phpDocString = CommentHelper::getDocComment($node);
if ($phpDocString === null) {
return null;
}
foreach (array_reverse($node->getComments()) as $comment) {
if (!$comment instanceof Doc) {
continue;
}

$className = $classStack[count($classStack) - 1] ?? null;
$typeMapCb = $typeMapStack[count($typeMapStack) - 1] ?? null;

$phpDocKey = $this->getPhpDocKey($className, $lookForTrait, $functionName, $phpDocString);
$phpDocMap[$phpDocKey] = static function () use ($phpDocString, $namespace, $uses, $className, $functionName, $typeMapCb, $resolvableTemplateTypes): NameScopedPhpDocString {
$nameScope = new NameScope(
$namespace,
$uses,
$className,
$functionName,
($typeMapCb !== null ? $typeMapCb() : TemplateTypeMap::createEmpty())->map(static function (string $name, Type $type) use ($className, $resolvableTemplateTypes): Type {
return TypeTraverser::map($type, static function (Type $type, callable $traverse) use ($className, $resolvableTemplateTypes): Type {
if (!$type instanceof TemplateType) {
return $traverse($type);
}
$phpDocString = $comment->getText();
$className = $classStack[count($classStack) - 1] ?? null;
$typeMapCb = $typeMapStack[count($typeMapStack) - 1] ?? null;

if (!$resolvableTemplateTypes) {
return $traverse($type->toArgument());
}
$phpDocKey = $this->getPhpDocKey($className, $lookForTrait, $functionName, $phpDocString);
$phpDocMap[$phpDocKey] = static function () use ($phpDocString, $namespace, $uses, $className, $functionName, $typeMapCb, $resolvableTemplateTypes): NameScopedPhpDocString {
$nameScope = new NameScope(
$namespace,
$uses,
$className,
$functionName,
($typeMapCb !== null ? $typeMapCb() : TemplateTypeMap::createEmpty())->map(static function (string $name, Type $type) use ($className, $resolvableTemplateTypes): Type {
return TypeTraverser::map($type, static function (Type $type, callable $traverse) use ($className, $resolvableTemplateTypes): Type {
if (!$type instanceof TemplateType) {
return $traverse($type);
}

$scope = $type->getScope();
if (!$resolvableTemplateTypes) {
return $traverse($type->toArgument());
}

if ($scope->getClassName() === null || $scope->getFunctionName() !== null || $scope->getClassName() !== $className) {
return $traverse($type->toArgument());
}
$scope = $type->getScope();

return $traverse($type);
});
})
);
return new NameScopedPhpDocString($phpDocString, $nameScope);
};
if ($scope->getClassName() === null || $scope->getFunctionName() !== null || $scope->getClassName() !== $className) {
return $traverse($type->toArgument());
}

if (!($node instanceof Node\Stmt\ClassLike) && !($node instanceof Node\FunctionLike)) {
return null;
}
return $traverse($type);
});
})
);
return new NameScopedPhpDocString($phpDocString, $nameScope);
};

$typeMapStack[] = function () use ($fileName, $className, $lookForTrait, $functionName, $phpDocString, $typeMapCb): TemplateTypeMap {
static $typeMap = null;
if ($typeMap !== null) {
return $typeMap;
if (!($node instanceof Node\Stmt\ClassLike) && !($node instanceof Node\FunctionLike)) {
continue;
}
$resolvedPhpDoc = $this->getResolvedPhpDoc(
$fileName,
$className,
$lookForTrait,
$functionName,
$phpDocString
);
return new TemplateTypeMap(array_merge(
$typeMapCb !== null ? $typeMapCb()->getTypes() : [],
$resolvedPhpDoc->getTemplateTypeMap()->getTypes()
));
};

return self::POP_TYPE_MAP_STACK;

$typeMapStack[] = function () use ($fileName, $className, $lookForTrait, $functionName, $phpDocString, $typeMapCb): TemplateTypeMap {
static $typeMap = null;
if ($typeMap !== null) {
return $typeMap;
}
$resolvedPhpDoc = $this->getResolvedPhpDoc(
$fileName,
$className,
$lookForTrait,
$functionName,
$phpDocString
);
return new TemplateTypeMap(array_merge(
$typeMapCb !== null ? $typeMapCb()->getTypes() : [],
$resolvedPhpDoc->getTemplateTypeMap()->getTypes()
));
};

return self::POP_TYPE_MAP_STACK;
}

return null;
},
static function (\PhpParser\Node $node, $callbackResult) use ($lookForTrait, &$namespace, &$functionName, &$classStack, &$uses, &$typeMapStack): void {
if ($node instanceof Node\Stmt\ClassLike && $lookForTrait === null) {
Expand Down
9 changes: 9 additions & 0 deletions tests/PHPStan/Analyser/data/override-root-scope-variable.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<?php

use PHPStan\TrinaryLogic;
use function PHPStan\Analyser\assertType;
use function PHPStan\Analyser\assertVariableCertainty;

assertVariableCertainty(TrinaryLogic::createMaybe(), $foo);
Expand Down Expand Up @@ -40,3 +41,11 @@ function (): void {

assertVariableCertainty(TrinaryLogic::createYes(), $bar);
};

/** @var Foo $lorem */
/** @var Bar $ipsum */

assertVariableCertainty(TrinaryLogic::createYes(), $lorem);
assertType('Foo', $lorem);
assertVariableCertainty(TrinaryLogic::createYes(), $ipsum);
assertType('Bar', $ipsum);

0 comments on commit 428c8e5

Please sign in to comment.