Skip to content

Commit

Permalink
Parameter name remapping when inheriting phpDocs
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Jan 3, 2020
1 parent 86ec127 commit ae238a1
Show file tree
Hide file tree
Showing 7 changed files with 301 additions and 19 deletions.
15 changes: 14 additions & 1 deletion src/Analyser/NodeScopeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -2535,12 +2535,22 @@ public function getPhpDocs(Scope $scope, Node\FunctionLike $functionLike): array
throw new \PHPStan\ShouldNotHappenException();
}
$functionName = $functionLike->name->name;
$positionalParameterNames = array_map(static function (Node\Param $param): string {
if (!$param->var instanceof Variable || !is_string($param->var->name)) {
throw new \PHPStan\ShouldNotHappenException();
}

return $param->var->name;
}, $functionLike->getParams());
$phpDocBlock = PhpDocBlock::resolvePhpDocBlockForMethod(
$docComment,
$scope->getClassReflection(),
$trait,
$functionLike->name->name,
$file
$file,
null,
$positionalParameterNames,
$positionalParameterNames
);

if ($phpDocBlock !== null) {
Expand Down Expand Up @@ -2572,6 +2582,9 @@ public function getPhpDocs(Scope $scope, Node\FunctionLike $functionLike): array
}
return $tag->getType();
}, $resolvedPhpDoc->getParamTags());
if ($phpDocBlock !== null) {
$phpDocParameterTypes = $phpDocBlock->transformArrayKeysWithParameterNameMapping($phpDocParameterTypes);
}
$nativeReturnType = $scope->getFunctionType($functionLike->getReturnType(), false, false);
$phpDocReturnType = $this->getPhpDocReturnType($phpDocBlock, $resolvedPhpDoc, $nativeReturnType);
$phpDocThrowType = $resolvedPhpDoc->getThrowsTag() !== null ? $resolvedPhpDoc->getThrowsTag()->getType() : null;
Expand Down
156 changes: 141 additions & 15 deletions src/PhpDoc/PhpDocBlock.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,32 @@ class PhpDocBlock
/** @var bool */
private $explicit;

/** @var array<string, string> */
private $parameterNameMapping;

/**
* @param string $docComment
* @param string $file
* @param \PHPStan\Reflection\ClassReflection $classReflection
* @param string|null $trait
* @param bool $explicit
* @param array<string, string> $parameterNameMapping
*/
private function __construct(
string $docComment,
string $file,
ClassReflection $classReflection,
?string $trait,
bool $explicit
bool $explicit,
array $parameterNameMapping
)
{
$this->docComment = $docComment;
$this->file = $file;
$this->classReflection = $classReflection;
$this->trait = $trait;
$this->explicit = $explicit;
$this->parameterNameMapping = $parameterNameMapping;
}

public function getDocComment(): string
Expand Down Expand Up @@ -66,13 +79,44 @@ public function isExplicit(): bool
return $this->explicit;
}

/**
* @template T
* @param array<string, T> $array
* @return array<string, T>
*/
public function transformArrayKeysWithParameterNameMapping(array $array): array
{
$newArray = [];
foreach ($array as $key => $value) {
if (!array_key_exists($key, $this->parameterNameMapping)) {
continue;
}
$newArray[$this->parameterNameMapping[$key]] = $value;
}

return $newArray;
}

