Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
skalpa committed Aug 15, 2024
1 parent 031dde7 commit 07dce5d
Show file tree
Hide file tree
Showing 10 changed files with 246 additions and 93 deletions.
23 changes: 6 additions & 17 deletions src/Symfony/Component/TypeInfo/Type.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@
namespace Symfony\Component\TypeInfo;

use Symfony\Component\TypeInfo\Exception\LogicException;
use Symfony\Component\TypeInfo\Type\BuiltinType;
use Symfony\Component\TypeInfo\Type\ObjectType;

/**
* @author Mathias Arlaud <[email protected]>
Expand All @@ -25,27 +23,18 @@ abstract class Type implements \Stringable
{
use TypeFactoryTrait;

abstract public function getBaseType(): BuiltinType|ObjectType;

/**
* @param TypeIdentifier|class-string $subject
*/
abstract public function isA(TypeIdentifier|string $subject): bool;
abstract public function getTypeIdentifier(): TypeIdentifierInterface;

abstract public function asNonNullable(): self;

abstract public function isNullable(): bool;

/**
* @param callable(Type): bool $callable
* Get all the classes/interfaces implemented by an "object" type.
*/
public function is(callable $callable): bool
{
return $callable($this);
}
abstract public function getClassNames(): array;

public function isNullable(): bool
{
return $this->is(fn (Type $t): bool => $t->isA(TypeIdentifier::NULL) || $t->isA(TypeIdentifier::MIXED));
}
abstract public function accepts(Type $type): bool;

/**
* Graceful fallback for unexisting methods.
Expand Down
125 changes: 125 additions & 0 deletions src/Symfony/Component/TypeInfo/Type/AtomicType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\TypeInfo\Type;

use Symfony\Component\TypeInfo\Exception\LogicException;
use Symfony\Component\TypeInfo\Type;
use Symfony\Component\TypeInfo\TypeIdentifier;
use Symfony\Component\TypeInfo\TypeIdentifierInterface;

/**
* Custom atomic type with no pre-established behavior.
*
* A custom type only accepts other custom types with the same name, type identifier and compatible variable types.
*
* They are by default seen as non-nullable, but users are free to implement their own types
* with a different behavior by extending this class.
*
* @template TPrimitive of TypeIdentifier
* @template TVariables of Type|void
*
* @author Mathias Arlaud <[email protected]>
* @author Baptiste Leduc <[email protected]>
*
* @experimental
*/
class AtomicType extends Type
{
/**
* @var list<TVariables>
*/
private readonly array $variableTypes;

/**
* @param TPrimitive $typeIdentifier Native type represented by this type
* @param string $name Type name
* @param bool $isNullable Whether this type is nullable
* @param string $compareName Whether to check another type name before accepting it
* @param TVariables ...$variableTypes
*/
public function __construct(
private readonly TypeIdentifierInterface $typeIdentifier,
private readonly string $name,
private readonly bool $isNullable = false,
private readonly bool $compareName = false,
Type ...$variableTypes
) {
$this->variableTypes = $variableTypes;
}

final public function getName(): string
{
return $this->name;
}

/**
* @return list<TVariables>
*/
final public function getVariableTypes(): array
{
return $this->variableTypes;
}

final public function getTypeIdentifier(): TypeIdentifierInterface
{
return $this->typeIdentifier;
}

public function isNullable(): bool
{
return $this->isNullable;
}

public function asNonNullable(): Type
{
if (!$this->isNullable()) {
return $this;
}

throw new LogicException(sprintf('"%s" cannot be turned as non nullable.', (string) $this));
}

public function accepts(Type $type): bool
{
if ($this->compareName && (!$type instanceof self || $this->name !== $type->name)) {
return false;
}

return $this->typeIdentifier->accepts($type->getTypeIdentifier()) && $this->acceptsVariables($type);
}

public function __toString(): string
{
return $this->name.$this->renderVariableTypes();
}

protected function renderVariableTypes(): string
{
return $this->variableTypes ? '<'.implode(', ', $this->variableTypes).'>' : '';
}

protected function acceptsVariables(Type $type): bool
{
if (count($this->variableTypes) && !$type instanceof self) {
return false;
}
$types = $type->getVariableTypes();

foreach ($this->getVariableTypes() as $n => $variableType) {
if (!isset($types[$n]) || !$variableType->accepts($types[$n])) {
return false;
}
}

return true;
}
}
42 changes: 7 additions & 35 deletions src/Symfony/Component/TypeInfo/Type/BuiltinType.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,61 +16,38 @@
use Symfony\Component\TypeInfo\TypeIdentifier;

