Skip to content

Commit 0b21f6c

Browse files
committed
[dead-code] Skip substr casting removal on PHP 7.x, as can return false|string
1 parent 7e1ee78 commit 0b21f6c

9 files changed

Lines changed: 122 additions & 33 deletions

File tree

rules-tests/DeadCode/Rector/Cast/RecastingRemovalRector/Fixture/skip_substr.php.inc

Lines changed: 0 additions & 17 deletions
This file was deleted.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
namespace Rector\Tests\DeadCode\Rector\Cast\RecastingRemovalRector\FixturePhp74;
4+
5+
final class SkipSubstr
6+
{
7+
public function run(string $value): string
8+
{
9+
return (string) substr($value, 10, 10);
10+
}
11+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace DeadCode\Rector\Cast\RecastingRemovalRector;
6+
7+
use Iterator;
8+
use PHPUnit\Framework\Attributes\DataProvider;
9+
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
10+
11+
final class RecastingRemovalRectorPhp74Test extends AbstractRectorTestCase
12+
{
13+
#[DataProvider('provideData')]
14+
public function test(string $filePath): void
15+
{
16+
$this->doTestFile($filePath);
17+
}
18+
19+
public static function provideData(): Iterator
20+
{
21+
return self::yieldFilesFromDirectory(__DIR__ . '/FixturePhp74');
22+
}
23+
24+
public function provideConfigFilePath(): string
25+
{
26+
return __DIR__ . '/config/configured_rule_php74.php';
27+
}
28+
}

rules-tests/DeadCode/Rector/Cast/RecastingRemovalRector/config/configured_rule.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
use Rector\Config\RectorConfig;
66
use Rector\DeadCode\Rector\Cast\RecastingRemovalRector;
7+
use Rector\ValueObject\PhpVersion;
78

89
return RectorConfig::configure()
9-
->withRules([RecastingRemovalRector::class]);
10+
->withRules([RecastingRemovalRector::class])
11+
->withPhpVersion(PhpVersion::PHP_80);
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Rector\Config\RectorConfig;
6+
use Rector\DeadCode\Rector\Cast\RecastingRemovalRector;
7+
use Rector\ValueObject\PhpVersion;
8+
9+
return RectorConfig::configure()
10+
->withRules([RecastingRemovalRector::class])
11+
->withPhpVersion(PhpVersion::PHP_74);
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
namespace Rector\Tests\TypeDeclaration\Rector\ClassMethod\ReturnUnionTypeRector\FixtureTrueInUnion;
4+
5+
/**
6+
* true|othertype work on >= php 8.2, ref https://3v4l.org/UJqXT
7+
*/
8+
final class TrueInUnionStrToUpper
9+
{
10+
public function run($value)
11+
{
12+
if ($value) {
13+
return true;
14+
}
15+
16+
return strtoupper('warning');
17+
}
18+
}
19+
20+
?>
21+
-----
22+
<?php
23+
24+
namespace Rector\Tests\TypeDeclaration\Rector\ClassMethod\ReturnUnionTypeRector\FixtureTrueInUnion;
25+
26+
/**
27+
* true|othertype work on >= php 8.2, ref https://3v4l.org/UJqXT
28+
*/
29+
final class TrueInUnionStrToUpper
30+
{
31+
public function run($value): true|string
32+
{
33+
if ($value) {
34+
return true;
35+
}
36+
37+
return strtoupper('warning');
38+
}
39+
}
40+
41+
?>

rules/CodingStyle/ClassNameImport/ClassNameImportSkipVoter/ClassLikeNameClassNameImportSkipVoter.php

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,15 +46,13 @@ public function shouldSkip(File $file, FullyQualifiedObjectType $fullyQualifiedO
4646
$namespace = strtolower((string) $namespace);
4747

4848
$shortNameLowered = $fullyQualifiedObjectType->getShortNameLowered();
49-
/**
50-
* on php 7.x, substr() result can return false, so force (string) is needed
51-
* @see https://github.com/rectorphp/rector-src/pull/7436
52-
*/
53-
$subClassName = (string) substr(
49+
50+
$subClassName = substr(
5451
$fullyQualifiedObjectType->getClassName(),
5552
0,
5653
-strlen($fullyQualifiedObjectType->getShortName()) - 1
5754
);
55+
5856
$fullyQualifiedObjectTypeNamespace = strtolower($subClassName);
5957

6058
foreach ($classLikeNames as $classLikeName) {

rules/DeadCode/Rector/Cast/RecastingRemovalRector.php

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,12 @@
1313
use PhpParser\Node\Expr\Cast\Int_;
1414
use PhpParser\Node\Expr\Cast\Object_;
1515
use PhpParser\Node\Expr\Cast\String_;
16-
use PhpParser\Node\Expr\FuncCall;
1716
use PhpParser\Node\Expr\MethodCall;
1817
use PhpParser\Node\Expr\StaticCall;
1918
use PHPStan\Reflection\Php\PhpPropertyReflection;
2019
use PHPStan\Type\ArrayType;
2120
use PHPStan\Type\BooleanType;
2221
use PHPStan\Type\Constant\ConstantArrayType;
23-
use PHPStan\Type\Constant\ConstantBooleanType;
2422
use PHPStan\Type\FloatType;
2523
use PHPStan\Type\IntegerType;
2624
use PHPStan\Type\MixedType;
@@ -55,7 +53,7 @@ final class RecastingRemovalRector extends AbstractRector
5553
public function __construct(
5654
private readonly PropertyFetchAnalyzer $propertyFetchAnalyzer,
5755
private readonly ReflectionResolver $reflectionResolver,
58-
private readonly ExprAnalyzer $exprAnalyzer
56+
private readonly ExprAnalyzer $exprAnalyzer,
5957
) {
6058
}
6159

@@ -105,13 +103,6 @@ public function refactor(Node $node): ?Node
105103
return null;
106104
}
107105

108-
// substr can return false on php 7.x
109-
if ($node->expr instanceof FuncCall
110-
&& $this->isName($node->expr, 'substr')
111-
&& ! $node->expr->isFirstClassCallable()) {
112-
$nodeType = new UnionType([new StringType(), new ConstantBooleanType(false)]);
113-
}
114-
115106
if ($nodeType instanceof ConstantArrayType && $nodeClass === Array_::class) {
116107
if ($this->shouldSkip($node->expr)) {
117108
return null;

src/NodeTypeResolver/NodeTypeResolver.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
use PHPStan\Type\NullType;
4040
use PHPStan\Type\ObjectType;
4141
use PHPStan\Type\ObjectWithoutClassType;
42+
use PHPStan\Type\StringType;
4243
use PHPStan\Type\ThisType;
4344
use PHPStan\Type\Type;
4445
use PHPStan\Type\TypeCombinator;
@@ -55,9 +56,11 @@
5556
use Rector\NodeTypeResolver\NodeTypeCorrector\AccessoryNonEmptyStringTypeCorrector;
5657
use Rector\NodeTypeResolver\NodeTypeCorrector\GenericClassStringTypeCorrector;
5758
use Rector\NodeTypeResolver\PHPStan\ObjectWithoutClassTypeWithParentTypes;
59+
use Rector\Php\PhpVersionProvider;
5860
use Rector\StaticTypeMapper\ValueObject\Type\AliasedObjectType;
5961
use Rector\StaticTypeMapper\ValueObject\Type\ShortenedObjectType;
6062
use Rector\TypeDeclaration\PHPStan\ObjectTypeSpecifier;
63+
use Rector\ValueObject\PhpVersion;
6164

6265
final class NodeTypeResolver
6366
{
@@ -83,6 +86,7 @@ public function __construct(
8386
private readonly AccessoryNonEmptyArrayTypeCorrector $accessoryNonEmptyArrayTypeCorrector,
8487
private readonly RenamedClassesDataCollector $renamedClassesDataCollector,
8588
private readonly NodeNameResolver $nodeNameResolver,
89+
private readonly PhpVersionProvider $phpVersionProvider,
8690
iterable $nodeTypeResolvers
8791
) {
8892
foreach ($nodeTypeResolvers as $nodeTypeResolver) {
@@ -620,6 +624,10 @@ private function resolveNativeTypeWithBuiltinMethodCallFallback(Expr $expr, Scop
620624
return $scope->getNativeType($expr);
621625
}
622626

627+
if ($this->isSubstrOnPHP74($expr)) {
628+
return new UnionType([new StringType(), new ConstantBooleanType(false)]);
629+
}
630+
623631
return $scope->getType($expr);
624632
}
625633

@@ -651,4 +659,20 @@ private function isEnumTypeMatch(MethodCall|NullsafeMethodCall $call, ObjectType
651659

652660
return $classReflection->getName() === $objectType->getClassName();
653661
}
662+
663+
/**
664+
* substr can return false on php 7.x and bellow
665+
*/
666+
private function isSubstrOnPHP74(FuncCall $expr): bool
667+
{
668+
if ($expr->isFirstClassCallable()) {
669+
return false;
670+
}
671+
672+
if (! $this->nodeNameResolver->isName($expr, 'substr')) {
673+
return false;
674+
}
675+
676+
return ! $this->phpVersionProvider->isAtLeastPhpVersion(PhpVersion::PHP_80);
677+
}
654678
}

0 commit comments

Comments
 (0)