From 49b3ca04b80764eea49af1d1acafdc05eebc5fc3 Mon Sep 17 00:00:00 2001 From: TomasVotruba Date: Tue, 14 Jan 2020 18:59:15 +0100 Subject: [PATCH 1/6] add TypeMapper --- README.md | 2 +- .../PHPStanStaticTypeMapperAwareInterface.php | 12 + .../src/Contract/TypeMapperInterface.php | 25 ++ .../src/PHPStanStaticTypeMapper.php | 177 ++++---------- .../src/TypeMapper/BooleanTypeMapper.php | 53 +++++ .../src/TypeMapper/ClassStringTypeMapper.php | 36 +++ .../src/TypeMapper/FloatTypeMapper.php | 53 +++++ .../src/TypeMapper/IntegerTypeMapper.php | 53 +++++ .../src/TypeMapper/MixedTypeMapper.php | 36 +++ .../src/TypeMapper/NeverTypeMapper.php | 36 +++ .../src/TypeMapper/NullTypeMapper.php | 36 +++ .../src/TypeMapper/ObjectTypeMapper.php | 91 +++++++ .../src/TypeMapper/StringTypeMapper.php | 50 ++++ .../src/TypeMapper/UnionTypeMapper.php | 222 ++++++++++++++++++ 14 files changed, 756 insertions(+), 126 deletions(-) create mode 100644 packages/PHPStanStaticTypeMapper/src/Contract/PHPStanStaticTypeMapperAwareInterface.php create mode 100644 packages/PHPStanStaticTypeMapper/src/Contract/TypeMapperInterface.php create mode 100644 packages/PHPStanStaticTypeMapper/src/TypeMapper/BooleanTypeMapper.php create mode 100644 packages/PHPStanStaticTypeMapper/src/TypeMapper/ClassStringTypeMapper.php create mode 100644 packages/PHPStanStaticTypeMapper/src/TypeMapper/FloatTypeMapper.php create mode 100644 packages/PHPStanStaticTypeMapper/src/TypeMapper/IntegerTypeMapper.php create mode 100644 packages/PHPStanStaticTypeMapper/src/TypeMapper/MixedTypeMapper.php create mode 100644 packages/PHPStanStaticTypeMapper/src/TypeMapper/NeverTypeMapper.php create mode 100644 packages/PHPStanStaticTypeMapper/src/TypeMapper/NullTypeMapper.php create mode 100644 packages/PHPStanStaticTypeMapper/src/TypeMapper/ObjectTypeMapper.php create mode 100644 packages/PHPStanStaticTypeMapper/src/TypeMapper/StringTypeMapper.php create mode 100644 packages/PHPStanStaticTypeMapper/src/TypeMapper/UnionTypeMapper.php diff --git a/README.md b/README.md index 27716a26e4d5..e1aeed8f822f 100644 --- a/README.md +++ b/README.md @@ -325,7 +325,7 @@ final class MyFirstRector extends AbstractRector public function refactor(Node $node): ?Node { // we only care about "set*" method names - if (! $this->isName($node, 'set*')) { + if (! $this->isName($node->name, 'set*')) { // return null to skip it return null; } diff --git a/packages/PHPStanStaticTypeMapper/src/Contract/PHPStanStaticTypeMapperAwareInterface.php b/packages/PHPStanStaticTypeMapper/src/Contract/PHPStanStaticTypeMapperAwareInterface.php new file mode 100644 index 000000000000..fcfe36d21be8 --- /dev/null +++ b/packages/PHPStanStaticTypeMapper/src/Contract/PHPStanStaticTypeMapperAwareInterface.php @@ -0,0 +1,12 @@ +phpVersionProvider = $phpVersionProvider; + $this->typeMappers = $typeMappers; } - public function mapToPHPStanPhpDocTypeNode(Type $phpStanType) + public function mapToPHPStanPhpDocTypeNode(Type $type): TypeNode { - if ($phpStanType instanceof MixedType) { - return new IdentifierTypeNode('mixed'); - } - - if ($phpStanType instanceof UnionType) { - $unionTypesNodes = []; - foreach ($phpStanType->getTypes() as $unionedType) { - $unionTypesNodes[] = $this->mapToPHPStanPhpDocTypeNode($unionedType); - } - - $unionTypesNodes = array_unique($unionTypesNodes); - - return new AttributeAwareUnionTypeNode($unionTypesNodes); - } - - if ($phpStanType instanceof ArrayType || $phpStanType instanceof IterableType) { - $itemTypeNode = $this->mapToPHPStanPhpDocTypeNode($phpStanType->getItemType()); + if ($type instanceof ArrayType || $type instanceof IterableType) { + $itemTypeNode = $this->mapToPHPStanPhpDocTypeNode($type->getItemType()); if ($itemTypeNode instanceof UnionTypeNode) { return $this->convertUnionArrayTypeNodesToArrayTypeOfUnionTypeNodes($itemTypeNode); @@ -87,39 +84,20 @@ public function mapToPHPStanPhpDocTypeNode(Type $phpStanType) return new ArrayTypeNode($itemTypeNode); } - if ($phpStanType instanceof IntegerType) { - return new IdentifierTypeNode('int'); - } - - if ($phpStanType instanceof ClassStringType) { - return new IdentifierTypeNode('class-string'); - } - - if ($phpStanType instanceof StringType) { - return new IdentifierTypeNode('string'); - } - - if ($phpStanType instanceof BooleanType) { - return new IdentifierTypeNode('bool'); - } - - if ($phpStanType instanceof FloatType) { - return new IdentifierTypeNode('float'); - } - - if ($phpStanType instanceof ObjectType) { - return new IdentifierTypeNode('\\' . $phpStanType->getClassName()); - } + foreach ($this->typeMappers as $typeMapper) { + if (! is_a($type, $typeMapper->getNodeClass(), true)) { + continue; + } - if ($phpStanType instanceof NullType) { - return new IdentifierTypeNode('null'); - } + // prevents circular dependency + if ($typeMapper instanceof PHPStanStaticTypeMapperAwareInterface) { + $typeMapper->setPHPStanStaticTypeMapper($this); + } - if ($phpStanType instanceof NeverType) { - return new IdentifierTypeNode('mixed'); + return $typeMapper->mapToPHPStanPhpDocTypeNode($type); } - throw new NotImplementedException(__METHOD__ . ' for ' . get_class($phpStanType)); + throw new NotImplementedException(__METHOD__ . ' for ' . get_class($type)); } /** @@ -144,36 +122,12 @@ public function mapToPhpParserNode(Type $phpStanType, ?string $kind = null): ?No return new Identifier('self'); } - if ($phpStanType instanceof IntegerType) { - if ($this->phpVersionProvider->isAtLeast(PhpVersionFeature::SCALAR_TYPES)) { - return new Identifier('int'); - } - - return null; - } - - if ($phpStanType instanceof StringType) { - if ($this->phpVersionProvider->isAtLeast(PhpVersionFeature::SCALAR_TYPES)) { - return new Identifier('string'); - } - - return null; - } - - if ($phpStanType instanceof BooleanType) { - if ($this->phpVersionProvider->isAtLeast(PhpVersionFeature::SCALAR_TYPES)) { - return new Identifier('bool'); - } - - return null; - } - - if ($phpStanType instanceof FloatType) { - if ($this->phpVersionProvider->isAtLeast(PhpVersionFeature::SCALAR_TYPES)) { - return new Identifier('float'); + foreach ($this->typeMappers as $typeMapper) { + if (! is_a($phpStanType, $typeMapper->getNodeClass(), true)) { + continue; } - return null; + return $typeMapper->mapToPhpParserNode($phpStanType); } if ($phpStanType instanceof ArrayType) { @@ -204,13 +158,13 @@ public function mapToPhpParserNode(Type $phpStanType, ?string $kind = null): ?No return new Identifier('callable'); } - if ($phpStanType instanceof ShortenedObjectType) { - return new Name\FullyQualified($phpStanType->getFullyQualifiedName()); - } - - if ($phpStanType instanceof AliasedObjectType) { - return new Name($phpStanType->getClassName()); - } +// if ($phpStanType instanceof ShortenedObjectType) { +// return new FullyQualified($phpStanType->getFullyQualifiedName()); +// } +// +// if ($phpStanType instanceof AliasedObjectType) { +// return new Name($phpStanType->getClassName()); +// } if ($phpStanType instanceof TypeWithClassName) { $lowerCasedClassName = strtolower($phpStanType->getClassName()); @@ -230,7 +184,7 @@ public function mapToPhpParserNode(Type $phpStanType, ?string $kind = null): ?No return null; } - return new Name\FullyQualified($phpStanType->getClassName()); + return new FullyQualified($phpStanType->getClassName()); } if ($phpStanType instanceof UnionType) { @@ -263,11 +217,8 @@ public function mapToPhpParserNode(Type $phpStanType, ?string $kind = null): ?No return new NullableType($nullabledTypeNode); } - if ($phpStanType instanceof NeverType || - $phpStanType instanceof VoidType || - $phpStanType instanceof MixedType || - $phpStanType instanceof ResourceType || - $phpStanType instanceof NullType + if ($phpStanType instanceof VoidType || + $phpStanType instanceof ResourceType ) { return null; } @@ -331,16 +282,8 @@ public function mapToDocString(Type $phpStanType, ?Type $parentType = null): str return '\\' . Closure::class; } - if ($phpStanType instanceof StringType) { - return 'string'; - } - - if ($phpStanType instanceof IntegerType) { - return 'int'; - } - - if ($phpStanType instanceof NullType) { - return 'null'; + if ($phpStanType instanceof StringType || $phpStanType instanceof NullType || $phpStanType instanceof IntegerType || $phpStanType instanceof MixedType || $phpStanType instanceof FloatType || $phpStanType instanceof CallableType || $phpStanType instanceof ResourceType) { + return $phpStanType->describe(VerbosityLevel::typeOnly()); } if ($phpStanType instanceof ArrayType) { @@ -369,16 +312,8 @@ public function mapToDocString(Type $phpStanType, ?Type $parentType = null): str return implode('|', $docStringTypes); } - if ($phpStanType instanceof MixedType) { - return 'mixed'; - } - - if ($phpStanType instanceof FloatType) { - return 'float'; - } - if ($phpStanType instanceof VoidType) { - if ($this->phpVersionProvider->isAtLeast('7.1')) { + if ($this->phpVersionProvider->isAtLeast(PhpVersionFeature::SCALAR_TYPES)) { // the void type is better done in PHP code return ''; } @@ -387,12 +322,8 @@ public function mapToDocString(Type $phpStanType, ?Type $parentType = null): str return 'void'; } - if ($phpStanType instanceof BooleanType) { - return 'bool'; - } - if ($phpStanType instanceof IterableType) { - if ($this->phpVersionProvider->isAtLeast('7.1')) { + if ($this->phpVersionProvider->isAtLeast(PhpVersionFeature::SCALAR_TYPES)) { // the void type is better done in PHP code return ''; } @@ -400,16 +331,12 @@ public function mapToDocString(Type $phpStanType, ?Type $parentType = null): str return 'iterable'; } - if ($phpStanType instanceof NeverType) { - return 'mixed'; - } - - if ($phpStanType instanceof CallableType) { - return 'callable'; + if ($phpStanType instanceof BooleanType) { + return 'bool'; } - if ($phpStanType instanceof ResourceType) { - return 'resource'; + if ($phpStanType instanceof NeverType) { + return 'mixed'; } throw new NotImplementedException(__METHOD__ . ' for ' . get_class($phpStanType)); @@ -456,7 +383,7 @@ private function matchTypeForNullableUnionType(UnionType $unionType): ?Type } /** - * @return Name|Name\FullyQualified|PhpParserUnionType|null + * @return Name|FullyQualified|PhpParserUnionType|null */ private function matchTypeForUnionedObjectTypes(UnionType $unionType): ?Node { @@ -481,7 +408,7 @@ private function matchTypeForUnionedObjectTypes(UnionType $unionType): ?Node } } - return new Name\FullyQualified($unionedType->getClassName()); + return new FullyQualified($unionedType->getClassName()); } return null; diff --git a/packages/PHPStanStaticTypeMapper/src/TypeMapper/BooleanTypeMapper.php b/packages/PHPStanStaticTypeMapper/src/TypeMapper/BooleanTypeMapper.php new file mode 100644 index 000000000000..ab8caa17b195 --- /dev/null +++ b/packages/PHPStanStaticTypeMapper/src/TypeMapper/BooleanTypeMapper.php @@ -0,0 +1,53 @@ +phpVersionProvider = $phpVersionProvider; + } + + public function getNodeClass(): string + { + return BooleanType::class; + } + + /** + * @param BooleanType $type + */ + public function mapToPHPStanPhpDocTypeNode(Type $type): TypeNode + { + return new IdentifierTypeNode('bool'); + } + + /** + * @param BooleanType $type + */ + public function mapToPhpParserNode(Type $type, ?string $kind = null): ?Node + { + if (! $this->phpVersionProvider->isAtLeast(PhpVersionFeature::SCALAR_TYPES)) { + return null; + } + + return new Identifier('bool'); + } +} diff --git a/packages/PHPStanStaticTypeMapper/src/TypeMapper/ClassStringTypeMapper.php b/packages/PHPStanStaticTypeMapper/src/TypeMapper/ClassStringTypeMapper.php new file mode 100644 index 000000000000..e321997b4135 --- /dev/null +++ b/packages/PHPStanStaticTypeMapper/src/TypeMapper/ClassStringTypeMapper.php @@ -0,0 +1,36 @@ +phpVersionProvider = $phpVersionProvider; + } + + public function getNodeClass(): string + { + return FloatType::class; + } + + /** + * @param FloatType $type + */ + public function mapToPHPStanPhpDocTypeNode(Type $type): TypeNode + { + return new IdentifierTypeNode('float'); + } + + /** + * @param FloatType $type + */ + public function mapToPhpParserNode(Type $type, ?string $kind = null): ?Node + { + if (! $this->phpVersionProvider->isAtLeast(PhpVersionFeature::SCALAR_TYPES)) { + return null; + } + + return new Identifier('float'); + } +} diff --git a/packages/PHPStanStaticTypeMapper/src/TypeMapper/IntegerTypeMapper.php b/packages/PHPStanStaticTypeMapper/src/TypeMapper/IntegerTypeMapper.php new file mode 100644 index 000000000000..988e5c362a5f --- /dev/null +++ b/packages/PHPStanStaticTypeMapper/src/TypeMapper/IntegerTypeMapper.php @@ -0,0 +1,53 @@ +phpVersionProvider = $phpVersionProvider; + } + + public function getNodeClass(): string + { + return IntegerType::class; + } + + /** + * @param IntegerType $type + */ + public function mapToPHPStanPhpDocTypeNode(Type $type): TypeNode + { + return new IdentifierTypeNode('int'); + } + + /** + * @param IntegerType $type + */ + public function mapToPhpParserNode(Type $type, ?string $kind = null): ?Node + { + if (! $this->phpVersionProvider->isAtLeast(PhpVersionFeature::SCALAR_TYPES)) { + return null; + } + + return new Identifier('int'); + } +} diff --git a/packages/PHPStanStaticTypeMapper/src/TypeMapper/MixedTypeMapper.php b/packages/PHPStanStaticTypeMapper/src/TypeMapper/MixedTypeMapper.php new file mode 100644 index 000000000000..d990fe0ff3ae --- /dev/null +++ b/packages/PHPStanStaticTypeMapper/src/TypeMapper/MixedTypeMapper.php @@ -0,0 +1,36 @@ +getClassName()); + } + + /** + * @param ObjectType $type + */ + public function mapToPhpParserNode(Type $type, ?string $kind = null): ?Node + { + if ($type instanceof ShortenedObjectType) { + return new FullyQualified($type->getFullyQualifiedName()); + } + + if ($type instanceof AliasedObjectType) { + return new Name($type->getClassName()); + } + + if ($type instanceof FullyQualifiedObjectType) { + return new FullyQualified($type->getClassName()); + } + + if ($type instanceof GenericObjectType) { + if ($type->getClassName() === 'object') { + return new Identifier('object'); + } + + return new FullyQualified($type->getClassName()); + + // if ($type->getClassName()) { +// dump($type); +// die; +// } +// +// if ($type->getClassName() === 'object') { +// dump($type); +// die; +// } else { +// die; +// } +// +// return new Identifier('object'); + } + + // ... + dump($type->getFullyQualifiedName()); + die; + + // fallback + return new FullyQualified($type->getFullyQualifiedName()); + + dump($type); + die; + + return null; + } +} diff --git a/packages/PHPStanStaticTypeMapper/src/TypeMapper/StringTypeMapper.php b/packages/PHPStanStaticTypeMapper/src/TypeMapper/StringTypeMapper.php new file mode 100644 index 000000000000..409c5e1ad7e7 --- /dev/null +++ b/packages/PHPStanStaticTypeMapper/src/TypeMapper/StringTypeMapper.php @@ -0,0 +1,50 @@ +phpVersionProvider = $phpVersionProvider; + } + + public function getNodeClass(): string + { + return StringType::class; + } + + /** + * @param StringType $type + */ + public function mapToPHPStanPhpDocTypeNode(Type $type): TypeNode + { + return new IdentifierTypeNode('string'); + } + + public function mapToPhpParserNode(Type $type, ?string $kind = null): ?Node + { + if (! $this->phpVersionProvider->isAtLeast(PhpVersionFeature::SCALAR_TYPES)) { + return null; + } + + return new Identifier('string'); + } +} diff --git a/packages/PHPStanStaticTypeMapper/src/TypeMapper/UnionTypeMapper.php b/packages/PHPStanStaticTypeMapper/src/TypeMapper/UnionTypeMapper.php new file mode 100644 index 000000000000..170ffe4c8439 --- /dev/null +++ b/packages/PHPStanStaticTypeMapper/src/TypeMapper/UnionTypeMapper.php @@ -0,0 +1,222 @@ +phpVersionProvider = $phpVersionProvider; + } + + public function getNodeClass(): string + { + return UnionType::class; + } + + /** + * @param UnionType $type + */ + public function mapToPHPStanPhpDocTypeNode(Type $type): TypeNode + { + $unionTypesNodes = []; + + foreach ($type->getTypes() as $unionedType) { + $unionTypesNodes[] = $this->phpStanStaticTypeMapper->mapToPHPStanPhpDocTypeNode($unionedType); + } + + $unionTypesNodes = array_unique($unionTypesNodes); + + return new AttributeAwareUnionTypeNode($unionTypesNodes); + } + + public function setPHPStanStaticTypeMapper(PHPStanStaticTypeMapper $phpStanStaticTypeMapper): void + { + $this->phpStanStaticTypeMapper = $phpStanStaticTypeMapper; + } + + /** + * @param UnionType $type + */ + public function mapToPhpParserNode(Type $type, ?string $kind = null): ?Node + { + // match array types + $arrayNode = $this->matchArrayTypes($type); + if ($arrayNode !== null) { + return $arrayNode; + } + + // special case for nullable + $nullabledType = $this->matchTypeForNullableUnionType($type); + if ($nullabledType === null) { + // use first unioned type in case of unioned object types + return $this->matchTypeForUnionedObjectTypes($type); + } + + $nullabledTypeNode = $this->mapToPhpParserNode($nullabledType); + if ($nullabledTypeNode === null) { + return null; + } + + if ($nullabledTypeNode instanceof Node\NullableType) { + return $nullabledTypeNode; + } + + if ($nullabledTypeNode instanceof Node\UnionType) { + throw new ShouldNotHappenException(); + } + + return new NullableType($nullabledTypeNode); + } + + private function matchArrayTypes(UnionType $unionType): ?Identifier + { + $isNullableType = false; + $hasIterable = false; + + foreach ($unionType->getTypes() as $unionedType) { + if ($unionedType instanceof IterableType) { + $hasIterable = true; + continue; + } + + if ($unionedType instanceof ArrayType) { + continue; + } + + if ($unionedType instanceof NullType) { + $isNullableType = true; + continue; + } + + if ($unionedType instanceof ObjectType) { + if ($unionedType->getClassName() === Traversable::class) { + $hasIterable = true; + continue; + } + } + + return null; + } + + $type = $hasIterable ? 'iterable' : 'array'; + if ($isNullableType) { + return new Identifier('?' . $type); + } + + return new Identifier($type); + } + + private function matchTypeForNullableUnionType(UnionType $unionType): ?Type + { + if (count($unionType->getTypes()) !== 2) { + return null; + } + + $firstType = $unionType->getTypes()[0]; + $secondType = $unionType->getTypes()[1]; + + if ($firstType instanceof NullType) { + return $secondType; + } + + if ($secondType instanceof NullType) { + return $firstType; + } + + return null; + } + + + /** + * @return Node\Name|Node\Name\FullyQualified|Node\UnionType|null + */ + private function matchTypeForUnionedObjectTypes(UnionType $unionType): ?Node + { + $phpParserUnionType = $this->matchPhpParserUnionType($unionType); + if ($phpParserUnionType !== null) { + return $phpParserUnionType; + } + + // do the type should be compatible with all other types, e.g. A extends B, B + foreach ($unionType->getTypes() as $unionedType) { + if (! $unionedType instanceof TypeWithClassName) { + return null; + } + + foreach ($unionType->getTypes() as $nestedUnionedType) { + if (! $nestedUnionedType instanceof TypeWithClassName) { + return null; + } + + if (! $this->areTypeWithClassNamesRelated($unionedType, $nestedUnionedType)) { + continue 2; + } + } + + return new Node\Name\FullyQualified($unionedType->getClassName()); + } + + return null; + } + + private function matchPhpParserUnionType(UnionType $unionType): ?Node\UnionType + { + if (! $this->phpVersionProvider->isAtLeast(PhpVersionFeature::UNION_TYPES)) { + return null; + } + + $phpParserUnionedTypes = []; + foreach ($unionType->getTypes() as $unionedType) { + /** @var Identifier|Node\Name|null $phpParserNode */ + $phpParserNode = $this->mapToPhpParserNode($unionedType); + if ($phpParserNode === null) { + return null; + } + + $phpParserUnionedTypes[] = $phpParserNode; + } + + return new Node\UnionType($phpParserUnionedTypes); + } + + private function areTypeWithClassNamesRelated(TypeWithClassName $firstType, TypeWithClassName $secondType): bool + { + if (is_a($firstType->getClassName(), $secondType->getClassName(), true)) { + return true; + } + + return is_a($secondType->getClassName(), $firstType->getClassName(), true); + } +} From ba72bc847d7542dbc482ccbb2158dc9e86c5db77 Mon Sep 17 00:00:00 2001 From: TomasVotruba Date: Tue, 14 Jan 2020 21:14:35 +0100 Subject: [PATCH 2/6] decouple UnionTypeAnalyzer and UnionTypeAnalysis --- .../config/config.yaml | 1 + .../src/PHPStanStaticTypeMapper.php | 8 -- .../src/TypeAnalyzer/UnionTypeAnalyzer.php | 49 ++++++++ .../src/TypeMapper/ObjectTypeMapper.php | 31 +---- .../src/TypeMapper/UnionTypeMapper.php | 115 +++++++++--------- .../src/ValueObject/UnionTypeAnalysis.php | 34 ++++++ .../src/Command/DumpNodesCommand.php | 1 + 7 files changed, 142 insertions(+), 97 deletions(-) create mode 100644 packages/PHPStanStaticTypeMapper/src/TypeAnalyzer/UnionTypeAnalyzer.php create mode 100644 packages/PHPStanStaticTypeMapper/src/ValueObject/UnionTypeAnalysis.php diff --git a/packages/PHPStanStaticTypeMapper/config/config.yaml b/packages/PHPStanStaticTypeMapper/config/config.yaml index fd750ab3d7b8..357e20805c40 100644 --- a/packages/PHPStanStaticTypeMapper/config/config.yaml +++ b/packages/PHPStanStaticTypeMapper/config/config.yaml @@ -6,3 +6,4 @@ services: Rector\PHPStanStaticTypeMapper\: resource: '../src' exclude: + - '../src/ValueObject/*' diff --git a/packages/PHPStanStaticTypeMapper/src/PHPStanStaticTypeMapper.php b/packages/PHPStanStaticTypeMapper/src/PHPStanStaticTypeMapper.php index 44e301c4554e..e0060a9d2d91 100644 --- a/packages/PHPStanStaticTypeMapper/src/PHPStanStaticTypeMapper.php +++ b/packages/PHPStanStaticTypeMapper/src/PHPStanStaticTypeMapper.php @@ -158,14 +158,6 @@ public function mapToPhpParserNode(Type $phpStanType, ?string $kind = null): ?No return new Identifier('callable'); } -// if ($phpStanType instanceof ShortenedObjectType) { -// return new FullyQualified($phpStanType->getFullyQualifiedName()); -// } -// -// if ($phpStanType instanceof AliasedObjectType) { -// return new Name($phpStanType->getClassName()); -// } - if ($phpStanType instanceof TypeWithClassName) { $lowerCasedClassName = strtolower($phpStanType->getClassName()); if ($lowerCasedClassName === 'callable') { diff --git a/packages/PHPStanStaticTypeMapper/src/TypeAnalyzer/UnionTypeAnalyzer.php b/packages/PHPStanStaticTypeMapper/src/TypeAnalyzer/UnionTypeAnalyzer.php new file mode 100644 index 000000000000..0dd5a5fee53a --- /dev/null +++ b/packages/PHPStanStaticTypeMapper/src/TypeAnalyzer/UnionTypeAnalyzer.php @@ -0,0 +1,49 @@ +getTypes() as $unionedType) { + if ($unionedType instanceof IterableType) { + $hasIterable = true; + continue; + } + + if ($unionedType instanceof ArrayType) { + continue; + } + + if ($unionedType instanceof NullType) { + $isNullableType = true; + continue; + } + + if ($unionedType instanceof ObjectType) { + if ($unionedType->getClassName() === Traversable::class) { + $hasIterable = true; + continue; + } + } + + return null; + } + + return new UnionTypeAnalysis($isNullableType, $hasIterable); + } +} diff --git a/packages/PHPStanStaticTypeMapper/src/TypeMapper/ObjectTypeMapper.php b/packages/PHPStanStaticTypeMapper/src/TypeMapper/ObjectTypeMapper.php index 4985b3a138f2..650a1faaa54c 100644 --- a/packages/PHPStanStaticTypeMapper/src/TypeMapper/ObjectTypeMapper.php +++ b/packages/PHPStanStaticTypeMapper/src/TypeMapper/ObjectTypeMapper.php @@ -4,19 +4,15 @@ namespace Rector\PHPStanStaticTypeMapper\TypeMapper; -use PHP_CodeSniffer\Reports\Full; use PhpParser\Node; use PhpParser\Node\Identifier; use PhpParser\Node\Name; use PhpParser\Node\Name\FullyQualified; -use PhpParser\Node\NullableType; -use PhpParser\Node\UnionType; use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; use PHPStan\PhpDocParser\Ast\Type\TypeNode; use PHPStan\Type\Generic\GenericObjectType; use PHPStan\Type\ObjectType; use PHPStan\Type\Type; -use Rector\NodeTypeResolver\ClassExistenceStaticHelper; use Rector\PHPStan\Type\AliasedObjectType; use Rector\PHPStan\Type\FullyQualifiedObjectType; use Rector\PHPStan\Type\ShortenedObjectType; @@ -58,34 +54,9 @@ public function mapToPhpParserNode(Type $type, ?string $kind = null): ?Node if ($type->getClassName() === 'object') { return new Identifier('object'); } - - return new FullyQualified($type->getClassName()); - - // if ($type->getClassName()) { -// dump($type); -// die; -// } -// -// if ($type->getClassName() === 'object') { -// dump($type); -// die; -// } else { -// die; -// } -// -// return new Identifier('object'); } - // ... - dump($type->getFullyQualifiedName()); - die; - // fallback - return new FullyQualified($type->getFullyQualifiedName()); - - dump($type); - die; - - return null; + return new FullyQualified($type->getClassName()); } } diff --git a/packages/PHPStanStaticTypeMapper/src/TypeMapper/UnionTypeMapper.php b/packages/PHPStanStaticTypeMapper/src/TypeMapper/UnionTypeMapper.php index 170ffe4c8439..57cb57441730 100644 --- a/packages/PHPStanStaticTypeMapper/src/TypeMapper/UnionTypeMapper.php +++ b/packages/PHPStanStaticTypeMapper/src/TypeMapper/UnionTypeMapper.php @@ -6,11 +6,12 @@ use PhpParser\Node; use PhpParser\Node\Identifier; +use PhpParser\Node\Name; +use PhpParser\Node\Name\FullyQualified; +use PhpParser\Node\NullableType; +use PhpParser\Node\UnionType as PhpParserUnionType; use PHPStan\PhpDocParser\Ast\Type\TypeNode; -use PHPStan\Type\ArrayType; -use PHPStan\Type\IterableType; use PHPStan\Type\NullType; -use PHPStan\Type\ObjectType; use PHPStan\Type\Type; use PHPStan\Type\TypeWithClassName; use PHPStan\Type\UnionType; @@ -20,8 +21,8 @@ use Rector\PHPStanStaticTypeMapper\Contract\PHPStanStaticTypeMapperAwareInterface; use Rector\PHPStanStaticTypeMapper\Contract\TypeMapperInterface; use Rector\PHPStanStaticTypeMapper\PHPStanStaticTypeMapper; +use Rector\PHPStanStaticTypeMapper\TypeAnalyzer\UnionTypeAnalyzer; use Rector\ValueObject\PhpVersionFeature; -use Traversable; final class UnionTypeMapper implements TypeMapperInterface, PHPStanStaticTypeMapperAwareInterface { @@ -35,9 +36,15 @@ final class UnionTypeMapper implements TypeMapperInterface, PHPStanStaticTypeMap */ private $phpVersionProvider; - public function __construct(PhpVersionProvider $phpVersionProvider) + /** + * @var UnionTypeAnalyzer + */ + private $unionTypeAnalyzer; + + public function __construct(PhpVersionProvider $phpVersionProvider, UnionTypeAnalyzer $unionTypeAnalyzer) { $this->phpVersionProvider = $phpVersionProvider; + $this->unionTypeAnalyzer = $unionTypeAnalyzer; } public function getNodeClass(): string @@ -61,6 +68,9 @@ public function mapToPHPStanPhpDocTypeNode(Type $type): TypeNode return new AttributeAwareUnionTypeNode($unionTypesNodes); } + /** + * @required + */ public function setPHPStanStaticTypeMapper(PHPStanStaticTypeMapper $phpStanStaticTypeMapper): void { $this->phpStanStaticTypeMapper = $phpStanStaticTypeMapper; @@ -84,16 +94,16 @@ public function mapToPhpParserNode(Type $type, ?string $kind = null): ?Node return $this->matchTypeForUnionedObjectTypes($type); } - $nullabledTypeNode = $this->mapToPhpParserNode($nullabledType); + $nullabledTypeNode = $this->phpStanStaticTypeMapper->mapToPhpParserNode($nullabledType); if ($nullabledTypeNode === null) { return null; } - if ($nullabledTypeNode instanceof Node\NullableType) { + if ($nullabledTypeNode instanceof NullableType) { return $nullabledTypeNode; } - if ($nullabledTypeNode instanceof Node\UnionType) { + if ($nullabledTypeNode instanceof PhpParserUnionType) { throw new ShouldNotHappenException(); } @@ -102,36 +112,13 @@ public function mapToPhpParserNode(Type $type, ?string $kind = null): ?Node private function matchArrayTypes(UnionType $unionType): ?Identifier { - $isNullableType = false; - $hasIterable = false; - - foreach ($unionType->getTypes() as $unionedType) { - if ($unionedType instanceof IterableType) { - $hasIterable = true; - continue; - } - - if ($unionedType instanceof ArrayType) { - continue; - } - - if ($unionedType instanceof NullType) { - $isNullableType = true; - continue; - } - - if ($unionedType instanceof ObjectType) { - if ($unionedType->getClassName() === Traversable::class) { - $hasIterable = true; - continue; - } - } - + $unionTypeAnalysis = $this->unionTypeAnalyzer->analyseForNullableAndIterable($unionType); + if ($unionTypeAnalysis === null) { return null; } - $type = $hasIterable ? 'iterable' : 'array'; - if ($isNullableType) { + $type = $unionTypeAnalysis->hasIterable() ? 'iterable' : 'array'; + if ($unionTypeAnalysis->isNullableType()) { return new Identifier('?' . $type); } @@ -158,9 +145,8 @@ private function matchTypeForNullableUnionType(UnionType $unionType): ?Type return null; } - /** - * @return Node\Name|Node\Name\FullyQualified|Node\UnionType|null + * @return Name|FullyQualified|PhpParserUnionType|null */ private function matchTypeForUnionedObjectTypes(UnionType $unionType): ?Node { @@ -169,38 +155,26 @@ private function matchTypeForUnionedObjectTypes(UnionType $unionType): ?Node return $phpParserUnionType; } - // do the type should be compatible with all other types, e.g. A extends B, B - foreach ($unionType->getTypes() as $unionedType) { - if (! $unionedType instanceof TypeWithClassName) { - return null; - } - - foreach ($unionType->getTypes() as $nestedUnionedType) { - if (! $nestedUnionedType instanceof TypeWithClassName) { - return null; - } - - if (! $this->areTypeWithClassNamesRelated($unionedType, $nestedUnionedType)) { - continue 2; - } - } - - return new Node\Name\FullyQualified($unionedType->getClassName()); + // the type should be compatible with all other types, e.g. A extends B, B + $compatibleObjectCandidate = $this->resolveCompatibleObjectCandidate($unionType); + if ($compatibleObjectCandidate === null) { + return null; } - return null; + return new FullyQualified($compatibleObjectCandidate); } - private function matchPhpParserUnionType(UnionType $unionType): ?Node\UnionType + private function matchPhpParserUnionType(UnionType $unionType): ?PhpParserUnionType { if (! $this->phpVersionProvider->isAtLeast(PhpVersionFeature::UNION_TYPES)) { return null; } $phpParserUnionedTypes = []; + foreach ($unionType->getTypes() as $unionedType) { - /** @var Identifier|Node\Name|null $phpParserNode */ - $phpParserNode = $this->mapToPhpParserNode($unionedType); + /** @var Identifier|Name|null $phpParserNode */ + $phpParserNode = $this->phpStanStaticTypeMapper->mapToPhpParserNode($unionedType); if ($phpParserNode === null) { return null; } @@ -208,7 +182,7 @@ private function matchPhpParserUnionType(UnionType $unionType): ?Node\UnionType $phpParserUnionedTypes[] = $phpParserNode; } - return new Node\UnionType($phpParserUnionedTypes); + return new PhpParserUnionType($phpParserUnionedTypes); } private function areTypeWithClassNamesRelated(TypeWithClassName $firstType, TypeWithClassName $secondType): bool @@ -219,4 +193,27 @@ private function areTypeWithClassNamesRelated(TypeWithClassName $firstType, Type return is_a($secondType->getClassName(), $firstType->getClassName(), true); } + + private function resolveCompatibleObjectCandidate(UnionType $unionType): ?string + { + foreach ($unionType->getTypes() as $unionedType) { + if (! $unionedType instanceof TypeWithClassName) { + return null; + } + + foreach ($unionType->getTypes() as $nestedUnionedType) { + if (! $nestedUnionedType instanceof TypeWithClassName) { + return null; + } + + if (! $this->areTypeWithClassNamesRelated($unionedType, $nestedUnionedType)) { + continue 2; + } + } + + return $unionedType->getClassName(); + } + + return null; + } } diff --git a/packages/PHPStanStaticTypeMapper/src/ValueObject/UnionTypeAnalysis.php b/packages/PHPStanStaticTypeMapper/src/ValueObject/UnionTypeAnalysis.php new file mode 100644 index 000000000000..efe4452fd8fa --- /dev/null +++ b/packages/PHPStanStaticTypeMapper/src/ValueObject/UnionTypeAnalysis.php @@ -0,0 +1,34 @@ +isNullableType = $isNullableType; + $this->hasIterable = $hasIterable; + } + + public function isNullableType(): bool + { + return $this->isNullableType; + } + + public function hasIterable(): bool + { + return $this->hasIterable; + } +} diff --git a/utils/DocumentationGenerator/src/Command/DumpNodesCommand.php b/utils/DocumentationGenerator/src/Command/DumpNodesCommand.php index 44ad37ef1511..a6334aca4910 100644 --- a/utils/DocumentationGenerator/src/Command/DumpNodesCommand.php +++ b/utils/DocumentationGenerator/src/Command/DumpNodesCommand.php @@ -270,6 +270,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $node = new Function_('some_function'); } elseif ($nodeClass === ClassMethod::class) { $node = new ClassMethod('someMethod'); + $node->flags |= Class_::MODIFIER_PUBLIC; } elseif ($nodeClass === Case_::class) { $node = new Case_(new ConstFetch(new Name('true'))); } elseif ($nodeClass === Use_::class) { From 0bf8eb833dd1dab194957321b0b49404bbc683e8 Mon Sep 17 00:00:00 2001 From: TomasVotruba Date: Tue, 14 Jan 2020 21:19:23 +0100 Subject: [PATCH 3/6] rebuid docs --- docs/NodesOverview.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/NodesOverview.md b/docs/NodesOverview.md index 0ceff98894ed..4c7841ef2b97 100644 --- a/docs/NodesOverview.md +++ b/docs/NodesOverview.md @@ -763,7 +763,7 @@ const SOME_CLASS_CONSTANT = 'default value'; #### `PhpParser\Node\Stmt\ClassMethod` ```php -function someMethod() +public function someMethod() { } ``` From 682869a0874bbb9ef2d17832e3abc1262ff4387a Mon Sep 17 00:00:00 2001 From: TomasVotruba Date: Tue, 14 Jan 2020 21:19:41 +0100 Subject: [PATCH 4/6] decouple IterableTypeMapper --- .../src/PHPStanStaticTypeMapper.php | 4 -- .../src/TypeMapper/IterableTypeMapper.php | 37 +++++++++++++++++++ 2 files changed, 37 insertions(+), 4 deletions(-) create mode 100644 packages/PHPStanStaticTypeMapper/src/TypeMapper/IterableTypeMapper.php diff --git a/packages/PHPStanStaticTypeMapper/src/PHPStanStaticTypeMapper.php b/packages/PHPStanStaticTypeMapper/src/PHPStanStaticTypeMapper.php index e0060a9d2d91..50b636ad9238 100644 --- a/packages/PHPStanStaticTypeMapper/src/PHPStanStaticTypeMapper.php +++ b/packages/PHPStanStaticTypeMapper/src/PHPStanStaticTypeMapper.php @@ -134,10 +134,6 @@ public function mapToPhpParserNode(Type $phpStanType, ?string $kind = null): ?No return new Identifier('array'); } - if ($phpStanType instanceof IterableType) { - return new Identifier('iterable'); - } - if ($phpStanType instanceof ThisType) { return new Identifier('self'); } diff --git a/packages/PHPStanStaticTypeMapper/src/TypeMapper/IterableTypeMapper.php b/packages/PHPStanStaticTypeMapper/src/TypeMapper/IterableTypeMapper.php new file mode 100644 index 000000000000..cd4ca079bef3 --- /dev/null +++ b/packages/PHPStanStaticTypeMapper/src/TypeMapper/IterableTypeMapper.php @@ -0,0 +1,37 @@ + Date: Tue, 14 Jan 2020 21:23:51 +0100 Subject: [PATCH 5/6] add ParentStaticTypeMapper --- .../src/PHPStanStaticTypeMapper.php | 5 --- .../src/TypeMapper/ParentStaticTypeMapper.php | 37 +++++++++++++++++++ 2 files changed, 37 insertions(+), 5 deletions(-) create mode 100644 packages/PHPStanStaticTypeMapper/src/TypeMapper/ParentStaticTypeMapper.php diff --git a/packages/PHPStanStaticTypeMapper/src/PHPStanStaticTypeMapper.php b/packages/PHPStanStaticTypeMapper/src/PHPStanStaticTypeMapper.php index 50b636ad9238..4628aeb13d87 100644 --- a/packages/PHPStanStaticTypeMapper/src/PHPStanStaticTypeMapper.php +++ b/packages/PHPStanStaticTypeMapper/src/PHPStanStaticTypeMapper.php @@ -43,7 +43,6 @@ use Rector\Php\PhpVersionProvider; use Rector\PHPStan\Type\AliasedObjectType; use Rector\PHPStan\Type\FullyQualifiedObjectType; -use Rector\PHPStan\Type\ParentStaticType; use Rector\PHPStan\Type\SelfObjectType; use Rector\PHPStan\Type\ShortenedObjectType; use Rector\PHPStanStaticTypeMapper\Contract\PHPStanStaticTypeMapperAwareInterface; @@ -138,10 +137,6 @@ public function mapToPhpParserNode(Type $phpStanType, ?string $kind = null): ?No return new Identifier('self'); } - if ($phpStanType instanceof ParentStaticType) { - return new Identifier('parent'); - } - if ($phpStanType instanceof StaticType) { return null; } diff --git a/packages/PHPStanStaticTypeMapper/src/TypeMapper/ParentStaticTypeMapper.php b/packages/PHPStanStaticTypeMapper/src/TypeMapper/ParentStaticTypeMapper.php new file mode 100644 index 000000000000..9e5687cb2304 --- /dev/null +++ b/packages/PHPStanStaticTypeMapper/src/TypeMapper/ParentStaticTypeMapper.php @@ -0,0 +1,37 @@ + Date: Tue, 14 Jan 2020 21:27:59 +0100 Subject: [PATCH 6/6] decouple ThisTypeMapper --- .../src/PHPStanStaticTypeMapper.php | 6 +-- .../src/TypeMapper/ParentStaticTypeMapper.php | 4 +- .../src/TypeMapper/ThisTypeMapper.php | 37 +++++++++++++++++++ 3 files changed, 40 insertions(+), 7 deletions(-) create mode 100644 packages/PHPStanStaticTypeMapper/src/TypeMapper/ThisTypeMapper.php diff --git a/packages/PHPStanStaticTypeMapper/src/PHPStanStaticTypeMapper.php b/packages/PHPStanStaticTypeMapper/src/PHPStanStaticTypeMapper.php index 4628aeb13d87..d8ca4c7e8ed7 100644 --- a/packages/PHPStanStaticTypeMapper/src/PHPStanStaticTypeMapper.php +++ b/packages/PHPStanStaticTypeMapper/src/PHPStanStaticTypeMapper.php @@ -30,7 +30,6 @@ use PHPStan\Type\ResourceType; use PHPStan\Type\StaticType; use PHPStan\Type\StringType; -use PHPStan\Type\ThisType; use PHPStan\Type\Type; use PHPStan\Type\TypeWithClassName; use PHPStan\Type\UnionType; @@ -122,6 +121,7 @@ public function mapToPhpParserNode(Type $phpStanType, ?string $kind = null): ?No } foreach ($this->typeMappers as $typeMapper) { + // it cannot be is_a for SelfObjectType, because type classes inherit from each other if (! is_a($phpStanType, $typeMapper->getNodeClass(), true)) { continue; } @@ -133,10 +133,6 @@ public function mapToPhpParserNode(Type $phpStanType, ?string $kind = null): ?No return new Identifier('array'); } - if ($phpStanType instanceof ThisType) { - return new Identifier('self'); - } - if ($phpStanType instanceof StaticType) { return null; } diff --git a/packages/PHPStanStaticTypeMapper/src/TypeMapper/ParentStaticTypeMapper.php b/packages/PHPStanStaticTypeMapper/src/TypeMapper/ParentStaticTypeMapper.php index 9e5687cb2304..da6fb13f278c 100644 --- a/packages/PHPStanStaticTypeMapper/src/TypeMapper/ParentStaticTypeMapper.php +++ b/packages/PHPStanStaticTypeMapper/src/TypeMapper/ParentStaticTypeMapper.php @@ -6,9 +6,9 @@ use PhpParser\Node; use PhpParser\Node\Identifier; +use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; use PHPStan\PhpDocParser\Ast\Type\TypeNode; use PHPStan\Type\Type; -use Rector\Exception\NotImplementedException; use Rector\PHPStan\Type\ParentStaticType; use Rector\PHPStanStaticTypeMapper\Contract\TypeMapperInterface; @@ -24,7 +24,7 @@ public function getNodeClass(): string */ public function mapToPHPStanPhpDocTypeNode(Type $type): TypeNode { - throw new NotImplementedException(); + return new IdentifierTypeNode('parent'); } /** diff --git a/packages/PHPStanStaticTypeMapper/src/TypeMapper/ThisTypeMapper.php b/packages/PHPStanStaticTypeMapper/src/TypeMapper/ThisTypeMapper.php new file mode 100644 index 000000000000..d8af6a281ba0 --- /dev/null +++ b/packages/PHPStanStaticTypeMapper/src/TypeMapper/ThisTypeMapper.php @@ -0,0 +1,37 @@ +