/**
* @param string|null $docComment
* @param \PHPStan\Reflection\ClassReflection $classReflection
* @param string|null $trait
* @param string $propertyName
* @param string $file
* @param bool|null $explicit
* @param array<int, string> $originalPositionalParameterNames
* @param array<int, string> $newPositionalParameterNames
* @return self|null
*/
public static function resolvePhpDocBlockForProperty(
?string $docComment,
ClassReflection $classReflection,
?string $trait,
string $propertyName,
string $file,
?bool $explicit = null
?bool $explicit,
array $originalPositionalParameterNames, // unused
array $newPositionalParameterNames // unused
): ?self
{
return self::resolvePhpDocBlock(
Expand All @@ -84,17 +128,32 @@ public static function resolvePhpDocBlockForProperty(
'hasNativeProperty',
'getNativeProperty',
__FUNCTION__,
$explicit
$explicit,
[],
[]
);
}

/**
* @param string|null $docComment
* @param \PHPStan\Reflection\ClassReflection $classReflection
* @param string|null $trait
* @param string $methodName
* @param string $file
* @param bool|null $explicit
* @param array<int, string> $originalPositionalParameterNames
* @param array<int, string> $newPositionalParameterNames
* @return self|null
*/
public static function resolvePhpDocBlockForMethod(
?string $docComment,
ClassReflection $classReflection,
?string $trait,
string $methodName,
string $file,
?bool $explicit = null
?bool $explicit,
array $originalPositionalParameterNames,
array $newPositionalParameterNames
): ?self
{
return self::resolvePhpDocBlock(
Expand All @@ -106,10 +165,26 @@ public static function resolvePhpDocBlockForMethod(
'hasNativeMethod',
'getNativeMethod',
__FUNCTION__,
$explicit
$explicit,
$originalPositionalParameterNames,
$newPositionalParameterNames
);
}

/**
* @param string|null $docComment
* @param \PHPStan\Reflection\ClassReflection $classReflection
* @param string|null $trait
* @param string $name
* @param string $file
* @param string $hasMethodName
* @param string $getMethodName
* @param string $resolveMethodName
* @param bool|null $explicit
* @param array<int, string> $originalPositionalParameterNames
* @param array<int, string> $newPositionalParameterNames
* @return self|null
*/
private static function resolvePhpDocBlock(
?string $docComment,
ClassReflection $classReflection,
Expand All @@ -119,7 +194,9 @@ private static function resolvePhpDocBlock(
string $hasMethodName,
string $getMethodName,
string $resolveMethodName,
?bool $explicit
?bool $explicit,
array $originalPositionalParameterNames,
array $newPositionalParameterNames
): ?self
{
if (
Expand All @@ -135,7 +212,8 @@ private static function resolvePhpDocBlock(
$hasMethodName,
$getMethodName,
$resolveMethodName,
$explicit ?? $docComment !== null
$explicit ?? $docComment !== null,
$originalPositionalParameterNames
);
if ($phpDocBlockFromClass !== null) {
return $phpDocBlockFromClass;
Expand All @@ -149,27 +227,48 @@ private static function resolvePhpDocBlock(
$hasMethodName,
$getMethodName,
$resolveMethodName,
$explicit ?? $docComment !== null
$explicit ?? $docComment !== null,
$originalPositionalParameterNames
);
if ($phpDocBlockFromClass !== null) {
return $phpDocBlockFromClass;
}
}
}

$parameterNameMapping = [];
foreach ($originalPositionalParameterNames as $i => $parameterName) {
if (!array_key_exists($i, $newPositionalParameterNames)) {
continue;
}
$parameterNameMapping[$newPositionalParameterNames[$i]] = $parameterName;
}

return $docComment !== null
? new self($docComment, $file, $classReflection, $trait, $explicit ?? true)
? new self($docComment, $file, $classReflection, $trait, $explicit ?? true, $parameterNameMapping)
: null;
}

/**
* @param \PHPStan\Reflection\ClassReflection $classReflection
* @param string|null $trait
* @param string $name
* @param string $hasMethodName
* @param string $getMethodName
* @param string $resolveMethodName
* @param bool $explicit
* @param array<int, string> $positionalParameterNames $positionalParameterNames
* @return self|null
*/
private static function resolvePhpDocBlockRecursive(
ClassReflection $classReflection,
?string $trait,
string $name,
string $hasMethodName,
string $getMethodName,
string $resolveMethodName,
bool $explicit
bool $explicit,
array $positionalParameterNames = []
): ?self
{
$phpDocBlockFromClass = self::resolvePhpDocBlockFromClass(
Expand All @@ -178,7 +277,8 @@ private static function resolvePhpDocBlockRecursive(
$hasMethodName,
$getMethodName,
$resolveMethodName,
$explicit
$explicit,
$positionalParameterNames
);

if ($phpDocBlockFromClass !== null) {
Expand All @@ -194,20 +294,32 @@ private static function resolvePhpDocBlockRecursive(
$hasMethodName,
$getMethodName,
$resolveMethodName,
$explicit
$explicit,
$positionalParameterNames
);
}

return null;
}

/**
* @param \PHPStan\Reflection\ClassReflection $classReflection
* @param string $name
* @param string $hasMethodName
* @param string $getMethodName
* @param string $resolveMethodName
* @param bool $explicit
* @param array<int, string> $positionalParameterNames
* @return self|null
*/
private static function resolvePhpDocBlockFromClass(
ClassReflection $classReflection,
string $name,
string $hasMethodName,
string $getMethodName,
string $resolveMethodName,
bool $explicit
bool $explicit,
array $positionalParameterNames
): ?self
{
if ($classReflection->getFileNameWithPhpDocs() !== null && $classReflection->$hasMethodName($name)) {
Expand All @@ -223,10 +335,22 @@ private static function resolvePhpDocBlockFromClass(
return null;
}

if ($parentReflection instanceof PhpPropertyReflection || $parentReflection instanceof ResolvedPropertyReflection || $parentReflection instanceof PhpMethodReflection || $parentReflection instanceof ResolvedMethodReflection) {
if ($parentReflection instanceof PhpPropertyReflection || $parentReflection instanceof ResolvedPropertyReflection) {
$traitReflection = $parentReflection->getDeclaringTrait();
$positionalMethodParameterNames = [];
} elseif ($parentReflection instanceof PhpMethodReflection || $parentReflection instanceof ResolvedMethodReflection) {
$traitReflection = $parentReflection->getDeclaringTrait();
$methodVariants = $parentReflection->getVariants();
$positionalMethodParameterNames = [];
if (count($methodVariants) === 1) {
$methodParameters = $methodVariants[0]->getParameters();
foreach ($methodParameters as $methodParameter) {
$positionalMethodParameterNames[] = $methodParameter->getName();
}
}
} else {
$traitReflection = null;
$positionalMethodParameterNames = [];
}

$trait = $traitReflection !== null
Expand All @@ -240,7 +364,9 @@ private static function resolvePhpDocBlockFromClass(
$trait,
$name,
$classReflection->getFileNameWithPhpDocs(),
$explicit
$explicit,
$positionalParameterNames,
$positionalMethodParameterNames
);
}
}
Expand Down
18 changes: 15 additions & 3 deletions src/Reflection/Php/PhpClassReflectionExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,10 @@ private function createProperty(
$declaringClassReflection,
null,
$propertyName,
$declaringClassReflection->getFileName()
$declaringClassReflection->getFileName(),
null,
[],
[]
);
if ($phpDocBlock !== null) {
$declaringTraitName = $this->findPropertyTrait(
Expand Down Expand Up @@ -488,16 +491,22 @@ private function createMethod(
$declaringTraitName = $this->findMethodTrait($methodReflection);
$resolvedPhpDoc = $this->findMethodPhpDocIncludingAncestors($declaringClassName, $methodReflection->getName());
$stubPhpDocString = null;
$phpDocBlock = null;
if ($resolvedPhpDoc === null) {
if ($declaringClass->getFileName() !== false) {
$docComment = $methodReflection->getDocComment();

$positionalParameterNames = array_map(static function (\ReflectionParameter $parameter): string {
return $parameter->getName();
}, $methodReflection->getParameters());
$phpDocBlock = PhpDocBlock::resolvePhpDocBlockForMethod(
$docComment,
$declaringClass,
$declaringTraitName,
$methodReflection->getName(),
$declaringClass->getFileName()
$declaringClass->getFileName(),
null,
$positionalParameterNames,
$positionalParameterNames
);

if ($phpDocBlock !== null) {
Expand Down Expand Up @@ -544,6 +553,9 @@ private function createMethod(
$phpDocBlockClassReflection->getActiveTemplateTypeMap()
);
}, $resolvedPhpDoc->getParamTags());
if ($phpDocBlock !== null) {
$phpDocParameterTypes = $phpDocBlock->transformArrayKeysWithParameterNameMapping($phpDocParameterTypes);
}
$nativeReturnType = TypehintHelper::decideTypeFromReflection(
$methodReflection->getReturnType(),
null,
Expand Down
6 changes: 6 additions & 0 deletions tests/PHPStan/Analyser/NodeScopeResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9778,6 +9778,11 @@ public function dataBug2740(): array
return $this->gatherAssertTypes(__DIR__ . '/data/bug-2740.php');
}

public function dataPhpDocInheritanceParameterRemapping(): array
{
return $this->gatherAssertTypes(__DIR__ . '/data/inheritdoc-parameter-remapping.php');
}

/**
* @dataProvider dataBug2574
* @dataProvider dataBug2577
Expand All @@ -9797,6 +9802,7 @@ public function dataBug2740(): array
* @dataProvider dataComplexGenericsExample
* @dataProvider dataBug2648
* @dataProvider dataBug2740
* @dataProvider dataPhpDocInheritanceParameterRemapping
* @param ConstantStringType $expectedType
* @param Type $actualType
*/
Expand Down
Loading

0 comments on commit ae238a1

Please sign in to comment.