diff --git a/rules-tests/DeadCode/Rector/Cast/RecastingRemovalRector/Fixture/skip_substr.php.inc b/rules-tests/DeadCode/Rector/Cast/RecastingRemovalRector/Fixture/skip_substr.php.inc deleted file mode 100644 index 07f63228bfb..00000000000 --- a/rules-tests/DeadCode/Rector/Cast/RecastingRemovalRector/Fixture/skip_substr.php.inc +++ /dev/null @@ -1,17 +0,0 @@ - diff --git a/rules-tests/DeadCode/Rector/Cast/RecastingRemovalRector/FixturePhp74/skip_substr.php.inc b/rules-tests/DeadCode/Rector/Cast/RecastingRemovalRector/FixturePhp74/skip_substr.php.inc new file mode 100644 index 00000000000..31aa57c0bc7 --- /dev/null +++ b/rules-tests/DeadCode/Rector/Cast/RecastingRemovalRector/FixturePhp74/skip_substr.php.inc @@ -0,0 +1,11 @@ +doTestFile($filePath); + } + + public static function provideData(): Iterator + { + return self::yieldFilesFromDirectory(__DIR__ . '/FixturePhp74'); + } + + public function provideConfigFilePath(): string + { + return __DIR__ . '/config/configured_rule_php74.php'; + } +} diff --git a/rules-tests/DeadCode/Rector/Cast/RecastingRemovalRector/config/configured_rule.php b/rules-tests/DeadCode/Rector/Cast/RecastingRemovalRector/config/configured_rule.php index 52e76816c96..7ae9f988b70 100644 --- a/rules-tests/DeadCode/Rector/Cast/RecastingRemovalRector/config/configured_rule.php +++ b/rules-tests/DeadCode/Rector/Cast/RecastingRemovalRector/config/configured_rule.php @@ -4,6 +4,8 @@ use Rector\Config\RectorConfig; use Rector\DeadCode\Rector\Cast\RecastingRemovalRector; +use Rector\ValueObject\PhpVersion; return RectorConfig::configure() - ->withRules([RecastingRemovalRector::class]); + ->withRules([RecastingRemovalRector::class]) + ->withPhpVersion(PhpVersion::PHP_80); diff --git a/rules-tests/DeadCode/Rector/Cast/RecastingRemovalRector/config/configured_rule_php74.php b/rules-tests/DeadCode/Rector/Cast/RecastingRemovalRector/config/configured_rule_php74.php new file mode 100644 index 00000000000..c86fc9d7fe5 --- /dev/null +++ b/rules-tests/DeadCode/Rector/Cast/RecastingRemovalRector/config/configured_rule_php74.php @@ -0,0 +1,11 @@ +withRules([RecastingRemovalRector::class]) + ->withPhpVersion(PhpVersion::PHP_74); diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/ReturnUnionTypeRector/FixtureTrueInUnion/true_in_union_strtoupper.php.inc b/rules-tests/TypeDeclaration/Rector/ClassMethod/ReturnUnionTypeRector/FixtureTrueInUnion/true_in_union_strtoupper.php.inc new file mode 100644 index 00000000000..eb25a83bc0e --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/ReturnUnionTypeRector/FixtureTrueInUnion/true_in_union_strtoupper.php.inc @@ -0,0 +1,41 @@ += php 8.2, ref https://3v4l.org/UJqXT + */ +final class TrueInUnionStrToUpper +{ + public function run($value) + { + if ($value) { + return true; + } + + return strtoupper('warning'); + } +} + +?> +----- += php 8.2, ref https://3v4l.org/UJqXT + */ +final class TrueInUnionStrToUpper +{ + public function run($value): true|string + { + if ($value) { + return true; + } + + return strtoupper('warning'); + } +} + +?> diff --git a/rules/CodingStyle/ClassNameImport/ClassNameImportSkipVoter/ClassLikeNameClassNameImportSkipVoter.php b/rules/CodingStyle/ClassNameImport/ClassNameImportSkipVoter/ClassLikeNameClassNameImportSkipVoter.php index 29737b3ed6f..3030cdb8275 100644 --- a/rules/CodingStyle/ClassNameImport/ClassNameImportSkipVoter/ClassLikeNameClassNameImportSkipVoter.php +++ b/rules/CodingStyle/ClassNameImport/ClassNameImportSkipVoter/ClassLikeNameClassNameImportSkipVoter.php @@ -46,15 +46,13 @@ public function shouldSkip(File $file, FullyQualifiedObjectType $fullyQualifiedO $namespace = strtolower((string) $namespace); $shortNameLowered = $fullyQualifiedObjectType->getShortNameLowered(); - /** - * on php 7.x, substr() result can return false, so force (string) is needed - * @see https://github.com/rectorphp/rector-src/pull/7436 - */ - $subClassName = (string) substr( + + $subClassName = substr( $fullyQualifiedObjectType->getClassName(), 0, -strlen($fullyQualifiedObjectType->getShortName()) - 1 ); + $fullyQualifiedObjectTypeNamespace = strtolower($subClassName); foreach ($classLikeNames as $classLikeName) { diff --git a/rules/DeadCode/Rector/Cast/RecastingRemovalRector.php b/rules/DeadCode/Rector/Cast/RecastingRemovalRector.php index fa2f3ce3fbd..627d513b057 100644 --- a/rules/DeadCode/Rector/Cast/RecastingRemovalRector.php +++ b/rules/DeadCode/Rector/Cast/RecastingRemovalRector.php @@ -13,14 +13,12 @@ use PhpParser\Node\Expr\Cast\Int_; use PhpParser\Node\Expr\Cast\Object_; use PhpParser\Node\Expr\Cast\String_; -use PhpParser\Node\Expr\FuncCall; use PhpParser\Node\Expr\MethodCall; use PhpParser\Node\Expr\StaticCall; use PHPStan\Reflection\Php\PhpPropertyReflection; use PHPStan\Type\ArrayType; use PHPStan\Type\BooleanType; use PHPStan\Type\Constant\ConstantArrayType; -use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\FloatType; use PHPStan\Type\IntegerType; use PHPStan\Type\MixedType; @@ -55,7 +53,7 @@ final class RecastingRemovalRector extends AbstractRector public function __construct( private readonly PropertyFetchAnalyzer $propertyFetchAnalyzer, private readonly ReflectionResolver $reflectionResolver, - private readonly ExprAnalyzer $exprAnalyzer + private readonly ExprAnalyzer $exprAnalyzer, ) { } @@ -105,13 +103,6 @@ public function refactor(Node $node): ?Node return null; } - // substr can return false on php 7.x - if ($node->expr instanceof FuncCall - && $this->isName($node->expr, 'substr') - && ! $node->expr->isFirstClassCallable()) { - $nodeType = new UnionType([new StringType(), new ConstantBooleanType(false)]); - } - if ($nodeType instanceof ConstantArrayType && $nodeClass === Array_::class) { if ($this->shouldSkip($node->expr)) { return null; diff --git a/src/NodeTypeResolver/NodeTypeResolver.php b/src/NodeTypeResolver/NodeTypeResolver.php index 6cde992ec83..41dad8c7b3e 100644 --- a/src/NodeTypeResolver/NodeTypeResolver.php +++ b/src/NodeTypeResolver/NodeTypeResolver.php @@ -39,6 +39,7 @@ use PHPStan\Type\NullType; use PHPStan\Type\ObjectType; use PHPStan\Type\ObjectWithoutClassType; +use PHPStan\Type\StringType; use PHPStan\Type\ThisType; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; @@ -55,9 +56,11 @@ use Rector\NodeTypeResolver\NodeTypeCorrector\AccessoryNonEmptyStringTypeCorrector; use Rector\NodeTypeResolver\NodeTypeCorrector\GenericClassStringTypeCorrector; use Rector\NodeTypeResolver\PHPStan\ObjectWithoutClassTypeWithParentTypes; +use Rector\Php\PhpVersionProvider; use Rector\StaticTypeMapper\ValueObject\Type\AliasedObjectType; use Rector\StaticTypeMapper\ValueObject\Type\ShortenedObjectType; use Rector\TypeDeclaration\PHPStan\ObjectTypeSpecifier; +use Rector\ValueObject\PhpVersion; final class NodeTypeResolver { @@ -83,6 +86,7 @@ public function __construct( private readonly AccessoryNonEmptyArrayTypeCorrector $accessoryNonEmptyArrayTypeCorrector, private readonly RenamedClassesDataCollector $renamedClassesDataCollector, private readonly NodeNameResolver $nodeNameResolver, + private readonly PhpVersionProvider $phpVersionProvider, iterable $nodeTypeResolvers ) { foreach ($nodeTypeResolvers as $nodeTypeResolver) { @@ -620,6 +624,10 @@ private function resolveNativeTypeWithBuiltinMethodCallFallback(Expr $expr, Scop return $scope->getNativeType($expr); } + if ($this->isSubstrOnPHP74($expr)) { + return new UnionType([new StringType(), new ConstantBooleanType(false)]); + } + return $scope->getType($expr); } @@ -651,4 +659,20 @@ private function isEnumTypeMatch(MethodCall|NullsafeMethodCall $call, ObjectType return $classReflection->getName() === $objectType->getClassName(); } + + /** + * substr can return false on php 7.x and bellow + */ + private function isSubstrOnPHP74(FuncCall $funcCall): bool + { + if ($funcCall->isFirstClassCallable()) { + return false; + } + + if (! $this->nodeNameResolver->isName($funcCall, 'substr')) { + return false; + } + + return ! $this->phpVersionProvider->isAtLeastPhpVersion(PhpVersion::PHP_80); + } }