Skip to content

Commit

Permalink
Bleeding edge - deep inspect unresolvable types in PHPDoc
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Jun 8, 2021
1 parent 003d235 commit 1de5de8
Show file tree
Hide file tree
Showing 13 changed files with 146 additions and 55 deletions.
7 changes: 5 additions & 2 deletions conf/config.neon
Original file line number Diff line number Diff line change
Expand Up @@ -394,8 +394,6 @@ services:

-
class: PHPStan\PhpDoc\PhpDocNodeResolver
arguments:
deepInspectTypes: %featureToggles.deepInspectTypes%

-
class: PHPStan\PhpDoc\PhpDocStringResolver
Expand Down Expand Up @@ -848,6 +846,11 @@ services:
-
class: PHPStan\Rules\Constants\LazyAlwaysUsedClassConstantsExtensionProvider

-
class: PHPStan\Rules\PhpDoc\UnresolvableTypeHelper
arguments:
deepInspectTypes: %featureToggles.deepInspectTypes%

-
class: PHPStan\Rules\Properties\LazyReadWritePropertiesExtensionProvider

Expand Down
34 changes: 5 additions & 29 deletions src/PhpDoc/PhpDocNodeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,11 @@
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\TemplateTagValueNode;
use PHPStan\Reflection\PassedByReference;
use PHPStan\Type\ErrorType;
use PHPStan\Rules\PhpDoc\UnresolvableTypeHelper;
use PHPStan\Type\Generic\TemplateTypeVariance;
use PHPStan\Type\MixedType;
use PHPStan\Type\NeverType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;
use PHPStan\Type\TypeTraverser;

