Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 3 additions & 7 deletions src/PhpDoc/StubValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ private function getRuleRegistry(Container $container): RuleRegistry
$phpVersion = $container->getByType(PhpVersion::class);
$localTypeAliasesCheck = $container->getByType(LocalTypeAliasesCheck::class);
$phpClassReflectionExtension = $container->getByType(PhpClassReflectionExtension::class);
$genericCallableRuleHelper = $container->getByType(GenericCallableRuleHelper::class);

$rules = [
// level 0
Expand All @@ -182,13 +183,8 @@ private function getRuleRegistry(Container $container): RuleRegistry
new MethodTemplateTypeRule($fileTypeMapper, $templateTypeCheck),
new MethodSignatureVarianceRule($varianceCheck),
new TraitTemplateTypeRule($fileTypeMapper, $templateTypeCheck),
new IncompatiblePhpDocTypeRule(
$fileTypeMapper,
$genericObjectTypeCheck,
$unresolvableTypeHelper,
$container->getByType(GenericCallableRuleHelper::class),
),
new IncompatiblePropertyPhpDocTypeRule($genericObjectTypeCheck, $unresolvableTypeHelper),
new IncompatiblePhpDocTypeRule($fileTypeMapper, $genericObjectTypeCheck, $unresolvableTypeHelper, $genericCallableRuleHelper),
new IncompatiblePropertyPhpDocTypeRule($genericObjectTypeCheck, $unresolvableTypeHelper, $genericCallableRuleHelper),
new InvalidPhpDocTagValueRule(
$container->getByType(Lexer::class),
$container->getByType(PhpDocParser::class),
Expand Down
33 changes: 19 additions & 14 deletions src/Rules/PhpDoc/GenericCallableRuleHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public function check(
Scope $scope,
string $location,
Type $callableType,
string $functionName,
?string $functionName,
array $functionTemplateTags,
?ClassReflection $classReflection,
): array
Expand All @@ -64,26 +64,31 @@ public function check(

$templateTags = $type->getTemplateTags();

$functionDescription = sprintf('function %s', $functionName);
$classDescription = null;
if ($classReflection !== null) {
$classDescription = $classReflection->getDisplayName();
$functionDescription = sprintf('method %s::%s', $classDescription, $functionName);
}

foreach (array_keys($functionTemplateTags) as $name) {
if (!isset($templateTags[$name])) {
continue;
if ($functionName !== null) {
$functionDescription = sprintf('function %s', $functionName);
if ($classReflection !== null) {
$functionDescription = sprintf('method %s::%s', $classDescription, $functionName);
}

$errors[] = RuleErrorBuilder::message(sprintf(
'PHPDoc tag %s template %s of %s shadows @template %s for %s.',
$location,
$name,
$typeDescription,
$name,
$functionDescription,
))->build();
foreach (array_keys($functionTemplateTags) as $name) {
if (!isset($templateTags[$name])) {
continue;
}

$errors[] = RuleErrorBuilder::message(sprintf(
'PHPDoc tag %s template %s of %s shadows @template %s for %s.',
$location,
$name,
$typeDescription,
$name,
$functionDescription,
))->build();
}
}

if ($classReflection !== null) {
Expand Down
13 changes: 13 additions & 0 deletions src/Rules/PhpDoc/IncompatiblePropertyPhpDocTypeRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class IncompatiblePropertyPhpDocTypeRule implements Rule
public function __construct(
private GenericObjectTypeCheck $genericObjectTypeCheck,
private UnresolvableTypeHelper $unresolvableTypeHelper,
private GenericCallableRuleHelper $genericCallableRuleHelper,
)
{
}
Expand Down Expand Up @@ -93,6 +94,18 @@ public function processNode(Node $node, Scope $scope): array
$className = SprintfHelper::escapeFormatString($classReflection->getDisplayName());
$escapedPropertyName = SprintfHelper::escapeFormatString($propertyName);

if ($node->isPromoted() === false) {
$messages = array_merge($messages, $this->genericCallableRuleHelper->check(
$node,
$scope,
'@var',
$phpDocType,
null,
[],
$classReflection,
));
}

$messages = array_merge($messages, $this->genericObjectTypeCheck->check(
$phpDocType,
sprintf(
Expand Down
4 changes: 4 additions & 0 deletions tests/PHPStan/Rules/PhpDoc/IncompatiblePhpDocTypeRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,10 @@ public function testGenericCallables(): void
'PHPDoc tag @return template T of Closure<T of mixed>(T): T shadows @template T for function GenericCallablesIncompatible\shadowsReturnArray.',
191,
],
[
'PHPDoc tag @param for parameter $shadows template T of Closure<T of mixed>(T): T shadows @template T for class GenericCallablesIncompatible\Test3.',
203,
],
]);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@

namespace PHPStan\Rules\PhpDoc;

use PHPStan\Rules\ClassCaseSensitivityCheck;
use PHPStan\Rules\ClassForbiddenNameCheck;
use PHPStan\Rules\ClassNameCheck;
use PHPStan\Rules\Generics\GenericObjectTypeCheck;
use PHPStan\Rules\Generics\TemplateTypeCheck;
use PHPStan\Rules\Rule;
use PHPStan\Testing\RuleTestCase;

Expand All @@ -14,9 +18,24 @@ class IncompatiblePropertyPhpDocTypeRuleTest extends RuleTestCase

protected function getRule(): Rule
{
$reflectionProvider = $this->createReflectionProvider();
$typeAliasResolver = $this->createTypeAliasResolver(['TypeAlias' => 'int'], $reflectionProvider);

return new IncompatiblePropertyPhpDocTypeRule(
new GenericObjectTypeCheck(),
new UnresolvableTypeHelper(),
new GenericCallableRuleHelper(
new TemplateTypeCheck(
$reflectionProvider,
new ClassNameCheck(
new ClassCaseSensitivityCheck($reflectionProvider, true),
new ClassForbiddenNameCheck(),
),
new GenericObjectTypeCheck(),
$typeAliasResolver,
true,
),
),
);
}

Expand Down Expand Up @@ -150,4 +169,30 @@ public function testBug7240(): void
$this->analyse([__DIR__ . '/data/bug-7240.php'], []);
}

public function testGenericCallables(): void
{
$this->analyse([__DIR__ . '/data/generic-callable-properties.php'], [
[
'PHPDoc tag @var template T of Closure<T of mixed>(T): T shadows @template T for class GenericCallableProperties\Test.',
16,
],
[
'PHPDoc tag @var template of Closure<stdClass of mixed>(stdClass): stdClass cannot have existing class stdClass as its name.',
21,
],
[
'PHPDoc tag @var template of callable<TypeAlias of mixed>(TypeAlias): TypeAlias cannot have existing type alias TypeAlias as its name.',
26,
],
[
'PHPDoc tag @var template TNull of callable<TNull of null>(TNull): TNull with bound type null is not supported.',
31,
],
[
'PHPDoc tag @var template TInvalid of callable<TInvalid of GenericCallableProperties\Invalid>(TInvalid): TInvalid has invalid bound type GenericCallableProperties\Invalid.',
36,
],
]);
}

}
42 changes: 42 additions & 0 deletions tests/PHPStan/Rules/PhpDoc/data/generic-callable-properties.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php declare(strict_types=1); // lint >= 8.0

namespace GenericCallableProperties;

use Closure;
use stdClass;

/**
* @template T
*/
class Test
{
/**
* @var Closure<T>(T): T
*/
private Closure $shadows;

/**
* @var Closure<stdClass>(stdClass): stdClass
*/
private Closure $existingClass;

/**
* @var callable<TypeAlias>(TypeAlias): TypeAlias
*/
private $typeAlias;

/**
* @var callable<TNull of null>(TNull): TNull
*/
private $unsupported;

/**
* @var callable<TInvalid of Invalid>(TInvalid): TInvalid
*/
private $invalid;

/**
* @param Closure<T>(T): T $notReported
*/
public function __construct(private Closure $notReported) {}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?php declare(strict_types=1);
<?php declare(strict_types=1); // lint > 8.0

namespace GenericCallablesIncompatible;

Expand Down Expand Up @@ -191,3 +191,14 @@ function shadowsParamOutArray(array &$existingClasses): void
function shadowsReturnArray(): array
{
}

/**
* @template T
*/
class Test3
{
/**
* @param Closure<T>(T): T $shadows
*/
public function __construct(private Closure $shadows) {}
}