/**
* Represents a PHP builtin type.
*
* @author Mathias Arlaud <[email protected]>
* @author Baptiste Leduc <[email protected]>
*
* @template T of TypeIdentifier
*
* @experimental
*/
final class BuiltinType extends Type
final class BuiltinType extends AtomicType
{
/**
* @param T $typeIdentifier
*/
public function __construct(
private readonly TypeIdentifier $typeIdentifier,
TypeIdentifier $typeIdentifier,
) {
}

public function getBaseType(): self|ObjectType
{
return $this;
}

/**
* @return T
*/
public function getTypeIdentifier(): TypeIdentifier
{
return $this->typeIdentifier;
}

public function isA(TypeIdentifier|string $subject): bool
{
if ($subject instanceof TypeIdentifier) {
return $this->getTypeIdentifier() === $subject;
}

try {
return TypeIdentifier::from($subject) === $this->getTypeIdentifier();
} catch (\ValueError) {
return false;
}
parent::__construct($typeIdentifier, $typeIdentifier->value, TypeIdentifier::NULL === $typeIdentifier || TypeIdentifier::MIXED === $typeIdentifier);
}

/**
* @return self|UnionType<BuiltinType<TypeIdentifier::OBJECT>|BuiltinType<TypeIdentifier::RESOURCE>|BuiltinType<TypeIdentifier::ARRAY>|BuiltinType<TypeIdentifier::STRING>|BuiltinType<TypeIdentifier::FLOAT>|BuiltinType<TypeIdentifier::INT>|BuiltinType<TypeIdentifier::BOOL>>
*/
public function asNonNullable(): self|UnionType
{
if (TypeIdentifier::NULL === $this->typeIdentifier) {
if (TypeIdentifier::NULL === $this->getTypeIdentifier()) {
throw new LogicException('"null" cannot be turned as non nullable.');
}

// "mixed" is an alias of "object|resource|array|string|float|int|bool|null"
// therefore, its non-nullable version is "object|resource|array|string|float|int|bool"
if (TypeIdentifier::MIXED === $this->typeIdentifier) {
if (TypeIdentifier::MIXED === $this->getTypeIdentifier()) {
return new UnionType(
new self(TypeIdentifier::OBJECT),
new self(TypeIdentifier::RESOURCE),
Expand All @@ -84,9 +61,4 @@ public function asNonNullable(): self|UnionType

return $this;
}

public function __toString(): string
{
return $this->typeIdentifier->value;
}
}
52 changes: 45 additions & 7 deletions src/Symfony/Component/TypeInfo/Type/CollectionType.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use Symfony\Component\TypeInfo\Exception\InvalidArgumentException;
use Symfony\Component\TypeInfo\Type;
use Symfony\Component\TypeInfo\TypeIdentifier;
use Symfony\Component\TypeInfo\TypeIdentifierInterface;

/**
* Represents a key/value collection type.
Expand All @@ -27,15 +28,52 @@
*
* @experimental
*/
final class CollectionType extends Type
final class CollectionType extends AtomicType
{
/**
* @var array<string>
*/
private readonly array $classNames;

/**
* @param T $type
*/
public function __construct(
private readonly BuiltinType|ObjectType|GenericType $type,
private readonly TypeIdentifier|string $type,
private readonly bool $isList = false,
private readonly bool $allowEmpty = false,
Type ...$variableTypes,
) {
if ('' === $type || $type instanceof TypeIdentifier && !\in_array($type, [TypeIdentifier::ARRAY, TypeIdentifier::ITERABLE], true)) {
throw new InvalidArgumentException(\sprintf('Invalid collection type. Expected array,iterable or class-string, got "%s".', $type->value));
}

parent::__construct(
$type instanceof TypeIdentifier ? $type : TypeIdentifier::OBJECT,
$type instanceof TypeIdentifier ? $type->toString() : $type,
false,
false,
...$variableTypes,
);

if (!$isList && 2 <= count($variableTypes)) {
$keyType = $variableTypes[0];
if (!$keyType instanceof UnionType) {
return $keyType instanceof AtomicType && \in_array($keyType->getTypeIdentifier(), [TypeIdentifier::INT, TypeIdentifier::STRING], true);
}
$typeIdentifiers = array_unique(array_map(fn(Type $type): TypeIdentifier => $type->getTypeIdentifier(), $keyType->getTypes()));

$isValid =
($keyType instanceof AtomicType && \in_array($keyType->getTypeIdentifier(), [TypeIdentifier::INT, TypeIdentifier::STRING], true)) ||
($keyType instanceof UnionType && 2 === count($keyType->getTypes()) && 'int|string' === (string) $keyType)
;
if (!$isValid) {
throw new InvalidArgumentException(\sprintf('"%s" is not a valid list key type.', (string) $keyType));
}
}



if ($this->isList()) {
$keyType = $this->getCollectionKeyType();

Expand Down Expand Up @@ -103,11 +141,6 @@ public function getCollectionValueType(): Type
return $defaultCollectionValueType;
}

public function __toString(): string
{
return (string) $this->type;
}

/**
* Proxies all method calls to the original type.
*
Expand All @@ -117,4 +150,9 @@ public function __call(string $method, array $arguments): mixed
{
return $this->type->{$method}(...$arguments);
}

private function validateKeyType(Type $type): void
{

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,9 @@
* @author Mathias Arlaud <[email protected]>
* @author Baptiste Leduc <[email protected]>
*
* @internal
*
* @template T of Type
*/
trait CompositeTypeTrait
class CompositeType extends Type
{
/**
* @var list<T>
Expand Down
4 changes: 2 additions & 2 deletions src/Symfony/Component/TypeInfo/Type/IntersectionType.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@
final class IntersectionType extends Type
{
/**
* @use CompositeTypeTrait<T>
* @use CompositeType<T>
*/
use CompositeTypeTrait;
use CompositeType;

public function is(callable $callable): bool
{
Expand Down
Loading

0 comments on commit 07dce5d

Please sign in to comment.