class PhpDocNodeResolver
{
Expand All @@ -38,17 +36,17 @@ class PhpDocNodeResolver

private ConstExprNodeResolver $constExprNodeResolver;

private bool $deepInspectTypes;
private UnresolvableTypeHelper $unresolvableTypeHelper;

public function __construct(
TypeNodeResolver $typeNodeResolver,
ConstExprNodeResolver $constExprNodeResolver,
bool $deepInspectTypes = false
UnresolvableTypeHelper $unresolvableTypeHelper
)
{
$this->typeNodeResolver = $typeNodeResolver;
$this->constExprNodeResolver = $constExprNodeResolver;
$this->deepInspectTypes = $deepInspectTypes;
$this->unresolvableTypeHelper = $unresolvableTypeHelper;
}

/**
Expand Down Expand Up @@ -471,29 +469,7 @@ private function shouldSkipType(string $tagName, Type $type): bool
return false;
}

if ($this->deepInspectTypes) {
$shouldSkip = false;
TypeTraverser::map($type, static function (Type $type, callable $traverse) use (&$shouldSkip): Type {
if ($type instanceof ErrorType) {
$shouldSkip = true;
return $type;
}
if ($type instanceof NeverType && !$type->isExplicit()) {
$shouldSkip = true;
return $type;
}

return $traverse($type);
});

return $shouldSkip;
}

if ($type instanceof ErrorType) {
return true;
}

return $type instanceof NeverType && !$type->isExplicit();
return $this->unresolvableTypeHelper->containsUnresolvableType($type);
}

}
7 changes: 5 additions & 2 deletions src/PhpDoc/StubValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
use PHPStan\Rules\PhpDoc\IncompatiblePropertyPhpDocTypeRule;
use PHPStan\Rules\PhpDoc\InvalidPhpDocTagValueRule;
use PHPStan\Rules\PhpDoc\InvalidThrowsPhpDocValueRule;
use PHPStan\Rules\PhpDoc\UnresolvableTypeHelper;
use PHPStan\Rules\Properties\ExistingClassesInPropertiesRule;
use PHPStan\Rules\Properties\MissingPropertyTypehintRule;
use PHPStan\Rules\Registry;
Expand Down Expand Up @@ -122,6 +123,7 @@ private function getRuleRegistry(Container $container): Registry
$classCaseSensitivityCheck = $container->getByType(ClassCaseSensitivityCheck::class);
$functionDefinitionCheck = $container->getByType(FunctionDefinitionCheck::class);
$missingTypehintCheck = $container->getByType(MissingTypehintCheck::class);
$unresolvableTypeHelper = $container->getByType(UnresolvableTypeHelper::class);

return new Registry([
// level 0
Expand All @@ -145,9 +147,10 @@ private function getRuleRegistry(Container $container): Registry
new TraitTemplateTypeRule($fileTypeMapper, $templateTypeCheck),
new IncompatiblePhpDocTypeRule(
$fileTypeMapper,
$genericObjectTypeCheck
$genericObjectTypeCheck,
$unresolvableTypeHelper
),
new IncompatiblePropertyPhpDocTypeRule($genericObjectTypeCheck),
new IncompatiblePropertyPhpDocTypeRule($genericObjectTypeCheck, $unresolvableTypeHelper),
new InvalidPhpDocTagValueRule(
$container->getByType(Lexer::class),
$container->getByType(PhpDocParser::class)
Expand Down
10 changes: 6 additions & 4 deletions src/Rules/Classes/MixinRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,10 @@
use PHPStan\Rules\ClassNameNodePair;
use PHPStan\Rules\Generics\GenericObjectTypeCheck;
use PHPStan\Rules\MissingTypehintCheck;
use PHPStan\Rules\PhpDoc\UnresolvableTypeHelper;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
use PHPStan\Type\ErrorType;
use PHPStan\Type\FileTypeMapper;
use PHPStan\Type\NeverType;
use PHPStan\Type\VerbosityLevel;

/**
Expand All @@ -32,6 +31,8 @@ class MixinRule implements Rule

private MissingTypehintCheck $missingTypehintCheck;

private UnresolvableTypeHelper $unresolvableTypeHelper;

private bool $checkClassCaseSensitivity;

public function __construct(
Expand All @@ -40,6 +41,7 @@ public function __construct(
ClassCaseSensitivityCheck $classCaseSensitivityCheck,
GenericObjectTypeCheck $genericObjectTypeCheck,
MissingTypehintCheck $missingTypehintCheck,
UnresolvableTypeHelper $unresolvableTypeHelper,
bool $checkClassCaseSensitivity
)
{
Expand All @@ -48,6 +50,7 @@ public function __construct(
$this->classCaseSensitivityCheck = $classCaseSensitivityCheck;
$this->genericObjectTypeCheck = $genericObjectTypeCheck;
$this->missingTypehintCheck = $missingTypehintCheck;
$this->unresolvableTypeHelper = $unresolvableTypeHelper;
$this->checkClassCaseSensitivity = $checkClassCaseSensitivity;
}

Expand Down Expand Up @@ -85,8 +88,7 @@ public function processNode(Node $node, Scope $scope): array
}

if (
$type instanceof ErrorType
|| ($type instanceof NeverType && !$type->isExplicit())
$this->unresolvableTypeHelper->containsUnresolvableType($type)
) {
$errors[] = RuleErrorBuilder::message('PHPDoc tag @mixin contains unresolvable type.')->build();
continue;
Expand Down
14 changes: 7 additions & 7 deletions src/Rules/PhpDoc/IncompatiblePhpDocTypeRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,8 @@
use PHPStan\Rules\Generics\GenericObjectTypeCheck;
use PHPStan\Rules\RuleErrorBuilder;
use PHPStan\Type\ArrayType;
use PHPStan\Type\ErrorType;
use PHPStan\Type\FileTypeMapper;
use PHPStan\Type\Generic\TemplateTypeHelper;
use PHPStan\Type\NeverType;
use PHPStan\Type\Type;
use PHPStan\Type\VerbosityLevel;

Expand All @@ -25,13 +23,17 @@ class IncompatiblePhpDocTypeRule implements \PHPStan\Rules\Rule

private \PHPStan\Rules\Generics\GenericObjectTypeCheck $genericObjectTypeCheck;

private UnresolvableTypeHelper $unresolvableTypeHelper;

public function __construct(
FileTypeMapper $fileTypeMapper,
GenericObjectTypeCheck $genericObjectTypeCheck
GenericObjectTypeCheck $genericObjectTypeCheck,
UnresolvableTypeHelper $unresolvableTypeHelper
)
{
$this->fileTypeMapper = $fileTypeMapper;
$this->genericObjectTypeCheck = $genericObjectTypeCheck;
$this->unresolvableTypeHelper = $unresolvableTypeHelper;
}

public function getNodeType(): string
Expand Down Expand Up @@ -74,8 +76,7 @@ public function processNode(Node $node, Scope $scope): array
))->identifier('phpDoc.unknownParameter')->metadata(['parameterName' => $parameterName])->build();

} elseif (
$phpDocParamType instanceof ErrorType
|| ($phpDocParamType instanceof NeverType && !$phpDocParamType->isExplicit())
$this->unresolvableTypeHelper->containsUnresolvableType($phpDocParamType)
) {
$errors[] = RuleErrorBuilder::message(sprintf(
'PHPDoc tag @param for parameter $%s contains unresolvable type.',
Expand Down Expand Up @@ -136,8 +137,7 @@ public function processNode(Node $node, Scope $scope): array
$phpDocReturnType = TemplateTypeHelper::resolveToBounds($resolvedPhpDoc->getReturnTag()->getType());

if (
$phpDocReturnType instanceof ErrorType
|| ($phpDocReturnType instanceof NeverType && !$phpDocReturnType->isExplicit())
$this->unresolvableTypeHelper->containsUnresolvableType($phpDocReturnType)
) {
$errors[] = RuleErrorBuilder::message('PHPDoc tag @return contains unresolvable type.')->build();

Expand Down
13 changes: 8 additions & 5 deletions src/Rules/PhpDoc/IncompatiblePropertyPhpDocTypeRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@
use PHPStan\Rules\Generics\GenericObjectTypeCheck;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
use PHPStan\Type\ErrorType;
use PHPStan\Type\NeverType;
use PHPStan\Type\VerbosityLevel;

/**
Expand All @@ -20,9 +18,15 @@ class IncompatiblePropertyPhpDocTypeRule implements Rule

private \PHPStan\Rules\Generics\GenericObjectTypeCheck $genericObjectTypeCheck;

public function __construct(GenericObjectTypeCheck $genericObjectTypeCheck)
private UnresolvableTypeHelper $unresolvableTypeHelper;

public function __construct(
GenericObjectTypeCheck $genericObjectTypeCheck,
UnresolvableTypeHelper $unresolvableTypeHelper
)
{
$this->genericObjectTypeCheck = $genericObjectTypeCheck;
$this->unresolvableTypeHelper = $unresolvableTypeHelper;
}

public function getNodeType(): string
Expand Down Expand Up @@ -51,8 +55,7 @@ public function processNode(Node $node, Scope $scope): array

$messages = [];
if (
$phpDocType instanceof ErrorType
|| ($phpDocType instanceof NeverType && !$phpDocType->isExplicit())
$this->unresolvableTypeHelper->containsUnresolvableType($phpDocType)
) {
$messages[] = RuleErrorBuilder::message(sprintf(
'%s for property %s::$%s contains unresolvable type.',
Expand Down
9 changes: 5 additions & 4 deletions src/Rules/PhpDoc/InvalidPhpDocVarTagTypeRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,7 @@
use PHPStan\Rules\MissingTypehintCheck;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
use PHPStan\Type\ErrorType;
use PHPStan\Type\FileTypeMapper;
use PHPStan\Type\NeverType;
use PHPStan\Type\VerbosityLevel;
use function sprintf;

Expand All @@ -33,6 +31,8 @@ class InvalidPhpDocVarTagTypeRule implements Rule

private MissingTypehintCheck $missingTypehintCheck;

private UnresolvableTypeHelper $unresolvableTypeHelper;

private bool $checkClassCaseSensitivity;

private bool $checkMissingVarTagTypehint;
Expand All @@ -43,6 +43,7 @@ public function __construct(
ClassCaseSensitivityCheck $classCaseSensitivityCheck,
GenericObjectTypeCheck $genericObjectTypeCheck,
MissingTypehintCheck $missingTypehintCheck,
UnresolvableTypeHelper $unresolvableTypeHelper,
bool $checkClassCaseSensitivity,
bool $checkMissingVarTagTypehint
)
Expand All @@ -52,6 +53,7 @@ public function __construct(
$this->classCaseSensitivityCheck = $classCaseSensitivityCheck;
$this->genericObjectTypeCheck = $genericObjectTypeCheck;
$this->missingTypehintCheck = $missingTypehintCheck;
$this->unresolvableTypeHelper = $unresolvableTypeHelper;
$this->checkClassCaseSensitivity = $checkClassCaseSensitivity;
$this->checkMissingVarTagTypehint = $checkMissingVarTagTypehint;
}
Expand Down Expand Up @@ -92,8 +94,7 @@ public function processNode(Node $node, Scope $scope): array
$identifier .= sprintf(' for variable $%s', $name);
}
if (
$varTagType instanceof ErrorType
|| ($varTagType instanceof NeverType && !$varTagType->isExplicit())
$this->unresolvableTypeHelper->containsUnresolvableType($varTagType)
) {
$errors[] = RuleErrorBuilder::message(sprintf('%s contains unresolvable type.', $identifier))->line($docComment->getStartLine())->build();
continue;
Expand Down
47 changes: 47 additions & 0 deletions src/Rules/PhpDoc/UnresolvableTypeHelper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\PhpDoc;

use PHPStan\Type\ErrorType;
use PHPStan\Type\NeverType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeTraverser;

class UnresolvableTypeHelper
{

private bool $deepInspectTypes;

public function __construct(bool $deepInspectTypes)
{
$this->deepInspectTypes = $deepInspectTypes;
}

public function containsUnresolvableType(Type $type): bool
{
if ($this->deepInspectTypes) {
$containsUnresolvable = false;
TypeTraverser::map($type, static function (Type $type, callable $traverse) use (&$containsUnresolvable): Type {
if ($type instanceof ErrorType) {
$containsUnresolvable = true;
return $type;
}
if ($type instanceof NeverType && !$type->isExplicit()) {
$containsUnresolvable = true;
return $type;
}

return $traverse($type);
});

return $containsUnresolvable;
}

if ($type instanceof ErrorType) {
return true;
}

return $type instanceof NeverType && !$type->isExplicit();
}

}
2 changes: 2 additions & 0 deletions tests/PHPStan/Rules/Classes/MixinRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use PHPStan\Rules\ClassCaseSensitivityCheck;
use PHPStan\Rules\Generics\GenericObjectTypeCheck;
use PHPStan\Rules\MissingTypehintCheck;
use PHPStan\Rules\PhpDoc\UnresolvableTypeHelper;
use PHPStan\Rules\Rule;
use PHPStan\Testing\RuleTestCase;
use PHPStan\Type\FileTypeMapper;
Expand All @@ -25,6 +26,7 @@ protected function getRule(): Rule
new ClassCaseSensitivityCheck($reflectionProvider),
new GenericObjectTypeCheck(),
new MissingTypehintCheck($reflectionProvider, true, true, true),
new UnresolvableTypeHelper(true),
true
);
}
Expand Down
22 changes: 21 additions & 1 deletion tests/PHPStan/Rules/PhpDoc/IncompatiblePhpDocTypeRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,15 @@
class IncompatiblePhpDocTypeRuleTest extends \PHPStan\Testing\RuleTestCase
{

/** @var bool */
private $deepInspectTypes = false;

protected function getRule(): \PHPStan\Rules\Rule
{
return new IncompatiblePhpDocTypeRule(
self::getContainer()->getByType(FileTypeMapper::class),
new GenericObjectTypeCheck()
new GenericObjectTypeCheck(),
new UnresolvableTypeHelper($this->deepInspectTypes)
);
}

Expand Down Expand Up @@ -150,4 +154,20 @@ public function testBug4643(): void
$this->analyse([__DIR__ . '/data/bug-4643.php'], []);
}

public function testBug3753(): void
{
$this->deepInspectTypes = true;
$this->analyse([__DIR__ . '/data/bug-3753.php'], [
[
'PHPDoc tag @param for parameter $foo contains unresolvable type.',
20,
],
]);
}

public function testBug3753NotDeepInspectTypes(): void
{
$this->analyse([__DIR__ . '/data/bug-3753.php'], []);
}

}
Loading

0 comments on commit 1de5de8

Please sign in to comment.