Skip to content

Commit

Permalink
[TypeDeclaration] Add ArrayShapeFromConstantArrayReturnRector (#1908)
Browse files Browse the repository at this point in the history
  • Loading branch information
TomasVotruba committed Mar 7, 2022
1 parent 55a13c0 commit 8057e33
Show file tree
Hide file tree
Showing 33 changed files with 446 additions and 29 deletions.
13 changes: 7 additions & 6 deletions bin/clean-phpstan.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

declare(strict_types=1);

use Nette\Utils\FileSystem;
use Nette\Utils\Strings;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
Expand Down Expand Up @@ -45,14 +46,14 @@ protected function execute(InputInterface $input, OutputInterface $output): int
return self::FAILURE;
}

$originalContent = (string) file_get_contents(self::FILE);
$originalContent = FileSystem::read(self::FILE);
$newContent = str_replace(
'reportUnmatchedIgnoredErrors: false',
'reportUnmatchedIgnoredErrors: true',
$originalContent
);

file_put_contents(self::FILE, $newContent);
FileSystem::write(self::FILE, $newContent);

$process = new Process(['composer', 'phpstan']);
$process->run();
Expand All @@ -63,7 +64,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$output->writeln($result);

if (! $isFailure) {
file_put_contents(self::FILE, $originalContent);
FileSystem::write(self::FILE, $originalContent);
return self::SUCCESS;
}

Expand All @@ -74,16 +75,16 @@ protected function execute(InputInterface $input, OutputInterface $output): int

$matchAll = Strings::matchAll($result, self::ONELINE_IGNORED_PATTERN_REGEX);

foreach ($matchAll as $match) {
$newContent = str_replace(' - \'' . $match['content'] . '\'', '', $newContent);
foreach ($matchAll as $singleMatchAll) {
$newContent = str_replace(" - '" . $singleMatchAll['content'] . "'", '', $newContent);
}

$newContent = str_replace(
'reportUnmatchedIgnoredErrors: true',
'reportUnmatchedIgnoredErrors: false',
$newContent
);
file_put_contents(self::FILE, $newContent);
FileSystem::write(self::FILE, $newContent);

$process2 = new Process(['composer', 'phpstan']);
$process2->run();
Expand Down
3 changes: 2 additions & 1 deletion config/set/type-declaration-strict.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Rector\TypeDeclaration\Rector\ClassMethod\AddMethodCallBasedStrictParamTypeRector;
use Rector\TypeDeclaration\Rector\ClassMethod\AddVoidReturnTypeWhereNoReturnRector;
use Rector\TypeDeclaration\Rector\ClassMethod\ArrayShapeFromConstantArrayReturnRector;
use Rector\TypeDeclaration\Rector\ClassMethod\ReturnTypeFromReturnNewRector;
use Rector\TypeDeclaration\Rector\ClassMethod\ReturnTypeFromStrictTypedCallRector;
use Rector\TypeDeclaration\Rector\ClassMethod\ReturnTypeFromStrictTypedPropertyRector;
Expand All @@ -22,7 +23,7 @@
$services->set(ReturnTypeFromStrictTypedCallRector::class);
$services->set(AddVoidReturnTypeWhereNoReturnRector::class);
$services->set(ReturnTypeFromReturnNewRector::class);

$services->set(TypedPropertyFromStrictGetterMethodReturnTypeRector::class);
$services->set(AddMethodCallBasedStrictParamTypeRector::class);
$services->set(ArrayShapeFromConstantArrayReturnRector::class);
};
2 changes: 1 addition & 1 deletion packages-tests/FileFormatter/ValueObject/IndentTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ public function provideSizeStyleAndIndentString(): Iterator
}

/**
* @return int[]
* @return array{int-one: int, int-greater-than-one: int}
*/
private static function sizes(): array
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@
use Rector\StaticTypeMapper\StaticTypeMapper;
use ReturnTypeWillChange;

/**
* @see https://wiki.php.net/rfc/internal_method_return_types#proposal
*/
final class PhpDocFromTypeDeclarationDecorator
{
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ public static function decode(array $json): self
}

