init
This commit is contained in:
94
backend/vendor/symfony/property-info/Util/LegacyTypeConverter.php
vendored
Normal file
94
backend/vendor/symfony/property-info/Util/LegacyTypeConverter.php
vendored
Normal file
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\PropertyInfo\Util;
|
||||
|
||||
use Symfony\Component\PropertyInfo\Type as LegacyType;
|
||||
use Symfony\Component\TypeInfo\Type;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class LegacyTypeConverter
|
||||
{
|
||||
/**
|
||||
* @param LegacyType[]|null $legacyTypes
|
||||
*/
|
||||
public static function toTypeInfoType(?array $legacyTypes): ?Type
|
||||
{
|
||||
if (null === $legacyTypes || [] === $legacyTypes) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$nullable = false;
|
||||
$types = [];
|
||||
|
||||
foreach ($legacyTypes as $legacyType) {
|
||||
switch ($legacyType->getBuiltinType()) {
|
||||
case LegacyType::BUILTIN_TYPE_ARRAY:
|
||||
$typeInfoType = Type::array(self::toTypeInfoType($legacyType->getCollectionValueTypes()), self::toTypeInfoType($legacyType->getCollectionKeyTypes()));
|
||||
break;
|
||||
case LegacyType::BUILTIN_TYPE_BOOL:
|
||||
$typeInfoType = Type::bool();
|
||||
break;
|
||||
case LegacyType::BUILTIN_TYPE_CALLABLE:
|
||||
$typeInfoType = Type::callable();
|
||||
break;
|
||||
case LegacyType::BUILTIN_TYPE_FALSE:
|
||||
$typeInfoType = Type::false();
|
||||
break;
|
||||
case LegacyType::BUILTIN_TYPE_FLOAT:
|
||||
$typeInfoType = Type::float();
|
||||
break;
|
||||
case LegacyType::BUILTIN_TYPE_INT:
|
||||
$typeInfoType = Type::int();
|
||||
break;
|
||||
case LegacyType::BUILTIN_TYPE_ITERABLE:
|
||||
$typeInfoType = Type::iterable(self::toTypeInfoType($legacyType->getCollectionValueTypes()), self::toTypeInfoType($legacyType->getCollectionKeyTypes()));
|
||||
break;
|
||||
case LegacyType::BUILTIN_TYPE_OBJECT:
|
||||
if ($legacyType->isCollection()) {
|
||||
$typeInfoType = Type::collection(Type::object($legacyType->getClassName()), self::toTypeInfoType($legacyType->getCollectionValueTypes()), self::toTypeInfoType($legacyType->getCollectionKeyTypes()));
|
||||
} else {
|
||||
$typeInfoType = Type::object($legacyType->getClassName());
|
||||
}
|
||||
|
||||
break;
|
||||
case LegacyType::BUILTIN_TYPE_RESOURCE:
|
||||
$typeInfoType = Type::resource();
|
||||
break;
|
||||
case LegacyType::BUILTIN_TYPE_STRING:
|
||||
$typeInfoType = Type::string();
|
||||
break;
|
||||
case LegacyType::BUILTIN_TYPE_TRUE:
|
||||
$typeInfoType = Type::true();
|
||||
break;
|
||||
default:
|
||||
$typeInfoType = null;
|
||||
break;
|
||||
}
|
||||
|
||||
if (LegacyType::BUILTIN_TYPE_NULL === $legacyType->getBuiltinType() || $legacyType->isNullable()) {
|
||||
$nullable = true;
|
||||
}
|
||||
|
||||
if (null !== $typeInfoType) {
|
||||
$types[] = $typeInfoType;
|
||||
}
|
||||
}
|
||||
|
||||
if (1 === \count($types)) {
|
||||
return $nullable ? Type::nullable($types[0]) : $types[0];
|
||||
}
|
||||
|
||||
return $nullable ? Type::nullable(Type::union(...$types)) : Type::union(...$types);
|
||||
}
|
||||
}
|
||||
475
backend/vendor/symfony/property-info/Util/PhpDocTypeHelper.php
vendored
Normal file
475
backend/vendor/symfony/property-info/Util/PhpDocTypeHelper.php
vendored
Normal file
@@ -0,0 +1,475 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\PropertyInfo\Util;
|
||||
|
||||
use phpDocumentor\Reflection\PseudoType;
|
||||
use phpDocumentor\Reflection\PseudoTypes\ConstExpression;
|
||||
use phpDocumentor\Reflection\PseudoTypes\Generic;
|
||||
use phpDocumentor\Reflection\PseudoTypes\List_;
|
||||
use phpDocumentor\Reflection\PseudoTypes\Scalar;
|
||||
use phpDocumentor\Reflection\Type as DocType;
|
||||
use phpDocumentor\Reflection\Types\Array_;
|
||||
use phpDocumentor\Reflection\Types\Collection;
|
||||
use phpDocumentor\Reflection\Types\Compound;
|
||||
use phpDocumentor\Reflection\Types\Integer;
|
||||
use phpDocumentor\Reflection\Types\Mixed_;
|
||||
use phpDocumentor\Reflection\Types\Null_;
|
||||
use phpDocumentor\Reflection\Types\Nullable;
|
||||
use phpDocumentor\Reflection\Types\Scalar as LegacyScalar;
|
||||
use phpDocumentor\Reflection\Types\String_;
|
||||
use Symfony\Component\PropertyInfo\Type as LegacyType;
|
||||
use Symfony\Component\TypeInfo\Type;
|
||||
use Symfony\Component\TypeInfo\Type\BuiltinType;
|
||||
use Symfony\Component\TypeInfo\TypeIdentifier;
|
||||
|
||||
// Workaround for phpdocumentor/type-resolver < 1.6
|
||||
// We trigger the autoloader here, so we don't need to trigger it inside the loop later.
|
||||
class_exists(List_::class);
|
||||
|
||||
/**
|
||||
* Transforms a php doc type to a {@link Type} instance.
|
||||
*
|
||||
* @author Kévin Dunglas <dunglas@gmail.com>
|
||||
* @author Guilhem N. <egetick@gmail.com>
|
||||
*/
|
||||
final class PhpDocTypeHelper
|
||||
{
|
||||
/**
|
||||
* Creates a {@see LegacyType} from a PHPDoc type.
|
||||
*
|
||||
* @deprecated since Symfony 7.3, use "getType" instead
|
||||
*
|
||||
* @return LegacyType[]
|
||||
*/
|
||||
public function getTypes(DocType $varType): array
|
||||
{
|
||||
trigger_deprecation('symfony/property-info', '7.3', 'The "%s()" method is deprecated, use "%s::getType()" instead.', __METHOD__, self::class);
|
||||
|
||||
if ($varType instanceof ConstExpression) {
|
||||
// It's safer to fall back to other extractors here, as resolving const types correctly is not easy at the moment
|
||||
return [];
|
||||
}
|
||||
|
||||
$types = [];
|
||||
$nullable = false;
|
||||
|
||||
if ($varType instanceof Nullable) {
|
||||
$nullable = true;
|
||||
$varType = $varType->getActualType();
|
||||
}
|
||||
|
||||
if ($varType instanceof LegacyScalar || $varType instanceof Scalar) {
|
||||
return [
|
||||
new LegacyType(LegacyType::BUILTIN_TYPE_BOOL),
|
||||
new LegacyType(LegacyType::BUILTIN_TYPE_FLOAT),
|
||||
new LegacyType(LegacyType::BUILTIN_TYPE_INT),
|
||||
new LegacyType(LegacyType::BUILTIN_TYPE_STRING),
|
||||
];
|
||||
}
|
||||
|
||||
if (!$varType instanceof Compound) {
|
||||
if ($varType instanceof Null_) {
|
||||
$nullable = true;
|
||||
}
|
||||
|
||||
$type = $this->createLegacyType($varType, $nullable);
|
||||
if (null !== $type) {
|
||||
$types[] = $type;
|
||||
}
|
||||
|
||||
return $types;
|
||||
}
|
||||
|
||||
$varTypes = [];
|
||||
for ($typeIndex = 0; $varType->has($typeIndex); ++$typeIndex) {
|
||||
$type = $varType->get($typeIndex);
|
||||
|
||||
if ($type instanceof Mixed_) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if ($type instanceof ConstExpression) {
|
||||
// It's safer to fall back to other extractors here, as resolving const types correctly is not easy at the moment
|
||||
return [];
|
||||
}
|
||||
|
||||
// If null is present, all types are nullable
|
||||
if ($type instanceof Null_) {
|
||||
$nullable = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($type instanceof Nullable) {
|
||||
$nullable = true;
|
||||
$type = $type->getActualType();
|
||||
}
|
||||
|
||||
$varTypes[] = $type;
|
||||
}
|
||||
|
||||
foreach ($varTypes as $varType) {
|
||||
$type = $this->createLegacyType($varType, $nullable);
|
||||
if (null !== $type) {
|
||||
$types[] = $type;
|
||||
}
|
||||
}
|
||||
|
||||
return $types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@see Type} from a PHPDoc type.
|
||||
*/
|
||||
public function getType(DocType $varType): ?Type
|
||||
{
|
||||
if ($varType instanceof ConstExpression) {
|
||||
// It's safer to fall back to other extractors here, as resolving const types correctly is not easy at the moment
|
||||
return null;
|
||||
}
|
||||
|
||||
$nullable = false;
|
||||
|
||||
if ($varType instanceof Nullable) {
|
||||
$nullable = true;
|
||||
$varType = $varType->getActualType();
|
||||
}
|
||||
|
||||
if (!$varType instanceof Compound) {
|
||||
if ($varType instanceof Null_) {
|
||||
$nullable = true;
|
||||
}
|
||||
|
||||
$type = $this->createType($varType);
|
||||
|
||||
return $nullable ? Type::nullable($type) : $type;
|
||||
}
|
||||
|
||||
$varTypes = [];
|
||||
for ($typeIndex = 0; $varType->has($typeIndex); ++$typeIndex) {
|
||||
$type = $varType->get($typeIndex);
|
||||
|
||||
if ($type instanceof ConstExpression) {
|
||||
// It's safer to fall back to other extractors here, as resolving const types correctly is not easy at the moment
|
||||
return null;
|
||||
}
|
||||
|
||||
// If null is present, all types are nullable
|
||||
if ($type instanceof Null_) {
|
||||
$nullable = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($type instanceof Nullable) {
|
||||
$nullable = true;
|
||||
$type = $type->getActualType();
|
||||
}
|
||||
|
||||
$varTypes[] = $type;
|
||||
}
|
||||
|
||||
$unionTypes = [];
|
||||
foreach ($varTypes as $varType) {
|
||||
if (!$t = $this->createType($varType)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($t instanceof BuiltinType && TypeIdentifier::MIXED === $t->getTypeIdentifier()) {
|
||||
return Type::mixed();
|
||||
}
|
||||
|
||||
$unionTypes[] = $t;
|
||||
}
|
||||
|
||||
if (!$unionTypes) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$type = 1 === \count($unionTypes) ? $unionTypes[0] : Type::union(...$unionTypes);
|
||||
|
||||
return $nullable ? Type::nullable($type) : $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@see LegacyType} from a PHPDoc type.
|
||||
*/
|
||||
private function createLegacyType(DocType $type, bool $nullable): ?LegacyType
|
||||
{
|
||||
$docType = (string) $type;
|
||||
|
||||
if ('mixed[]' === $docType) {
|
||||
$docType = 'array';
|
||||
} elseif ('array' !== $docType && $type instanceof Array_ && $this->hasNoExplicitKeyType($type)) {
|
||||
$docType = \sprintf('%s[]', $type->getValueType());
|
||||
}
|
||||
|
||||
if ($type instanceof Collection || $type instanceof Generic) {
|
||||
$fqsen = $type->getFqsen();
|
||||
if ($type instanceof Collection && $fqsen && 'list' === $fqsen->getName() && !class_exists(List_::class, false) && !class_exists((string) $fqsen)) {
|
||||
// Workaround for phpdocumentor/type-resolver < 1.6
|
||||
return new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, $nullable, null, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), $this->getTypes($type->getValueType()));
|
||||
}
|
||||
|
||||
[$phpType, $class] = $this->getPhpTypeAndClass((string) $fqsen);
|
||||
|
||||
$collection = is_a($class, \Traversable::class, true) || is_a($class, \ArrayAccess::class, true);
|
||||
|
||||
// it's safer to fall back to other extractors if the generic type is too abstract
|
||||
if (!$collection && !class_exists($class, false) && !interface_exists($class, false)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($type instanceof Generic) {
|
||||
$genericTypes = $type->getTypes();
|
||||
|
||||
if (null === $valueType = $genericTypes[1] ?? null) {
|
||||
$keyType = new Compound([new String_(), new Integer()]);
|
||||
$valueType = $genericTypes[0] ?? new Mixed_();
|
||||
} else {
|
||||
$keyType = $genericTypes[0];
|
||||
}
|
||||
} else {
|
||||
$keyType = $type->getKeyType();
|
||||
$valueType = $type->getValueType();
|
||||
}
|
||||
|
||||
$keys = $this->getTypes($keyType);
|
||||
$values = $this->getTypes($valueType);
|
||||
|
||||
return new LegacyType($phpType, $nullable, $class, $collection, $keys, $values);
|
||||
}
|
||||
|
||||
// Cannot guess
|
||||
if (!$docType || 'mixed' === $docType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (str_ends_with($docType, '[]') && $type instanceof Array_) {
|
||||
$collectionKeyTypes = new LegacyType(LegacyType::BUILTIN_TYPE_INT);
|
||||
$collectionValueTypes = $this->getTypes($type->getValueType());
|
||||
|
||||
return new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, $nullable, null, true, $collectionKeyTypes, $collectionValueTypes);
|
||||
}
|
||||
|
||||
if ((str_starts_with($docType, 'list<') || str_starts_with($docType, 'array<')) && $type instanceof Array_) {
|
||||
// array<value> is converted to x[] which is handled above
|
||||
// so it's only necessary to handle array<key, value> here
|
||||
$collectionKeyTypes = $this->getTypes($type->getKeyType());
|
||||
$collectionValueTypes = $this->getTypes($type->getValueType());
|
||||
|
||||
return new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, $nullable, null, true, $collectionKeyTypes, $collectionValueTypes);
|
||||
}
|
||||
|
||||
if ($type instanceof PseudoType) {
|
||||
if ($type->underlyingType() instanceof Integer) {
|
||||
return new LegacyType(LegacyType::BUILTIN_TYPE_INT, $nullable, null);
|
||||
} elseif ($type->underlyingType() instanceof String_) {
|
||||
return new LegacyType(LegacyType::BUILTIN_TYPE_STRING, $nullable, null);
|
||||
}
|
||||
}
|
||||
|
||||
$docType = $this->normalizeType($docType);
|
||||
[$phpType, $class] = $this->getPhpTypeAndClass($docType);
|
||||
|
||||
if ('array' === $docType) {
|
||||
return new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, $nullable, null, true, null, null);
|
||||
}
|
||||
|
||||
return new LegacyType($phpType, $nullable, $class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@see Type} from a PHPDoc type.
|
||||
*/
|
||||
private function createType(DocType $docType): ?Type
|
||||
{
|
||||
$docTypeString = (string) $docType;
|
||||
|
||||
if ('mixed[]' === $docTypeString) {
|
||||
$docTypeString = 'array';
|
||||
}
|
||||
|
||||
if ($docType instanceof Generic) {
|
||||
$fqsen = $docType->getFqsen();
|
||||
|
||||
[$phpType, $class] = $this->getPhpTypeAndClass((string) $fqsen);
|
||||
|
||||
$collection = is_a($class, \Traversable::class, true) || is_a($class, \ArrayAccess::class, true);
|
||||
|
||||
// it's safer to fall back to other extractors if the generic type is too abstract
|
||||
if (!$collection && !class_exists($class, false) && !interface_exists($class, false)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$genericTypes = $docType->getTypes();
|
||||
$type = null !== $class ? Type::object($class) : Type::builtin($phpType);
|
||||
|
||||
if ($collection) {
|
||||
if (null === $valueType = $genericTypes[1] ?? null) {
|
||||
$keyType = null;
|
||||
$valueType = $genericTypes[0] ?? null;
|
||||
} else {
|
||||
$keyType = $genericTypes[0] ?? null;
|
||||
}
|
||||
|
||||
$value = $valueType ? $this->getType($valueType) : null;
|
||||
$key = $keyType ? $this->getType($keyType) : null;
|
||||
|
||||
return Type::collection($type, $value, $key);
|
||||
}
|
||||
|
||||
$variableTypes = array_map(fn ($t) => $this->getType($t), $genericTypes);
|
||||
|
||||
return Type::generic($type, ...array_filter($variableTypes));
|
||||
}
|
||||
|
||||
if ($docType instanceof Collection) {
|
||||
$fqsen = $docType->getFqsen();
|
||||
if ($fqsen && 'list' === $fqsen->getName() && !class_exists(List_::class, false) && !class_exists((string) $fqsen)) {
|
||||
// Workaround for phpdocumentor/type-resolver < 1.6
|
||||
return Type::list($this->getType($docType->getValueType()));
|
||||
}
|
||||
|
||||
[$phpType, $class] = $this->getPhpTypeAndClass((string) $fqsen);
|
||||
|
||||
$collection = is_a($class, \Traversable::class, true) || is_a($class, \ArrayAccess::class, true);
|
||||
|
||||
// it's safer to fall back to other extractors if the generic type is too abstract
|
||||
if (!$collection && !class_exists($class, false) && !interface_exists($class, false)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$type = null !== $class ? Type::object($class) : Type::builtin($phpType);
|
||||
|
||||
if ($collection) {
|
||||
$value = $this->getType($docType->getValueType());
|
||||
$key = $this->getType($docType->getKeyType());
|
||||
|
||||
return Type::collection($type, $value, $key);
|
||||
}
|
||||
|
||||
$variableTypes = [];
|
||||
|
||||
if (!$this->hasNoExplicitKeyType($docType) && null !== $keyType = $this->getType($docType->getKeyType())) {
|
||||
$variableTypes[] = $keyType;
|
||||
}
|
||||
|
||||
if (null !== $valueType = $this->getType($docType->getValueType())) {
|
||||
$variableTypes[] = $valueType;
|
||||
}
|
||||
|
||||
return Type::generic($type, ...$variableTypes);
|
||||
}
|
||||
|
||||
if (!$docTypeString) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($docType instanceof Array_ && $this->hasNoExplicitKeyType($docType) && str_starts_with($docTypeString, 'array<')) {
|
||||
return Type::list($this->getType($docType->getValueType()));
|
||||
}
|
||||
|
||||
if (str_ends_with($docTypeString, '[]') && $docType instanceof Array_) {
|
||||
return Type::list($this->getType($docType->getValueType()));
|
||||
}
|
||||
|
||||
if (str_starts_with($docTypeString, 'list<') && $docType instanceof Array_) {
|
||||
$collectionValueType = $this->getType($docType->getValueType());
|
||||
|
||||
return Type::list($collectionValueType);
|
||||
}
|
||||
|
||||
if (str_starts_with($docTypeString, 'array<') && $docType instanceof Array_) {
|
||||
// array<value> is converted to x[] which is handled above
|
||||
// so it's only necessary to handle array<key, value> here
|
||||
$collectionKeyType = $this->getType($docType->getKeyType());
|
||||
$collectionValueType = $this->getType($docType->getValueType());
|
||||
|
||||
return Type::array($collectionValueType, $collectionKeyType);
|
||||
}
|
||||
|
||||
$docTypeString = match ($docTypeString) {
|
||||
'integer' => 'int',
|
||||
'boolean' => 'bool',
|
||||
// real is not part of the PHPDoc standard, so we ignore it
|
||||
'double' => 'float',
|
||||
'callback' => 'callable',
|
||||
'void' => 'null',
|
||||
default => $docTypeString,
|
||||
};
|
||||
|
||||
[$phpType, $class] = $this->getPhpTypeAndClass($docTypeString);
|
||||
|
||||
if ('array' === $docTypeString) {
|
||||
return Type::array();
|
||||
}
|
||||
|
||||
if (null === $class) {
|
||||
return Type::builtin($phpType);
|
||||
}
|
||||
|
||||
if ($docType instanceof LegacyScalar || $docType instanceof Scalar) {
|
||||
return Type::object('scalar');
|
||||
}
|
||||
|
||||
if ($docType instanceof PseudoType) {
|
||||
if ($docType->underlyingType() instanceof Integer) {
|
||||
return Type::int();
|
||||
}
|
||||
|
||||
if ($docType->underlyingType() instanceof String_) {
|
||||
return Type::string();
|
||||
}
|
||||
|
||||
// It's safer to fall back to other extractors here, as resolving pseudo types correctly is not easy at the moment
|
||||
return null;
|
||||
}
|
||||
|
||||
return Type::object($class);
|
||||
}
|
||||
|
||||
private function normalizeType(string $docType): string
|
||||
{
|
||||
return match ($docType) {
|
||||
'integer' => 'int',
|
||||
'boolean' => 'bool',
|
||||
// real is not part of the PHPDoc standard, so we ignore it
|
||||
'double' => 'float',
|
||||
'callback' => 'callable',
|
||||
'void' => 'null',
|
||||
default => $docType,
|
||||
};
|
||||
}
|
||||
|
||||
private function hasNoExplicitKeyType(Array_|Collection $type): bool
|
||||
{
|
||||
if (method_exists($type, 'getOriginalKeyType')) {
|
||||
return null === $type->getOriginalKeyType();
|
||||
}
|
||||
|
||||
// Workaround for phpdocumentor/reflection-docblock < 6
|
||||
// "getOriginalKeyType()" doesn't exist, so we check if key type is Compound(string, int) which is the default.
|
||||
return $type->getKeyType() instanceof Compound;
|
||||
}
|
||||
|
||||
private function getPhpTypeAndClass(string $docType): array
|
||||
{
|
||||
if (\in_array($docType, TypeIdentifier::values(), true)) {
|
||||
return [$docType, null];
|
||||
}
|
||||
|
||||
if (\in_array($docType, ['parent', 'self', 'static'], true)) {
|
||||
return ['object', $docType];
|
||||
}
|
||||
|
||||
return ['object', ltrim($docType, '\\')];
|
||||
}
|
||||
}
|
||||
214
backend/vendor/symfony/property-info/Util/PhpStanTypeHelper.php
vendored
Normal file
214
backend/vendor/symfony/property-info/Util/PhpStanTypeHelper.php
vendored
Normal file
@@ -0,0 +1,214 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\PropertyInfo\Util;
|
||||
|
||||
use PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode;
|
||||
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagValueNode;
|
||||
use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode;
|
||||
use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode;
|
||||
use PHPStan\PhpDocParser\Ast\Type\ArrayShapeNode;
|
||||
use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode;
|
||||
use PHPStan\PhpDocParser\Ast\Type\CallableTypeNode;
|
||||
use PHPStan\PhpDocParser\Ast\Type\CallableTypeParameterNode;
|
||||
use PHPStan\PhpDocParser\Ast\Type\ConstTypeNode;
|
||||
use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode;
|
||||
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
|
||||
use PHPStan\PhpDocParser\Ast\Type\NullableTypeNode;
|
||||
use PHPStan\PhpDocParser\Ast\Type\ThisTypeNode;
|
||||
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
|
||||
use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode;
|
||||
use Symfony\Component\PropertyInfo\Type;
|
||||
use Symfony\Component\TypeInfo\TypeContext\TypeContext;
|
||||
|
||||
/**
|
||||
* Transforms a php doc tag value to a {@link Type} instance.
|
||||
*
|
||||
* @author Baptiste Leduc <baptiste.leduc@gmail.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class PhpStanTypeHelper
|
||||
{
|
||||
/**
|
||||
* Creates a {@see Type} from a PhpDocTagValueNode type.
|
||||
*
|
||||
* @return Type[]
|
||||
*/
|
||||
public function getTypes(PhpDocTagValueNode $node, TypeContext $typeContext): array
|
||||
{
|
||||
if ($node instanceof ParamTagValueNode || $node instanceof ReturnTagValueNode || $node instanceof VarTagValueNode) {
|
||||
return $this->compressNullableType($this->extractTypes($node->type, $typeContext));
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Because PhpStan extract null as a separated type when Symfony / PHP compress it in the first available type we
|
||||
* need this method to mimic how Symfony want null types.
|
||||
*
|
||||
* @param Type[] $types
|
||||
*
|
||||
* @return Type[]
|
||||
*/
|
||||
private function compressNullableType(array $types): array
|
||||
{
|
||||
$firstTypeIndex = null;
|
||||
$nullableTypeIndex = null;
|
||||
|
||||
foreach ($types as $k => $type) {
|
||||
if (null === $firstTypeIndex && Type::BUILTIN_TYPE_NULL !== $type->getBuiltinType() && !$type->isNullable()) {
|
||||
$firstTypeIndex = $k;
|
||||
}
|
||||
|
||||
if (null === $nullableTypeIndex && Type::BUILTIN_TYPE_NULL === $type->getBuiltinType()) {
|
||||
$nullableTypeIndex = $k;
|
||||
}
|
||||
|
||||
if (null !== $firstTypeIndex && null !== $nullableTypeIndex) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (null !== $firstTypeIndex && null !== $nullableTypeIndex) {
|
||||
$firstType = $types[$firstTypeIndex];
|
||||
$types[$firstTypeIndex] = new Type(
|
||||
$firstType->getBuiltinType(),
|
||||
true,
|
||||
$firstType->getClassName(),
|
||||
$firstType->isCollection(),
|
||||
$firstType->getCollectionKeyTypes(),
|
||||
$firstType->getCollectionValueTypes()
|
||||
);
|
||||
unset($types[$nullableTypeIndex]);
|
||||
}
|
||||
|
||||
return array_values($types);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Type[]
|
||||
*/
|
||||
private function extractTypes(TypeNode $node, TypeContext $typeContext): array
|
||||
{
|
||||
if ($node instanceof UnionTypeNode) {
|
||||
$types = [];
|
||||
foreach ($node->types as $type) {
|
||||
if ($type instanceof ConstTypeNode) {
|
||||
// It's safer to fall back to other extractors here, as resolving const types correctly is not easy at the moment
|
||||
return [];
|
||||
}
|
||||
foreach ($this->extractTypes($type, $typeContext) as $subType) {
|
||||
$types[] = $subType;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->compressNullableType($types);
|
||||
}
|
||||
if ($node instanceof GenericTypeNode) {
|
||||
if ('class-string' === $node->type->name) {
|
||||
return [new Type(Type::BUILTIN_TYPE_STRING)];
|
||||
}
|
||||
|
||||
[$mainType] = $this->extractTypes($node->type, $typeContext);
|
||||
|
||||
if (Type::BUILTIN_TYPE_INT === $mainType->getBuiltinType()) {
|
||||
return [$mainType];
|
||||
}
|
||||
|
||||
$collection = $mainType->isCollection() || is_a($mainType->getClassName(), \Traversable::class, true) || is_a($mainType->getClassName(), \ArrayAccess::class, true);
|
||||
|
||||
// it's safer to fall back to other extractors if the generic type is too abstract
|
||||
if (!$collection && !class_exists($mainType->getClassName()) && !interface_exists($mainType->getClassName(), false)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$collectionKeyTypes = $mainType->getCollectionKeyTypes();
|
||||
$collectionKeyValues = [];
|
||||
if (1 === \count($node->genericTypes)) {
|
||||
foreach ($this->extractTypes($node->genericTypes[0], $typeContext) as $subType) {
|
||||
$collectionKeyValues[] = $subType;
|
||||
}
|
||||
} elseif (2 === \count($node->genericTypes)) {
|
||||
foreach ($this->extractTypes($node->genericTypes[0], $typeContext) as $keySubType) {
|
||||
$collectionKeyTypes[] = $keySubType;
|
||||
}
|
||||
foreach ($this->extractTypes($node->genericTypes[1], $typeContext) as $valueSubType) {
|
||||
$collectionKeyValues[] = $valueSubType;
|
||||
}
|
||||
}
|
||||
|
||||
return [new Type($mainType->getBuiltinType(), $mainType->isNullable(), $mainType->getClassName(), $collection, $collectionKeyTypes, $collectionKeyValues)];
|
||||
}
|
||||
if ($node instanceof ArrayShapeNode) {
|
||||
return [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true)];
|
||||
}
|
||||
if ($node instanceof ArrayTypeNode) {
|
||||
return [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, [new Type(Type::BUILTIN_TYPE_INT)], $this->extractTypes($node->type, $typeContext))];
|
||||
}
|
||||
if ($node instanceof CallableTypeNode || $node instanceof CallableTypeParameterNode) {
|
||||
return [new Type(Type::BUILTIN_TYPE_CALLABLE)];
|
||||
}
|
||||
if ($node instanceof NullableTypeNode) {
|
||||
$subTypes = $this->extractTypes($node->type, $typeContext);
|
||||
if (\count($subTypes) > 1) {
|
||||
$subTypes[] = new Type(Type::BUILTIN_TYPE_NULL);
|
||||
|
||||
return $subTypes;
|
||||
}
|
||||
|
||||
return [new Type($subTypes[0]->getBuiltinType(), true, $subTypes[0]->getClassName(), $subTypes[0]->isCollection(), $subTypes[0]->getCollectionKeyTypes(), $subTypes[0]->getCollectionValueTypes())];
|
||||
}
|
||||
if ($node instanceof ThisTypeNode) {
|
||||
return [new Type(Type::BUILTIN_TYPE_OBJECT, false, $typeContext->getCalledClass())];
|
||||
}
|
||||
if ($node instanceof IdentifierTypeNode) {
|
||||
if (\in_array($node->name, Type::$builtinTypes, true)) {
|
||||
return [new Type($node->name, false, null, \in_array($node->name, Type::$builtinCollectionTypes, true))];
|
||||
}
|
||||
|
||||
return match ($node->name) {
|
||||
'integer',
|
||||
'positive-int',
|
||||
'negative-int',
|
||||
'non-positive-int',
|
||||
'non-negative-int',
|
||||
'non-zero-int' => [new Type(Type::BUILTIN_TYPE_INT)],
|
||||
'double' => [new Type(Type::BUILTIN_TYPE_FLOAT)],
|
||||
'list',
|
||||
'non-empty-list' => [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT))],
|
||||
'non-empty-array' => [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true)],
|
||||
'mixed' => [], // mixed seems to be ignored in all other extractors
|
||||
'parent' => [new Type(Type::BUILTIN_TYPE_OBJECT, false, $node->name)],
|
||||
'static',
|
||||
'self' => [new Type(Type::BUILTIN_TYPE_OBJECT, false, $typeContext->getCalledClass())],
|
||||
'class-string',
|
||||
'html-escaped-string',
|
||||
'lowercase-string',
|
||||
'non-empty-lowercase-string',
|
||||
'non-empty-string',
|
||||
'numeric-string',
|
||||
'trait-string',
|
||||
'interface-string',
|
||||
'literal-string' => [new Type(Type::BUILTIN_TYPE_STRING)],
|
||||
'void' => [new Type(Type::BUILTIN_TYPE_NULL)],
|
||||
'scalar' => [new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_FLOAT), new Type(Type::BUILTIN_TYPE_STRING), new Type(Type::BUILTIN_TYPE_BOOL)],
|
||||
'number' => [new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_FLOAT)],
|
||||
'numeric' => [new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_FLOAT), new Type(Type::BUILTIN_TYPE_STRING)],
|
||||
'array-key' => [new Type(Type::BUILTIN_TYPE_STRING), new Type(Type::BUILTIN_TYPE_INT)],
|
||||
default => [new Type(Type::BUILTIN_TYPE_OBJECT, false, $typeContext->normalize($node->name))],
|
||||
};
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user