From 03ea9b2c4e823b6b9800c2de052193326ebb2027 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Sun, 1 Sep 2024 13:40:01 +0700 Subject: [PATCH] [ReturnTypeInferer] Drop this/static docblock type check on ReturnTypeInferer (#6270) * [ReturnTypeInferer] Drop this/static type check on ReturnTypeInferer * use phpstan TypeCombinator --- .../GenericClassStringTypeNormalizer.php | 116 ---------- .../TypeInferer/ReturnTypeInferer.php | 214 +----------------- .../TypeAnalyzer/UnionTypeAnalyzer.php | 18 +- 3 files changed, 5 insertions(+), 343 deletions(-) diff --git a/rules/TypeDeclaration/TypeAnalyzer/GenericClassStringTypeNormalizer.php b/rules/TypeDeclaration/TypeAnalyzer/GenericClassStringTypeNormalizer.php index 10897d29b5..16305eeb9b 100644 --- a/rules/TypeDeclaration/TypeAnalyzer/GenericClassStringTypeNormalizer.php +++ b/rules/TypeDeclaration/TypeAnalyzer/GenericClassStringTypeNormalizer.php @@ -4,62 +4,11 @@ namespace Rector\TypeDeclaration\TypeAnalyzer; -use PHPStan\Reflection\ReflectionProvider; -use PHPStan\Type\ArrayType; -use PHPStan\Type\ClassStringType; -use PHPStan\Type\Constant\ConstantIntegerType; -use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\Generic\GenericClassStringType; -use PHPStan\Type\MixedType; -use PHPStan\Type\ObjectType; -use PHPStan\Type\StringType; -use PHPStan\Type\Type; -use PHPStan\Type\TypeTraverser; use PHPStan\Type\UnionType; -use Rector\PHPStanStaticTypeMapper\TypeAnalyzer\UnionTypeAnalyzer; -use Rector\TypeDeclaration\NodeTypeAnalyzer\DetailedTypeAnalyzer; final readonly class GenericClassStringTypeNormalizer { - public function __construct( - private ReflectionProvider $reflectionProvider, - private DetailedTypeAnalyzer $detailedTypeAnalyzer, - private UnionTypeAnalyzer $unionTypeAnalyzer - ) { - } - - public function normalize(Type $type): ArrayType | UnionType | Type - { - $type = TypeTraverser::map($type, function (Type $type, $callback): Type { - if (! $type instanceof ConstantStringType) { - return $callback($type); - } - - $value = $type->getValue(); - - // skip string that look like classe - if ($value === 'error') { - return $callback($type); - } - - if (! $this->reflectionProvider->hasClass($value)) { - return $callback($type); - } - - return $this->resolveStringType($value); - }); - - if ($type instanceof UnionType && ! $this->unionTypeAnalyzer->isNullable($type, true)) { - return $this->resolveClassStringInUnionType($type); - } - - if ($type instanceof ArrayType && $type->getKeyType() instanceof UnionType) { - return $this->resolveArrayTypeWithUnionKeyType($type); - } - - return $type; - } - public function isAllGenericClassStringType(UnionType $unionType): bool { foreach ($unionType->getTypes() as $type) { @@ -70,69 +19,4 @@ public function isAllGenericClassStringType(UnionType $unionType): bool return true; } - - private function resolveArrayTypeWithUnionKeyType(ArrayType $arrayType): ArrayType - { - $itemType = $arrayType->getItemType(); - - if (! $itemType instanceof UnionType) { - return $arrayType; - } - - $keyType = $arrayType->getKeyType(); - $isAllGenericClassStringType = $this->isAllGenericClassStringType($itemType); - - if (! $isAllGenericClassStringType) { - return new ArrayType($keyType, new MixedType()); - } - - if ($this->detailedTypeAnalyzer->isTooDetailed($itemType)) { - return new ArrayType($keyType, new ClassStringType()); - } - - return $arrayType; - } - - private function resolveClassStringInUnionType(UnionType $type): UnionType | ArrayType - { - $unionTypes = $type->getTypes(); - - foreach ($unionTypes as $unionType) { - if (! $unionType instanceof ArrayType) { - return $type; - } - - $keyType = $unionType->getKeyType(); - $itemType = $unionType->getItemType(); - - if (! $keyType instanceof MixedType && ! $keyType instanceof ConstantIntegerType) { - return $type; - } - - if ($itemType instanceof ArrayType) { - $arrayType = new ArrayType(new MixedType(), new MixedType()); - return new ArrayType($keyType, $arrayType); - } - - if (! $itemType instanceof ClassStringType) { - return $type; - } - } - - return new ArrayType(new MixedType(), new ClassStringType()); - } - - private function resolveStringType(string $value): GenericClassStringType | StringType - { - $classReflection = $this->reflectionProvider->getClass($value); - if ($classReflection->isBuiltin()) { - return new GenericClassStringType(new ObjectType($value)); - } - - if (str_contains($value, '\\')) { - return new GenericClassStringType(new ObjectType($value)); - } - - return new StringType(); - } } diff --git a/rules/TypeDeclaration/TypeInferer/ReturnTypeInferer.php b/rules/TypeDeclaration/TypeInferer/ReturnTypeInferer.php index f5a51ca66a..908e7fc7ff 100644 --- a/rules/TypeDeclaration/TypeInferer/ReturnTypeInferer.php +++ b/rules/TypeDeclaration/TypeInferer/ReturnTypeInferer.php @@ -4,33 +4,13 @@ namespace Rector\TypeDeclaration\TypeInferer; -use PhpParser\Node; -use PhpParser\Node\Expr; use PhpParser\Node\Expr\Closure; -use PhpParser\Node\Expr\Yield_; -use PhpParser\Node\FunctionLike; use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Function_; -use PhpParser\Node\Stmt\Return_; -use PHPStan\Reflection\ClassReflection; -use PHPStan\Reflection\ReflectionProvider; -use PHPStan\Type\BenevolentUnionType; use PHPStan\Type\MixedType; -use PHPStan\Type\ThisType; use PHPStan\Type\Type; -use PHPStan\Type\TypeWithClassName; -use PHPStan\Type\UnionType; -use Rector\Enum\ObjectReference; -use Rector\Exception\ShouldNotHappenException; -use Rector\NodeTypeResolver\NodeTypeResolver; -use Rector\Php\PhpVersionProvider; -use Rector\PhpParser\Node\BetterNodeFinder; -use Rector\Reflection\ReflectionResolver; -use Rector\StaticTypeMapper\ValueObject\Type\FullyQualifiedObjectType; -use Rector\TypeDeclaration\TypeAnalyzer\GenericClassStringTypeNormalizer; use Rector\TypeDeclaration\TypeInferer\ReturnTypeInferer\ReturnedNodesReturnTypeInfererTypeInferer; use Rector\TypeDeclaration\TypeNormalizer; -use Rector\ValueObject\PhpVersionFeature; /** * @internal @@ -39,207 +19,17 @@ { public function __construct( private TypeNormalizer $typeNormalizer, - private ReturnedNodesReturnTypeInfererTypeInferer $returnedNodesReturnTypeInfererTypeInferer, - private GenericClassStringTypeNormalizer $genericClassStringTypeNormalizer, - private PhpVersionProvider $phpVersionProvider, - private BetterNodeFinder $betterNodeFinder, - private ReflectionResolver $reflectionResolver, - private ReflectionProvider $reflectionProvider, - private NodeTypeResolver $nodeTypeResolver + private ReturnedNodesReturnTypeInfererTypeInferer $returnedNodesReturnTypeInfererTypeInferer ) { } public function inferFunctionLike(ClassMethod|Function_|Closure $functionLike): Type { - $isSupportedStaticReturnType = $this->phpVersionProvider->isAtLeastPhpVersion( - PhpVersionFeature::STATIC_RETURN_TYPE - ); - $originalType = $this->returnedNodesReturnTypeInfererTypeInferer->inferFunctionLike($functionLike); if ($originalType instanceof MixedType) { return new MixedType(); } - $type = $this->typeNormalizer->normalizeArrayTypeAndArrayNever($originalType); - - // in case of void, check return type of children methods - if ($type instanceof MixedType) { - return new MixedType(); - } - - $type = $this->verifyStaticType($type, $isSupportedStaticReturnType); - if (! $type instanceof Type) { - return new MixedType(); - } - - $type = $this->verifyThisType($type, $functionLike); - - // normalize ConstStringType to ClassStringType - $resolvedType = $this->genericClassStringTypeNormalizer->normalize($type); - return $this->resolveTypeWithVoidHandling($functionLike, $resolvedType); - } - - private function verifyStaticType(Type $type, bool $isSupportedStaticReturnType): ?Type - { - if ($this->isStaticType($type)) { - /** @var TypeWithClassName $type */ - return $this->resolveStaticType($isSupportedStaticReturnType, $type); - } - - if ($type instanceof UnionType) { - return $this->resolveUnionStaticTypes($type, $isSupportedStaticReturnType); - } - - return $type; - } - - private function verifyThisType(Type $type, FunctionLike $functionLike): Type - { - if (! $type instanceof ThisType) { - return $type; - } - - $classReflection = $this->reflectionResolver->resolveClassReflection($functionLike); - $objectType = $type->getStaticObjectType(); - $objectTypeClassName = $objectType->getClassName(); - - if (! $classReflection instanceof ClassReflection || ! $classReflection->isClass()) { - return $type; - } - - if ($classReflection->getName() === $objectTypeClassName) { - return $type; - } - - return new MixedType(); - } - - private function resolveTypeWithVoidHandling( - ClassMethod|Function_|Closure $functionLike, - Type $resolvedType - ): Type { - if ($resolvedType->isVoid()->yes()) { - $hasReturnValue = (bool) $this->betterNodeFinder->findFirstInFunctionLikeScoped( - $functionLike, - static function (Node $subNode): bool { - if (! $subNode instanceof Return_) { - // yield return is handled on speicific rule: AddReturnTypeDeclarationFromYieldsRector - return $subNode instanceof Yield_; - } - - return $subNode->expr instanceof Expr; - } - ); - - if ($hasReturnValue) { - return new MixedType(); - } - } - - if ($resolvedType instanceof UnionType) { - $benevolentUnionTypeIntegerType = $this->resolveBenevolentUnionTypeInteger($functionLike, $resolvedType); - if ($benevolentUnionTypeIntegerType->isInteger()->yes()) { - return $benevolentUnionTypeIntegerType; - } - } - - return $resolvedType; - } - - private function resolveBenevolentUnionTypeInteger( - ClassMethod|Function_|Closure $functionLike, - UnionType $unionType - ): Type { - $types = $unionType->getTypes(); - $countTypes = count($types); - - if ($countTypes !== 2) { - return $unionType; - } - - if (! ($types[0]->isInteger()->yes() && $types[1]->isString()->yes())) { - return $unionType; - } - - $returns = $this->betterNodeFinder->findReturnsScoped($functionLike); - $returnsWithExpr = array_filter( - $returns, - static fn (Return_ $return): bool => $return->expr instanceof Expr - ); - - if ($returns !== $returnsWithExpr) { - return $unionType; - } - - if ($returnsWithExpr === []) { - return $unionType; - } - - foreach ($returnsWithExpr as $returnWithExpr) { - /** @var Expr $expr */ - $expr = $returnWithExpr->expr; - $type = $this->nodeTypeResolver->getNativeType($expr); - - if (! $type instanceof BenevolentUnionType) { - return $unionType; - } - } - - return $types[0]; - } - - private function isStaticType(Type $type): bool - { - if (! $type instanceof TypeWithClassName) { - return false; - } - - return $type->getClassName() === ObjectReference::STATIC; - } - - private function resolveUnionStaticTypes(UnionType $unionType, bool $isSupportedStaticReturnType): UnionType|null - { - $resolvedTypes = []; - $hasStatic = false; - - foreach ($unionType->getTypes() as $unionedType) { - if ($this->isStaticType($unionedType)) { - /** @var FullyQualifiedObjectType $unionedType */ - $classReflection = $this->reflectionProvider->getClass($unionedType->getClassName()); - - $resolvedTypes[] = new ThisType($classReflection); - $hasStatic = true; - continue; - } - - $resolvedTypes[] = $unionedType; - } - - if (! $hasStatic) { - return $unionType; - } - - // has static, but it is not supported - if (! $isSupportedStaticReturnType) { - return null; - } - - return new UnionType($resolvedTypes); - } - - private function resolveStaticType( - bool $isSupportedStaticReturnType, - TypeWithClassName $typeWithClassName - ): ?ThisType { - if (! $isSupportedStaticReturnType) { - return null; - } - - $classReflection = $typeWithClassName->getClassReflection(); - if (! $classReflection instanceof ClassReflection) { - throw new ShouldNotHappenException(); - } - - return new ThisType($classReflection); + return $this->typeNormalizer->normalizeArrayTypeAndArrayNever($originalType); } } diff --git a/src/PHPStanStaticTypeMapper/TypeAnalyzer/UnionTypeAnalyzer.php b/src/PHPStanStaticTypeMapper/TypeAnalyzer/UnionTypeAnalyzer.php index be19eaba32..e1f2f4ca87 100644 --- a/src/PHPStanStaticTypeMapper/TypeAnalyzer/UnionTypeAnalyzer.php +++ b/src/PHPStanStaticTypeMapper/TypeAnalyzer/UnionTypeAnalyzer.php @@ -4,25 +4,13 @@ namespace Rector\PHPStanStaticTypeMapper\TypeAnalyzer; -use PHPStan\Type\NullType; +use PHPStan\Type\TypeCombinator; use PHPStan\Type\UnionType; final class UnionTypeAnalyzer { - public function isNullable(UnionType $unionType, bool $checkTwoTypes = false): bool + public function isNullable(UnionType $unionType): bool { - $types = $unionType->getTypes(); - - if ($checkTwoTypes && count($types) > 2) { - return false; - } - - foreach ($types as $type) { - if ($type instanceof NullType) { - return true; - } - } - - return false; + return TypeCombinator::containsNull($unionType); } }