/**
* @return array<string, mixed>
* @return array{rector_class: class-string<RectorInterface>, line: int}
*/
public function jsonSerialize(): array
{
Expand Down
9 changes: 9 additions & 0 deletions packages/NodeTypeResolver/TypeComparator/TypeComparator.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
use PHPStan\Type\ArrayType;
use PHPStan\Type\BooleanType;
use PHPStan\Type\Constant\ConstantArrayType;
use PHPStan\Type\Constant\ConstantBooleanType;
use PHPStan\Type\ConstantScalarType;
use PHPStan\Type\Generic\GenericClassStringType;
Expand Down Expand Up @@ -213,6 +214,14 @@ private function areArrayUnionConstantEqualTypes(Type $firstType, Type $secondTy
return false;
}

if ($firstType instanceof ConstantArrayType) {
return false;
}

if ($secondType instanceof ConstantArrayType) {
return false;
}

$firstKeyType = $this->normalizeSingleUnionType($firstType->getKeyType());
$secondKeyType = $this->normalizeSingleUnionType($secondType->getKeyType());

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php

declare(strict_types=1);

namespace Rector\PHPStanStaticTypeMapper\TypeMapper;

use PHPStan\PhpDocParser\Ast\Type\ArrayShapeItemNode;
use PHPStan\PhpDocParser\Ast\Type\ArrayShapeNode;
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
use PHPStan\Type\ArrayType;
use PHPStan\Type\Constant\ConstantArrayType;
use PHPStan\Type\Constant\ConstantStringType;
use PHPStan\Type\MixedType;
use PHPStan\Type\NeverType;
use Rector\PHPStanStaticTypeMapper\Enum\TypeKind;
use Rector\PHPStanStaticTypeMapper\PHPStanStaticTypeMapper;

final class ArrayShapeTypeMapper
{
public function __construct(
private readonly PHPStanStaticTypeMapper $phpStanStaticTypeMapper
) {
}

public function mapConstantArrayType(ConstantArrayType $constantArrayType): ArrayShapeNode|ArrayType|null
{
// empty array
if ($constantArrayType->getKeyType() instanceof NeverType) {
return new ArrayType(new MixedType(), new MixedType());
}

$arrayShapeItemNodes = [];

foreach ($constantArrayType->getKeyTypes() as $index => $keyType) {
// not real array shape
if (! $keyType instanceof ConstantStringType) {
return null;
}

$keyDocTypeNode = new IdentifierTypeNode($keyType->getValue());
$valueType = $constantArrayType->getValueTypes()[$index];

$valueDocTypeNode = $this->phpStanStaticTypeMapper->mapToPHPStanPhpDocTypeNode(
$valueType,
TypeKind::RETURN()
);

$arrayShapeItemNodes[] = new ArrayShapeItemNode(
$keyDocTypeNode,
$constantArrayType->isOptionalKey($index),
$valueDocTypeNode
);
}

return new ArrayShapeNode($arrayShapeItemNodes);
}
}
13 changes: 12 additions & 1 deletion packages/PHPStanStaticTypeMapper/TypeMapper/ArrayTypeMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ final class ArrayTypeMapper implements TypeMapperInterface

private DetailedTypeAnalyzer $detailedTypeAnalyzer;

private ArrayShapeTypeMapper $arrayShapeTypeMapper;

// To avoid circular dependency

#[Required]
Expand All @@ -61,13 +63,15 @@ public function autowire(
UnionTypeCommonTypeNarrower $unionTypeCommonTypeNarrower,
ReflectionProvider $reflectionProvider,
GenericClassStringTypeNormalizer $genericClassStringTypeNormalizer,
DetailedTypeAnalyzer $detailedTypeAnalyzer
DetailedTypeAnalyzer $detailedTypeAnalyzer,
ArrayShapeTypeMapper $arrayShapeTypeMapper
): void {
$this->phpStanStaticTypeMapper = $phpStanStaticTypeMapper;
$this->unionTypeCommonTypeNarrower = $unionTypeCommonTypeNarrower;
$this->reflectionProvider = $reflectionProvider;
$this->genericClassStringTypeNormalizer = $genericClassStringTypeNormalizer;
$this->detailedTypeAnalyzer = $detailedTypeAnalyzer;
$this->arrayShapeTypeMapper = $arrayShapeTypeMapper;
}

/**
Expand All @@ -89,6 +93,13 @@ public function mapToPHPStanPhpDocTypeNode(Type $type, TypeKind $typeKind): Type
return $this->createArrayTypeNodeFromUnionType($itemType, $typeKind);
}

if ($type instanceof ConstantArrayType && $typeKind->equals(TypeKind::RETURN())) {
$arrayShapeNode = $this->arrayShapeTypeMapper->mapConstantArrayType($type);
if ($arrayShapeNode instanceof TypeNode) {
return $arrayShapeNode;
}
}

if ($itemType instanceof ArrayType && $this->isGenericArrayCandidate($itemType)) {
return $this->createGenericArrayType($type, $typeKind, true);
}
Expand Down
9 changes: 8 additions & 1 deletion phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ includes:
- vendor/symplify/phpstan-rules/config/services-rules.neon
- vendor/symplify/phpstan-rules/config/static-rules.neon
- vendor/symplify/phpstan-rules/config/string-to-constant-rules.neon
- vendor/symplify/phpstan-rules/config/symfony-rules.neon
- vendor/symplify/phpstan-rules/packages/symfony/config/symfony-rules.neon
- vendor/symplify/phpstan-rules/config/test-rules.neon

parameters:
Expand Down Expand Up @@ -575,3 +575,10 @@ parameters:
-
message: '#Make callable type explicit#'
path: src/NodeManipulator/BinaryOpManipulator.php

# resolve later
- '#Add explicit array type to assigned "(.*?)" expression#'
- '#Complete known array shape to the method @return type#'
# @todo remove from phpstan-rules for reoctr
- '#Function "array_walk\(\)" cannot be used/left in the code\: #'
- '#Instead of "Nette\\Utils\\FileSystem" class/interface use "Symplify\\SmartFileSystem\\SmartFileSystem"#'
5 changes: 5 additions & 0 deletions rector.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use Rector\Privatization\Rector\Class_\FinalizeClassesWithoutChildrenRector;
use Rector\Set\ValueObject\LevelSetList;
use Rector\Set\ValueObject\SetList;
use Rector\TypeDeclaration\Rector\ClassMethod\ArrayShapeFromConstantArrayReturnRector;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;

return static function (ContainerConfigurator $containerConfigurator): void {
Expand Down Expand Up @@ -73,6 +74,10 @@
MyCLabsClassToEnumRector::class,
SpatieEnumClassToEnumRector::class,

ArrayShapeFromConstantArrayReturnRector::class => [
__DIR__ . '/rules/Transform/Rector/ClassMethod/ReturnTypeWillChangeRector.php',
],

// test paths
'*/tests/**/Fixture/*',
'*/rules-tests/**/Fixture/*',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace Rector\Tests\CodingStyle\Rector\ClassConst\VarConstantCommentRector\Fixture;

class PairClassStringArray
final class PairClassStringArray
{
private const CLASSES = [
\stdClass::class => PairClassStringArray::class,
Expand All @@ -15,7 +15,7 @@ class PairClassStringArray

namespace Rector\Tests\CodingStyle\Rector\ClassConst\VarConstantCommentRector\Fixture;

class PairClassStringArray
final class PairClassStringArray
{
/**
* @var array<string, class-string<\Rector\Tests\CodingStyle\Rector\ClassConst\VarConstantCommentRector\Fixture\PairClassStringArray>>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ namespace Rector\Tests\TypeDeclaration\Rector\ClassMethod\AddArrayReturnDocTypeR
class ChildHasPriority extends ParentClassWithDefinedReturn
{
/**
* @return array<int, array<string, int|float|string>>
* @return array<int, array{a: string, b: int, c: float}>
*/
public function getData()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

declare(strict_types=1);

namespace Rector\Tests\TypeDeclaration\Rector\ClassMethod\ArrayShapeFromConstantArrayReturnRector;

use Iterator;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
use Symplify\SmartFileSystem\SmartFileInfo;

final class ArrayShapeFromConstantArrayReturnRectorTest extends AbstractRectorTestCase
{
/**
* @dataProvider provideData()
*/
public function test(SmartFileInfo $fileInfo): void
{
$this->doTestFileInfo($fileInfo);
}

/**
* @return Iterator<SmartFileInfo>
*/
public function provideData(): Iterator
{
return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture');
}

public function provideConfigFilePath(): string
{
return __DIR__ . '/config/configured_rule.php';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

namespace Rector\Tests\TypeDeclaration\Rector\ClassMethod\ArrayShapeFromConstantArrayReturnRector\Fixture;

final class IncludeConstants
{
public const NAME = 'name';

public function run(string $name)
{
return [self::NAME => $name];
}
}

?>
-----
<?php

namespace Rector\Tests\TypeDeclaration\Rector\ClassMethod\ArrayShapeFromConstantArrayReturnRector\Fixture;

final class IncludeConstants
{
public const NAME = 'name';

/**
* @return array{name: string}
*/
public function run(string $name)
{
return [self::NAME => $name];
}
}

?>
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

namespace Rector\Tests\TypeDeclaration\Rector\ClassMethod\ArrayShapeFromConstantArrayReturnRector\Fixture;

use Rector\Tests\TypeDeclaration\Rector\ClassMethod\ArrayShapeFromConstantArrayReturnRector\Source\ExternalConstant;

final class IncludeExternalConstants
{
/**
* @return array<string, string|int>
*/
public function run(int $line, string $file): array
{
return [
ExternalConstant::LINE => $line,
ExternalConstant::FILE => $file,
];
}
}

?>
-----
<?php

namespace Rector\Tests\TypeDeclaration\Rector\ClassMethod\ArrayShapeFromConstantArrayReturnRector\Fixture;

use Rector\Tests\TypeDeclaration\Rector\ClassMethod\ArrayShapeFromConstantArrayReturnRector\Source\ExternalConstant;

final class IncludeExternalConstants
{
/**
* @return array{line: int, file: string}
*/
public function run(int $line, string $file): array
{
return [
ExternalConstant::LINE => $line,
ExternalConstant::FILE => $file,
];
}
}

?>

0 comments on commit 8057e33

Please sign in to comment.