Skip to content

Commit b31bad5

Browse files
author
Kirill Nesmeyanov
committed
Add list type support
1 parent b5c8079 commit b31bad5

File tree

13 files changed

+707
-261
lines changed

13 files changed

+707
-261
lines changed

src/Platform/StandardPlatform.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,11 @@ public function getTypes(): iterable
5757
\IteratorAggregate::class,
5858
], 'array-key', 'mixed');
5959

60+
// Adds support for the "list" type
61+
yield new Builder\ListTypeBuilder(['list'], 'mixed');
62+
6063
// Adds support for the "object" type
61-
yield new Builder\ObjectTypeBuilder([
62-
'object',
63-
\stdClass::class,
64-
]);
64+
yield new Builder\ObjectTypeBuilder(['object', \stdClass::class]);
6565

6666
// Adds support for the "?T" statement
6767
yield new Builder\NullableTypeBuilder();

src/Runtime/Path/Entry/ArrayIndexEntry.php

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,15 @@
66

77
final class ArrayIndexEntry extends Entry
88
{
9-
/**
10-
* @param array-key $index
11-
*/
129
public function __construct(
13-
public readonly int|string $index,
10+
public readonly mixed $index,
1411
) {
15-
$key = (string) $this->index;
12+
if (\is_scalar($this->index)) {
13+
$key = (string) $this->index;
14+
} else {
15+
$key = \get_debug_type($this->index);
16+
}
17+
1618
$key = $key === '' ? '0' : $key;
1719

1820
parent::__construct($key);
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace TypeLang\Mapper\Type\Builder;
6+
7+
use TypeLang\Mapper\Exception\Definition\Shape\ShapeFieldsNotSupportedException;
8+
use TypeLang\Mapper\Exception\Definition\Template\Hint\TemplateArgumentHintsNotSupportedException;
9+
use TypeLang\Mapper\Exception\Definition\Template\TooManyTemplateArgumentsException;
10+
use TypeLang\Mapper\Exception\Definition\TypeNotFoundException;
11+
use TypeLang\Mapper\Runtime\Parser\TypeParserInterface;
12+
use TypeLang\Mapper\Runtime\Repository\TypeRepositoryInterface;
13+
use TypeLang\Mapper\Type\ListType;
14+
use TypeLang\Parser\Node\Stmt\NamedTypeNode;
15+
use TypeLang\Parser\Node\Stmt\Template\TemplateArgumentNode;
16+
use TypeLang\Parser\Node\Stmt\TypeStatement;
17+
18+
/**
19+
* @template-extends NamedTypeBuilder<ListType>
20+
*/
21+
class ListTypeBuilder extends NamedTypeBuilder
22+
{
23+
/**
24+
* @var non-empty-lowercase-string
25+
*/
26+
public const DEFAULT_INNER_VALUE_TYPE = 'mixed';
27+
28+
/**
29+
* @param non-empty-array<non-empty-string>|non-empty-string $names
30+
* @param non-empty-string $valueType
31+
*/
32+
public function __construct(
33+
array|string $names,
34+
protected readonly string $valueType = self::DEFAULT_INNER_VALUE_TYPE,
35+
) {
36+
parent::__construct($names);
37+
}
38+
39+
/**
40+
* @throws ShapeFieldsNotSupportedException
41+
* @throws TemplateArgumentHintsNotSupportedException
42+
* @throws TooManyTemplateArgumentsException
43+
* @throws TypeNotFoundException
44+
* @throws \Throwable
45+
*/
46+
public function build(
47+
TypeStatement $statement,
48+
TypeRepositoryInterface $types,
49+
TypeParserInterface $parser,
50+
): ListType {
51+
$this->expectNoShapeFields($statement);
52+
53+
$arguments = $statement->arguments->items ?? [];
54+
55+
return match (\count($arguments)) {
56+
0 => $this->buildWithNoValue($types, $parser),
57+
1 => $this->buildWithValue($statement, $types),
58+
default => throw TooManyTemplateArgumentsException::becauseTemplateArgumentsRangeOverflows(
59+
passedArgumentsCount: \count($arguments),
60+
minSupportedArgumentsCount: 0,
61+
maxSupportedArgumentsCount: 1,
62+
type: $statement,
63+
),
64+
};
65+
}
66+
67+
/**
68+
* @throws TypeNotFoundException
69+
* @throws \Throwable
70+
*/
71+
private function buildWithNoValue(TypeRepositoryInterface $types, TypeParserInterface $parser): ListType
72+
{
73+
return new ListType(
74+
value: $types->getTypeByStatement(
75+
statement: $parser->getStatementByDefinition(
76+
definition: $this->valueType,
77+
),
78+
),
79+
);
80+
}
81+
82+
/**
83+
* @throws TemplateArgumentHintsNotSupportedException
84+
* @throws TypeNotFoundException
85+
* @throws \Throwable
86+
*/
87+
private function buildWithValue(
88+
NamedTypeNode $statement,
89+
TypeRepositoryInterface $types,
90+
): ListType {
91+
$arguments = $statement->arguments->items ?? [];
92+
93+
assert(\array_key_exists(0, $arguments));
94+
95+
/** @var TemplateArgumentNode $value */
96+
$value = $arguments[0];
97+
98+
$this->expectNoTemplateArgumentHint($statement, $value);
99+
100+
return new ListType(
101+
value: $types->getTypeByStatement(
102+
statement: $value->value,
103+
),
104+
);
105+
}
106+
}

src/Type/ListType.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace TypeLang\Mapper\Type;
6+
7+
use TypeLang\Mapper\Type\ListType\ListTypeDenormalizer;
8+
use TypeLang\Mapper\Type\ListType\ListTypeNormalizer;
9+
10+
/**
11+
* @template-extends AsymmetricType<ListTypeNormalizer, ListTypeDenormalizer>
12+
*/
13+
class ListType extends AsymmetricType
14+
{
15+
public function __construct(
16+
TypeInterface $value = new MixedType(),
17+
) {
18+
parent::__construct(
19+
normalizer: new ListTypeNormalizer($value),
20+
denormalizer: new ListTypeDenormalizer($value),
21+
);
22+
}
23+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace TypeLang\Mapper\Type\ListType;
6+
7+
use TypeLang\Mapper\Exception\Mapping\InvalidValueException;
8+
use TypeLang\Mapper\Exception\Mapping\RuntimeExceptionInterface;
9+
use TypeLang\Mapper\Runtime\Context;
10+
use TypeLang\Mapper\Runtime\Path\Entry\ArrayIndexEntry;
11+
use TypeLang\Mapper\Type\MixedType;
12+
use TypeLang\Mapper\Type\TypeInterface;
13+
14+
class ListTypeDenormalizer implements TypeInterface
15+
{
16+
public function __construct(
17+
protected readonly TypeInterface $value = new MixedType(),
18+
) {}
19+
20+
public function match(mixed $value, Context $context): bool
21+
{
22+
return \is_array($value) && \array_is_list($value);
23+
}
24+
25+
/**
26+
* @return list<mixed>
27+
* @throws InvalidValueException
28+
* @throws \Throwable
29+
* @throws RuntimeExceptionInterface
30+
*/
31+
public function cast(mixed $value, Context $context): array
32+
{
33+
if (!\is_array($value) || !\array_is_list($value)) {
34+
throw InvalidValueException::createFromContext(
35+
value: $value,
36+
context: $context,
37+
);
38+
}
39+
40+
$result = [];
41+
42+
foreach ($value as $index => $item) {
43+
$entrance = $context->enter($item, new ArrayIndexEntry($index));
44+
45+
$result[] = $this->value->cast($item, $entrance);
46+
}
47+
48+
return $result;
49+
}
50+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace TypeLang\Mapper\Type\ListType;
6+
7+
use TypeLang\Mapper\Exception\Mapping\InvalidValueException;
8+
use TypeLang\Mapper\Exception\Mapping\RuntimeExceptionInterface;
9+
use TypeLang\Mapper\Runtime\Context;
10+
use TypeLang\Mapper\Runtime\Path\Entry\ArrayIndexEntry;
11+
use TypeLang\Mapper\Type\MixedType;
12+
use TypeLang\Mapper\Type\TypeInterface;
13+
14+
class ListTypeNormalizer implements TypeInterface
15+
{
16+
public function __construct(
17+
protected readonly TypeInterface $value = new MixedType(),
18+
) {}
19+
20+
public function match(mixed $value, Context $context): bool
21+
{
22+
return \is_iterable($value);
23+
}
24+
25+
/**
26+
* @return list<mixed>
27+
* @throws InvalidValueException
28+
* @throws \Throwable
29+
* @throws RuntimeExceptionInterface
30+
*/
31+
public function cast(mixed $value, Context $context): array
32+
{
33+
if (!\is_iterable($value)) {
34+
throw InvalidValueException::createFromContext(
35+
value: $value,
36+
context: $context,
37+
);
38+
}
39+
40+
$result = [];
41+
42+
foreach ($value as $index => $item) {
43+
$valueEntrance = $context->enter($item, new ArrayIndexEntry($index));
44+
45+
$result[] = $this->value->cast($item, $valueEntrance);
46+
}
47+
48+
return $result;
49+
}
50+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
Feature: Checking for presence of type definitions in the Phan Platform
2+
3+
Background:
4+
Given platform "TypeLang\Mapper\Platform\StandardPlatform"
5+
6+
Scenario Outline: Presence of "<type>" type
7+
Given type statement "<type>"
8+
Then the type must be defined
9+
Examples:
10+
| type | reason |
11+
| list | Phan Compat (5.x) |
12+
| integer | Phan Compat (5.x) |
13+
| string | Phan Compat (5.x) |
14+
| NULL | Phan Compat (5.x) |
15+
| double | Phan Compat (5.x) |
16+
| object | Phan Compat (5.x) |
17+
| boolean | Phan Compat (5.x) |
18+
| array | Phan Compat (5.x) |
19+
| iterable | Phan Compat (5.x) |
20+
| array-key | Phan Compat (5.x) |
21+
| bool | Phan Compat (5.x) |
22+
| false | Phan Compat (5.x) |
23+
| float | Phan Compat (5.x) |
24+
| int | Phan Compat (5.x) |
25+
| mixed | Phan Compat (5.x) |
26+
| null | Phan Compat (5.x) |
27+
| true | Phan Compat (5.x) |
28+
# TODO | class-string | Phan Compat (5.x) |
29+
# TODO | associative-array | Phan Compat (5.x) |
30+
# TODO | non-empty-associative-array | Phan Compat (5.x) |
31+
# TODO | non-empty-array | Phan Compat (5.x) |
32+
# TODO | non-empty-list | Phan Compat (5.x) |
33+
# TODO | non-empty-string | Phan Compat (5.x) |
34+
# TODO | non-empty-lowercase-string | Phan Compat (5.x) |
35+
# TODO | non-zero-int | Phan Compat (5.x) |
36+
# TODO | resource | Phan Compat (5.x) |
37+
# TODO | callable | Phan Compat (5.x) |
38+
# TODO | callable-array | Phan Compat (5.x) |
39+
# TODO | callable-object | Phan Compat (5.x) |
40+
# TODO | callable-string | Phan Compat (5.x) |
41+
# TODO | closure | Phan Compat (5.x) |
42+
# TODO | phan-intersection-type | Phan Compat (5.x) |
43+
# TODO | non-empty-mixed | Phan Compat (5.x) |
44+
# TODO | non-null-mixed | Phan Compat (5.x) |
45+
# TODO | scalar | Phan Compat (5.x) |
46+
# TODO | lowercase-string | Phan Compat (5.x) |
47+
# TODO | numeric-string | Phan Compat (5.x) |
48+
# TODO | void | Phan Compat (5.x) |
49+
# TODO | never | Phan Compat (5.x) |
50+
# TODO | no-return | Phan Compat (5.x) |
51+
# TODO | never-return | Phan Compat (5.x) |
52+
# TODO | never-returns | Phan Compat (5.x) |
53+
# TODO | static | Phan Compat (5.x) |
54+
# TODO | $this | Phan Compat (5.x) |
55+

0 commit comments

Comments
 (0)