From 428c8e594f6b2ac2706e79760964b0635f78bdbc Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 20 Jun 2020 14:52:09 +0200 Subject: [PATCH] Support multiple doc comments above statement --- src/Analyser/NodeScopeResolver.php | 51 ++++---- src/Type/FileTypeMapper.php | 111 +++++++++--------- .../data/override-root-scope-variable.php | 9 ++ 3 files changed, 93 insertions(+), 78 deletions(-) diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 22f93051de..4487c63979 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -2,6 +2,7 @@ namespace PHPStan\Analyser; +use PhpParser\Comment\Doc; use PhpParser\Node; use PhpParser\Node\Expr; use PhpParser\Node\Expr\Array_; @@ -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) { diff --git a/src/Type/FileTypeMapper.php b/src/Type/FileTypeMapper.php index fe4814d0f4..e5a37b2fab 100644 --- a/src/Type/FileTypeMapper.php +++ b/src/Type/FileTypeMapper.php @@ -2,6 +2,7 @@ namespace PHPStan\Type; +use PhpParser\Comment\Doc; use PhpParser\Node; use PHPStan\Analyser\NameScope; use PHPStan\Broker\AnonymousClassNameHelper; @@ -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) { diff --git a/tests/PHPStan/Analyser/data/override-root-scope-variable.php b/tests/PHPStan/Analyser/data/override-root-scope-variable.php index a5adb76ee7..c265fffd3f 100644 --- a/tests/PHPStan/Analyser/data/override-root-scope-variable.php +++ b/tests/PHPStan/Analyser/data/override-root-scope-variable.php @@ -1,6 +1,7 @@