add mixed type

This commit is contained in:
Tomas Votruba 2019-09-03 08:41:22 +02:00
parent cf1514fad6
commit 57db278625
13 changed files with 102 additions and 56 deletions

View File

@ -10,6 +10,7 @@ use PHPStan\Type\ClosureType;
use PHPStan\Type\FloatType;
use PHPStan\Type\IntegerType;
use PHPStan\Type\IntersectionType;
use PHPStan\Type\MixedType;
use PHPStan\Type\NullType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\ObjectWithoutClassType;
@ -36,6 +37,7 @@ final class StaticTypeToStringResolver
BooleanType::class => ['bool'],
StringType::class => ['string'],
NullType::class => ['null'],
MixedType::class => ['mixed'],
// more complex callables
function (ArrayType $arrayType): array {
@ -69,8 +71,7 @@ final class StaticTypeToStringResolver
return $this->removeGenericArrayTypeIfThereIsSpecificArrayType($types);
},
function (ObjectType $objectType): array {
// the must be absolute, since we have no other way to check absolute/local path
return ['\\' . $objectType->getClassName()];
return [$objectType->getClassName()];
},
];

View File

@ -31,12 +31,12 @@ final class ParamPhpDocNodeFactory
if (count($types) > 1) {
$unionedTypes = [];
foreach ($types as $type) {
$unionedTypes[] = new IdentifierTypeNode($type);
$unionedTypes[] = $this->createIdentifierTypeNode($type);
}
$typeNode = new UnionTypeNode($unionedTypes);
} elseif (count($types) === 1) {
$typeNode = new IdentifierTypeNode($types[0]);
$typeNode = $this->createIdentifierTypeNode($types[0]);
} else {
throw new ShouldNotHappenException(__METHOD__ . '() on line ' . __LINE__);
}
@ -53,4 +53,14 @@ final class ParamPhpDocNodeFactory
return new AttributeAwarePhpDocTagNode('@param', $paramTagValueNode);
}
private function createIdentifierTypeNode(string $type): IdentifierTypeNode
{
if (class_exists($type)) {
// FQN class name
$type = '\\' . $type;
}
return new IdentifierTypeNode($type);
}
}

View File

@ -99,14 +99,11 @@ CODE_SAMPLE
*/
public function refactor(Node $node): ?Node
{
/** @var Param[] $params */
$params = (array) $node->params;
if (count($params) === 0) {
if (count($node->getParams()) === 0) {
return null;
}
foreach ($params as $param) {
foreach ($node->getParams() as $param) {
if ($this->shouldSkipParam($param)) {
return null;
}
@ -116,7 +113,8 @@ CODE_SAMPLE
return null;
}
$this->docBlockManipulator->addTag($node, $this->paramPhpDocNodeFactory->create($types, $param));
$paramTagNode = $this->paramPhpDocNodeFactory->create($types, $param);
$this->docBlockManipulator->addTag($node, $paramTagNode);
}
return $node;

View File

@ -90,8 +90,8 @@ CODE_SAMPLE
}
$inferedReturnTypes = $this->returnTypeInferer->inferFunctionLike($node);
$returnTypeInfo = new ReturnTypeInfo($inferedReturnTypes, $this->typeAnalyzer, $inferedReturnTypes);
$returnTypeInfo = new ReturnTypeInfo($inferedReturnTypes, $this->typeAnalyzer, $inferedReturnTypes);
$returnTypeNode = $returnTypeInfo->getFqnTypeNode();
if ($returnTypeNode === null) {
return null;

View File

@ -115,23 +115,27 @@ CODE_SAMPLE
return null;
}
$shouldPopulateChildren = false;
// should be previous overridden?
if ($node->returnType !== null && $returnTypeInfo->getFqnTypeNode() !== null) {
$isSubtype = $this->isSubtypeOf($returnTypeInfo->getFqnTypeNode(), $node->returnType);
// @see https://wiki.php.net/rfc/covariant-returns-and-contravariant-parameters
if ($this->isAtLeastPhpVersion('7.4') && $isSubtype) {
$shouldPopulateChildren = true;
$node->returnType = $returnTypeInfo->getFqnTypeNode();
} elseif ($isSubtype === false) {
$node->returnType = $returnTypeInfo->getFqnTypeNode();
}
} else {
if ($returnTypeInfo->getFqnTypeNode() !== null) {
} elseif ($isSubtype === false) { // type override
$shouldPopulateChildren = true;
$node->returnType = $returnTypeInfo->getFqnTypeNode();
}
} elseif ($returnTypeInfo->getFqnTypeNode() !== null) {
$shouldPopulateChildren = true;
$node->returnType = $returnTypeInfo->getFqnTypeNode();
}
$this->populateChildren($node, $returnTypeInfo);
if ($shouldPopulateChildren) {
$this->populateChildren($node, $returnTypeInfo);
}
return $node;
}
@ -200,16 +204,16 @@ CODE_SAMPLE
*/
private function shouldSkip(Node $node): bool
{
if (! $node instanceof ClassMethod) {
return false;
}
if ($this->overrideExistingReturnTypes === false) {
if ($node->returnType) {
return true;
}
}
if (! $node instanceof ClassMethod) {
return false;
}
return $this->isNames($node, self::EXCLUDED_METHOD_NAMES);
}

