Skip to content

Commit

Permalink
Add resolution of generic @method tags
Browse files Browse the repository at this point in the history
  • Loading branch information
mad-briller authored Feb 23, 2024
1 parent 97408e6 commit e9b40b7
Show file tree
Hide file tree
Showing 6 changed files with 72 additions and 1 deletion.
22 changes: 22 additions & 0 deletions src/PhpDoc/PhpDocNodeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@
use PHPStan\PhpDocParser\Ast\PhpDoc\TemplateTagValueNode;
use PHPStan\Reflection\PassedByReference;
use PHPStan\Rules\PhpDoc\UnresolvableTypeHelper;
use PHPStan\Type\Generic\TemplateTypeFactory;
use PHPStan\Type\Generic\TemplateTypeMap;
use PHPStan\Type\Generic\TemplateTypeScope;
use PHPStan\Type\Generic\TemplateTypeVariance;
use PHPStan\Type\MixedType;
use PHPStan\Type\Type;
Expand Down Expand Up @@ -160,6 +163,24 @@ public function resolveMethodTags(PhpDocNode $phpDocNode, NameScope $nameScope):

foreach (['@method', '@psalm-method', '@phpstan-method'] as $tagName) {
foreach ($phpDocNode->getMethodTagValues($tagName) as $tagValue) {
$templateTags = [];

if (count($tagValue->templateTypes) > 0 && $nameScope->getClassName() !== null) {
foreach ($tagValue->templateTypes as $templateType) {
$templateTags[$templateType->name] = new TemplateTag(
$templateType->name,
$templateType->bound !== null
? $this->typeNodeResolver->resolve($templateType->bound, $nameScope)
: new MixedType(),
TemplateTypeVariance::createInvariant(),
);
}

$templateTypeScope = TemplateTypeScope::createWithMethod($nameScope->getClassName(), $tagValue->methodName);
$templateTypeMap = new TemplateTypeMap(array_map(static fn (TemplateTag $tag): Type => TemplateTypeFactory::fromTemplateTag($templateTypeScope, $tag), $templateTags));
$nameScope = $nameScope->withTemplateTypeMap($templateTypeMap);
}

$parameters = [];
foreach ($tagValue->parameters as $parameterNode) {
$parameterName = substr($parameterNode->parameterName, 1);
Expand Down Expand Up @@ -191,6 +212,7 @@ public function resolveMethodTags(PhpDocNode $phpDocNode, NameScope $nameScope):
: new MixedType(),
$tagValue->isStatic,
$parameters,
$templateTags,
);
}
}
Expand Down
10 changes: 10 additions & 0 deletions src/PhpDoc/Tag/MethodTag.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ class MethodTag

/**
* @param array<string, MethodTagParameter> $parameters
* @param array<string, TemplateTag> $templateTags
*/
public function __construct(
private Type $returnType,
private bool $isStatic,
private array $parameters,
private array $templateTags = [],
)
{
}
Expand All @@ -37,4 +39,12 @@ public function getParameters(): array
return $this->parameters;
}

/**
* @return array<string, TemplateTag>
*/
public function getTemplateTags(): array
{
return $this->templateTags;
}

}
3 changes: 2 additions & 1 deletion src/Reflection/Annotations/AnnotationMethodReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public function __construct(
private bool $isStatic,
private bool $isVariadic,
private ?Type $throwType,
private TemplateTypeMap $templateTypeMap,
)
{
}
Expand Down Expand Up @@ -69,7 +70,7 @@ public function getVariants(): array
if ($this->variants === null) {
$this->variants = [
new FunctionVariantWithPhpDocs(
TemplateTypeMap::createEmpty(),
$this->templateTypeMap,
null,
$this->parameters,
$this->isVariadic,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,18 @@

namespace PHPStan\Reflection\Annotations;

use PHPStan\PhpDoc\Tag\TemplateTag;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\ExtendedMethodReflection;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Reflection\MethodsClassReflectionExtension;
use PHPStan\Type\Generic\TemplateTypeFactory;
use PHPStan\Type\Generic\TemplateTypeHelper;
use PHPStan\Type\Generic\TemplateTypeMap;
use PHPStan\Type\Generic\TemplateTypeScope;
use PHPStan\Type\Generic\TemplateTypeVariance;
use PHPStan\Type\Type;
use function array_map;
use function count;

class AnnotationsMethodsClassReflectionExtension implements MethodsClassReflectionExtension
Expand Down Expand Up @@ -57,6 +63,13 @@ private function findClassReflectionWithMethod(
);
}

$templateTypeScope = TemplateTypeScope::createWithClass($classReflection->getName());

$templateTypeMap = new TemplateTypeMap(array_map(
static fn (TemplateTag $tag): Type => TemplateTypeFactory::fromTemplateTag($templateTypeScope, $tag),
$methodTags[$methodName]->getTemplateTags(),
));

$isStatic = $methodTags[$methodName]->isStatic();
$nativeCallMethodName = $isStatic ? '__callStatic' : '__call';

Expand All @@ -75,6 +88,7 @@ private function findClassReflectionWithMethod(
$classReflection->hasNativeMethod($nativeCallMethodName)
? $classReflection->getNativeMethod($nativeCallMethodName)->getThrowType()
: null,
$templateTypeMap,
);
}

Expand Down
1 change: 1 addition & 0 deletions tests/PHPStan/Analyser/NodeScopeResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public function dataFileAsserts(): iterable
yield from $this->gatherAssertTypes(__DIR__ . '/data/json-decode/narrow_type_with_force_array.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/json-decode/invalid_type.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/json-decode/json_object_as_array.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/generic-method-tags.php');

require_once __DIR__ . '/data/bug2574.php';

Expand Down
23 changes: 23 additions & 0 deletions tests/PHPStan/Analyser/data/generic-method-tags.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

namespace GenericMethodTags;

use function PHPStan\Testing\assertType;

/**
* @method TVal doThing<TVal of mixed>(TVal $param)
*/
class Test
{
public function __call(): mixed
{
}
}

function test(int $int, string $string): void
{
$test = new Test();

assertType('int', $test->doThing($int));
assertType('string', $test->doThing($string));
}

0 comments on commit e9b40b7

Please sign in to comment.