mirror of
https://github.com/rectorphp/rector.git
synced 2025-02-21 01:41:00 +01:00
add mixed type
This commit is contained in:
parent
cf1514fad6
commit
57db278625
@ -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()];
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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';
|
||||
}
|
||||
}
|
@ -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',
|
||||
|
@ -21,7 +21,7 @@ namespace Rector\TypeDeclaration\Tests\Rector\FunctionLike\PropertyTypeDeclarati
|
||||
class ConstructorArrayParamWithNullable
|
||||
{
|
||||
/**
|
||||
* @var array|null
|
||||
* @var mixed[]|null
|
||||
*/
|
||||
private $data;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user