View File

@ -10,7 +10,6 @@ use Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer\DocBlockManipulator;
use Rector\Rector\AbstractRector;
use Rector\RectorDefinition\RectorDefinition;
use Rector\TypeDeclaration\TypeInferer\PropertyTypeInferer;
use Rector\TypeDeclaration\ValueObject\IdentifierValueObject;
/**
* @sponsor Thanks https://spaceflow.io/ for sponsoring this rule - visit them on https://github.com/SpaceFlow-app
@ -61,20 +60,10 @@ final class PropertyTypeDeclarationRector extends AbstractRector
$types = $this->propertyTypeInferer->inferProperty($node);
if ($types) {
$this->setNodeVarTypes($node, $types);
$this->docBlockManipulator->changeVarTag($node, $types);
return $node;
}
return null;
}
/**
* @param string[]|IdentifierValueObject[] $varTypes
*/
private function setNodeVarTypes(Node $node, array $varTypes): Node
{
$this->docBlockManipulator->changeVarTag($node, $varTypes);
return $node;
}
}

View File

@ -26,9 +26,9 @@ final class PropertyTypeInferer extends AbstractPriorityAwareTypeInferer
*/
public function inferProperty(Property $property): array
{
foreach ($this->propertyTypeInferers as $propertyTypeInferers) {
$types = $propertyTypeInferers->inferProperty($property);
if ($types !== []) {
foreach ($this->propertyTypeInferers as $propertyTypeInferer) {
$types = $propertyTypeInferer->inferProperty($property);
if ($types !== [] && $types !== ['mixed']) {
return $types;
}
}

View File

@ -58,7 +58,7 @@ final class GetterPropertyTypeInferer extends AbstractTypeInferer implements Pro
}
$returnTypes = $this->inferClassMethodReturnTypes($classMethod);
if ($returnTypes !== []) {
if ($returnTypes !== [] && $returnTypes !== ['mixed']) {
return $returnTypes;
}
}
@ -107,7 +107,7 @@ final class GetterPropertyTypeInferer extends AbstractTypeInferer implements Pro
}
$inferedTypes = $this->returnedNodesReturnTypeInferer->inferFunctionLike($classMethod);
if ($inferedTypes) {
if ($inferedTypes !== [] && $inferedTypes !== ['mixed']) {
return $inferedTypes;
}

View File

@ -3,6 +3,7 @@
namespace Rector\TypeDeclaration\TypeInferer;
use PhpParser\Node\FunctionLike;
use Rector\Exception\ShouldNotHappenException;
use Rector\TypeDeclaration\Contract\TypeInferer\ReturnTypeInfererInterface;
final class ReturnTypeInferer extends AbstractPriorityAwareTypeInferer
@ -25,14 +26,7 @@ final class ReturnTypeInferer extends AbstractPriorityAwareTypeInferer
*/
public function inferFunctionLike(FunctionLike $functionLike): array
{
foreach ($this->returnTypeInferers as $returnTypeInferer) {
$types = $returnTypeInferer->inferFunctionLike($functionLike);
if ($types !== []) {
return $types;
}
}
return [];
return $this->inferFunctionLikeWithExcludedInferers($functionLike, []);
}
/**
@ -42,18 +36,43 @@ final class ReturnTypeInferer extends AbstractPriorityAwareTypeInferer
public function inferFunctionLikeWithExcludedInferers(FunctionLike $functionLike, array $excludedInferers): array
{
foreach ($this->returnTypeInferers as $returnTypeInferer) {
foreach ($excludedInferers as $excludedInferer) {
if (is_a($returnTypeInferer, $excludedInferer, true)) {
continue 2;
}
if ($this->shouldSkipExcludedTypeInferer($returnTypeInferer, $excludedInferers)) {
continue;
}
$types = $returnTypeInferer->inferFunctionLike($functionLike);
if ($types !== []) {
if ($types !== [] && $types !== ['mixed']) {
return $types;
}
}
return [];
}
/**
* @param string[] $excludedInferers
*/
private function shouldSkipExcludedTypeInferer(
ReturnTypeInfererInterface $returnTypeInferer,
array $excludedInferers
): bool {
foreach ($excludedInferers as $excludedInferer) {
$this->ensureIsTypeInferer($excludedInferer);
if (is_a($returnTypeInferer, $excludedInferer)) {
return true;
}
}
return false;
}
private function ensureIsTypeInferer(string $excludedInferer): void
{
if (is_a($excludedInferer, ReturnTypeInfererInterface::class, true)) {
return;
}
throw new ShouldNotHappenException();
}
}

View File

@ -58,16 +58,15 @@ final class ReturnedNodesReturnTypeInferer extends AbstractTypeInferer implement
}
/**
* @param ClassMethod|Closure|Function_ $functionLike
* @return Return_[]
*/
private function collectReturns(FunctionLike $functionLike): array
{
$returns = [];
$this->callableNodeTraverser->traverseNodesWithCallable((array) $functionLike->stmts, function (Node $node) use (
&$returns
): ?int {
$this->callableNodeTraverser->traverseNodesWithCallable((array) $functionLike->getStmts(), function (
Node $node
) use (&$returns): ?int {
if ($node instanceof Function_ || $node instanceof Closure || $node instanceof ArrowFunction) {
// skip Return_ nodes in nested functions
return NodeTraverser::DONT_TRAVERSE_CHILDREN;

View File

@ -0,0 +1,25 @@
<?php
namespace Rector\TypeDeclaration\Tests\Rector\ClassMethod\ReturnTypeDeclarationRector\Fixture;
use stdClass;
class SkipMixedAndString
{
/** @var mixed */
protected $value;
public function getValue()
{
if ($this->value instanceof stdClass) {
return $this->getStringValue();
}
return $this->value;
}
public function getStringValue(): string
{
return 'abc';
}
}

View File

@ -10,6 +10,7 @@ final class ReturnTypeDeclarationRectorTest extends AbstractRectorTestCase
public function test(): void
{
$files = [
__DIR__ . '/Fixture/skip_mixed_and_string.php.inc',
__DIR__ . '/Fixture/skip_self.php.inc',
// static types
__DIR__ . '/Fixture/void_type.php.inc',

View File

@ -21,7 +21,7 @@ namespace Rector\TypeDeclaration\Tests\Rector\FunctionLike\PropertyTypeDeclarati
class ConstructorArrayParamWithNullable
{
/**
* @var array|null
* @var mixed[]|null
*/
private $data;