Skip to content

Commit 3df5e99

Browse files
committed
Improve iterable and array types
1 parent 1b3be85 commit 3df5e99

12 files changed

Lines changed: 292 additions & 203 deletions

src/Exception/Runtime/InvalidIterableValueException.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ public static function createFromPath(
5151

5252
/**
5353
* @param int<0, max> $index
54+
*
5455
* @return self<iterable<array-key, mixed>>
5556
*/
5657
public static function createFromContext(

src/Exception/Runtime/InvalidObjectValueException.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ public static function createFromPath(
4646

4747
/**
4848
* @param non-empty-string $field
49+
*
4950
* @return self<array<array-key, mixed>|object>
5051
*/
5152
public static function createFromContext(

src/Exception/Runtime/MissingRequiredObjectFieldException.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ public static function createFromPath(
4444

4545
/**
4646
* @param non-empty-string $field
47+
*
4748
* @return self<array<array-key, mixed>|object>
4849
*/
4950
public static function createFromContext(

src/Platform/StandardPlatform.php

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,12 @@ public function getTypes(Direction $direction): iterable
5858

5959
// Adds support for the "array" type
6060
yield $array = new Builder\ArrayTypeBuilder('array', 'array-key', 'mixed');
61-
yield new Builder\TypeAliasBuilder('iterable', $array);
62-
yield new Builder\TypeAliasBuilder(\Iterator::class, $array);
63-
yield new Builder\TypeAliasBuilder(\Generator::class, $array);
64-
yield new Builder\TypeAliasBuilder(\Traversable::class, $array);
65-
yield new Builder\TypeAliasBuilder(\IteratorAggregate::class, $array);
61+
// Adds support for the "iterable" type
62+
yield $iterable = new Builder\IterableToArrayTypeBuilder('iterable', 'array-key', 'mixed');
63+
yield new Builder\TypeAliasBuilder(\Iterator::class, $iterable);
64+
yield new Builder\TypeAliasBuilder(\Generator::class, $iterable);
65+
yield new Builder\TypeAliasBuilder(\Traversable::class, $iterable);
66+
yield new Builder\TypeAliasBuilder(\IteratorAggregate::class, $iterable);
6667

6768
// Adds support for the "iterable<T> -> list<T>" type
6869
yield new Builder\ListTypeBuilder('list', 'mixed');

src/Type/ArrayType.php

Lines changed: 6 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -5,86 +5,31 @@
55
namespace TypeLang\Mapper\Type;
66

77
use TypeLang\Mapper\Context\Context;
8-
use TypeLang\Mapper\Context\Path\Entry\ArrayIndexEntry;
9-
use TypeLang\Mapper\Exception\Runtime\InvalidIterableKeyException;
10-
use TypeLang\Mapper\Exception\Runtime\InvalidIterableValueException;
118
use TypeLang\Mapper\Exception\Runtime\InvalidValueException;
129

1310
/**
1411
* @template TKey of array-key = array-key
1512
* @template TValue of mixed = mixed
16-
* @template-implements TypeInterface<array<TKey, TValue>>
13+
* @template-extends IterableToArrayType<TKey, TValue>
1714
*/
18-
class ArrayType implements TypeInterface
15+
class ArrayType extends IterableToArrayType
1916
{
20-
public function __construct(
21-
/**
22-
* @var TypeInterface<TKey>
23-
*/
24-
protected readonly TypeInterface $key = new ArrayKeyType(),
25-
/**
26-
* @var TypeInterface<TValue>
27-
*/
28-
protected readonly TypeInterface $value = new MixedType(),
29-
) {}
30-
3117
/**
32-
* @phpstan-assert-if-true iterable<mixed, mixed>|array<array-key, mixed> $value
18+
* @phpstan-assert-if-true array<array-key, mixed> $value
3319
*/
20+
#[\Override]
3421
public function match(mixed $value, Context $context): bool
3522
{
3623
return \is_array($value);
3724
}
3825

26+
#[\Override]
3927
public function cast(mixed $value, Context $context): array
4028
{
41-
if (!$this->match($value, $context)) {
29+
if (!\is_array($value)) {
4230
throw InvalidValueException::createFromContext($context);
4331
}
4432

4533
return $this->process($value, $context);
4634
}
47-
48-
/**
49-
* @param iterable<mixed, mixed> $value
50-
*
51-
* @return array<TKey, TValue>
52-
* @throws \Throwable
53-
*/
54-
protected function process(iterable $value, Context $context): array
55-
{
56-
$result = [];
57-
$index = 0;
58-
59-
foreach ($value as $key => $item) {
60-
try {
61-
$key = $this->key->cast($key, $context);
62-
} catch (InvalidValueException $e) {
63-
throw InvalidIterableKeyException::createFromContext(
64-
index: $index,
65-
key: $key,
66-
context: $context,
67-
previous: $e,
68-
);
69-
}
70-
71-
$entrance = $context->enter($item, new ArrayIndexEntry($key));
72-
73-
try {
74-
$result[$key] = $this->value->cast($item, $entrance);
75-
} catch (InvalidValueException $e) {
76-
throw InvalidIterableValueException::createFromContext(
77-
element: $item,
78-
index: $index,
79-
key: $key,
80-
context: $entrance,
81-
previous: $e,
82-
);
83-
}
84-
85-
++$index;
86-
}
87-
88-
return $result;
89-
}
9035
}

src/Type/Builder/ArrayTypeBuilder.php

Lines changed: 5 additions & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -4,147 +4,18 @@
44

55
namespace TypeLang\Mapper\Type\Builder;
66

7-
use TypeLang\Mapper\Exception\Definition\Template\Hint\TemplateArgumentHintsNotSupportedException;
8-
use TypeLang\Mapper\Exception\Definition\Template\TooManyTemplateArgumentsException;
9-
use TypeLang\Mapper\Exception\Definition\TypeNotFoundException;
107
use TypeLang\Mapper\Type\ArrayType;
11-
use TypeLang\Mapper\Type\Parser\TypeParserInterface;
12-
use TypeLang\Mapper\Type\Repository\TypeRepositoryInterface;
13-
use TypeLang\Parser\Node\Stmt\NamedTypeNode;
14-
use TypeLang\Parser\Node\Stmt\Template\TemplateArgumentNode;
15-
use TypeLang\Parser\Node\Stmt\TypeStatement;
8+
use TypeLang\Mapper\Type\TypeInterface;
169

1710
/**
1811
* @template TKey of array-key = array-key
1912
* @template TValue of mixed = mixed
20-
* @template-extends NamedTypeBuilder<ArrayType<TKey, TValue>>
13+
* @template-extends MapTypeBuilder<TKey, TValue>
2114
*/
22-
class ArrayTypeBuilder extends NamedTypeBuilder
15+
class ArrayTypeBuilder extends MapTypeBuilder
2316
{
24-
/**
25-
* @var non-empty-lowercase-string
26-
*/
27-
public const DEFAULT_INNER_KEY_TYPE = 'array-key';
28-
29-
/**
30-
* @var non-empty-lowercase-string
31-
*/
32-
public const DEFAULT_INNER_VALUE_TYPE = 'mixed';
33-
34-
/**
35-
* @param non-empty-array<non-empty-string>|non-empty-string $names
36-
* @param non-empty-string $keyType
37-
* @param non-empty-string $valueType
38-
*/
39-
public function __construct(
40-
array|string $names,
41-
protected readonly string $keyType = self::DEFAULT_INNER_KEY_TYPE,
42-
protected readonly string $valueType = self::DEFAULT_INNER_VALUE_TYPE,
43-
) {
44-
parent::__construct($names);
45-
}
46-
47-
public function build(
48-
TypeStatement $statement,
49-
TypeRepositoryInterface $types,
50-
TypeParserInterface $parser,
51-
): ArrayType {
52-
/** @phpstan-ignore-next-line : Additional DbC assertion */
53-
assert($statement instanceof NamedTypeNode);
54-
55-
$this->expectNoShapeFields($statement);
56-
57-
$arguments = $statement->arguments->items ?? [];
58-
59-
/** @phpstan-ignore-next-line : It's too difficult for PHPStan to calculate the specified type */
60-
return match (\count($arguments)) {
61-
0 => $this->buildWithNoKeyValue($types, $parser),
62-
1 => $this->buildWithValue($statement, $types, $parser),
63-
2 => $this->buildWithKeyValue($statement, $types),
64-
default => throw TooManyTemplateArgumentsException::becauseTemplateArgumentsRangeOverflows(
65-
minSupportedArgumentsCount: 0,
66-
maxSupportedArgumentsCount: 2,
67-
type: $statement,
68-
),
69-
};
70-
}
71-
72-
/**
73-
* @return ArrayType<TKey, TValue>
74-
* @throws TypeNotFoundException
75-
* @throws \Throwable
76-
*/
77-
private function buildWithNoKeyValue(TypeRepositoryInterface $types, TypeParserInterface $parser): ArrayType
78-
{
79-
/** @phpstan-ignore-next-line : It's too difficult for PHPStan to calculate the specified type */
80-
return new ArrayType(
81-
key: $types->getTypeByStatement(
82-
statement: $parser->getStatementByDefinition(
83-
definition: $this->keyType,
84-
),
85-
),
86-
value: $types->getTypeByStatement(
87-
statement: $parser->getStatementByDefinition(
88-
definition: $this->valueType,
89-
),
90-
),
91-
);
92-
}
93-
94-
/**
95-
* @return ArrayType<TKey, TValue>
96-
* @throws TemplateArgumentHintsNotSupportedException
97-
* @throws TypeNotFoundException
98-
* @throws \Throwable
99-
*/
100-
private function buildWithKeyValue(NamedTypeNode $statement, TypeRepositoryInterface $types): ArrayType
17+
protected function create(TypeInterface $key, TypeInterface $value): ArrayType
10118
{
102-
$arguments = $statement->arguments->items ?? [];
103-
104-
assert(\array_key_exists(0, $arguments));
105-
assert(\array_key_exists(1, $arguments));
106-
107-
/** @var TemplateArgumentNode $key */
108-
$key = $arguments[0];
109-
$this->expectNoTemplateArgumentHint($statement, $key);
110-
111-
/** @var TemplateArgumentNode $value */
112-
$value = $arguments[1];
113-
$this->expectNoTemplateArgumentHint($statement, $value);
114-
115-
/** @phpstan-ignore-next-line : It's too difficult for PHPStan to calculate the specified type */
116-
return new ArrayType(
117-
key: $types->getTypeByStatement($key->value),
118-
value: $types->getTypeByStatement($value->value),
119-
);
120-
}
121-
122-
/**
123-
* @return ArrayType<TKey, TValue>
124-
* @throws TemplateArgumentHintsNotSupportedException
125-
* @throws TypeNotFoundException
126-
* @throws \Throwable
127-
*/
128-
private function buildWithValue(
129-
NamedTypeNode $statement,
130-
TypeRepositoryInterface $types,
131-
TypeParserInterface $parser,
132-
): ArrayType {
133-
$arguments = $statement->arguments->items ?? [];
134-
135-
assert(\array_key_exists(0, $arguments));
136-
137-
/** @var TemplateArgumentNode $value */
138-
$value = $arguments[0];
139-
140-
$this->expectNoTemplateArgumentHint($statement, $value);
141-
142-
/** @phpstan-ignore-next-line : It's too difficult for PHPStan to calculate the specified type */
143-
return new ArrayType(
144-
key: $types->getTypeByStatement(
145-
statement: $parser->getStatementByDefinition($this->keyType),
146-
),
147-
value: $types->getTypeByStatement($value->value),
148-
);
19+
return new ArrayType($key, $value);
14920
}
15021
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace TypeLang\Mapper\Type\Builder;
6+
7+
use TypeLang\Mapper\Type\IterableToArrayType;
8+
use TypeLang\Mapper\Type\TypeInterface;
9+
10+
/**
11+
* @template TKey of array-key = array-key
12+
* @template TValue of mixed = mixed
13+
* @template-extends MapTypeBuilder<TKey, TValue>
14+
*/
15+
class IterableToArrayTypeBuilder extends MapTypeBuilder
16+
{
17+
protected function create(TypeInterface $key, TypeInterface $value): IterableToArrayType
18+
{
19+
return new IterableToArrayType($key, $value);
20+
}
21+
}

0 commit comments

Comments
 (0)