Skip to content

Commit 9a95b6b

Browse files
[dead-code] Skip substr casting removal on PHP 7.x, as can return false|string (#7449)
* [dead-code] Skip substr casting removal on PHP 7.x, as can return false|string * [ci-review] Rector Rectify --------- Co-authored-by: GitHub Action <actions@github.com>
1 parent d57b45e commit 9a95b6b

File tree

8 files changed

+122
-7
lines changed

8 files changed

+122
-7
lines changed
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 & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ final class RecastingRemovalRector extends AbstractRector
5353
public function __construct(
5454
private readonly PropertyFetchAnalyzer $propertyFetchAnalyzer,
5555
private readonly ReflectionResolver $reflectionResolver,
56-
private readonly ExprAnalyzer $exprAnalyzer
56+
private readonly ExprAnalyzer $exprAnalyzer,
5757
) {
5858
}
5959

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 $funcCall): bool
667+
{
668+
if ($funcCall->isFirstClassCallable()) {
669+
return false;
670+
}
671+
672+
if (! $this->nodeNameResolver->isName($funcCall, 'substr')) {
673+
return false;
674+
}
675+
676+
return ! $this->phpVersionProvider->isAtLeastPhpVersion(PhpVersion::PHP_80);
677+
}
654678
}

0 commit comments

Comments
 (0)