Skip to content

Commit 6097bef

Browse files
committed
Add constants and const mask support
1 parent cced8a2 commit 6097bef

15 files changed

+535
-63
lines changed

src/Exception/Runtime/InvalidIterableKeyException.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,14 @@
88
use TypeLang\Mapper\Context\RuntimeContext;
99

1010
/**
11-
* @template TValue of iterable = iterable<mixed, mixed>
11+
* @template TValue of mixed = mixed
1212
*
1313
* @template-extends IterableKeyException<TValue>
1414
*/
1515
class InvalidIterableKeyException extends IterableKeyException
1616
{
1717
/**
18-
* @template TArgValue of iterable
18+
* @template TArgValue of mixed
1919
*
2020
* @param int<0, max> $index
2121
* @param TArgValue $value
@@ -25,11 +25,11 @@ class InvalidIterableKeyException extends IterableKeyException
2525
public static function createFromPath(
2626
int $index,
2727
mixed $key,
28-
iterable $value,
28+
mixed $value,
2929
PathInterface $path,
3030
?\Throwable $previous = null,
3131
): self {
32-
$template = 'The key {{key}} on index {{index}} in {{value}} is invalid';
32+
$template = 'The key {{key}} in {{value}} is invalid';
3333

3434
/** @var self<TArgValue> */
3535
return new self(

src/Exception/Runtime/IterableException.php

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,15 @@
77
use TypeLang\Mapper\Context\Path\PathInterface;
88

99
/**
10-
* @template TValue of iterable = iterable<mixed, mixed>
10+
* @template TValue of mixed = mixed
1111
*
1212
* @template-extends ValueException<TValue>
1313
*/
1414
abstract class IterableException extends ValueException implements
1515
NotInterceptableExceptionInterface
1616
{
17-
/**
18-
* @param TValue $value unlike {@see ValueException::$value}, this exception
19-
* value can only be {@see iterable}
20-
*/
2117
public function __construct(
22-
iterable $value,
18+
mixed $value,
2319
PathInterface $path,
2420
string $template,
2521
int $code = 0,

src/Exception/Runtime/IterableKeyException.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
use TypeLang\Mapper\Context\Path\PathInterface;
88

99
/**
10-
* @template TValue of iterable = iterable<mixed, mixed>
10+
* @template TValue of mixed = mixed
1111
*
1212
* @template-extends IterableException<TValue>
1313
*/
@@ -30,7 +30,7 @@ public function __construct(
3030
* be compatible with PHP array keys ({@see int} or {@see string}).
3131
*/
3232
public readonly mixed $key,
33-
iterable $value,
33+
mixed $value,
3434
PathInterface $path,
3535
string $template,
3636
int $code = 0,

src/Mapping/Reference/ReferencesResolver.php

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ private function resolveFromCurrentNamespace(TypeStatement $statement, \Reflecti
5454
return $this->typeResolver->resolve(
5555
type: $statement,
5656
transform: static function (Name $name) use ($class): ?Name {
57+
if ($name->isFullQualified() || $name->isBuiltin()) {
58+
return $name;
59+
}
60+
5761
$namespace = $class->getNamespaceName();
5862

5963
// Replace "namespace\ClassName" sequences to current
@@ -72,19 +76,13 @@ private function resolveFromCurrentNamespace(TypeStatement $statement, \Reflecti
7276
}
7377
}
7478

75-
if ($namespace !== '' && self::entryExists($namespace . '\\' . $name->toString())) {
76-
return (new Name($namespace))
77-
->withAdded($name);
79+
if ($namespace === '') {
80+
return null;
7881
}
7982

80-
return null;
83+
return (new Name($namespace))
84+
->withAdded($name);
8185
},
8286
);
8387
}
84-
85-
private static function entryExists(string $fqn): bool
86-
{
87-
return \class_exists($fqn)
88-
|| \interface_exists($fqn, false);
89-
}
9088
}

src/Platform/StandardPlatform.php

Lines changed: 28 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,32 @@ public function getTypes(): iterable
4242
// Adds support for the "string" type
4343
yield $string = new Builder\SimpleTypeBuilder('string', Type\StringType::class);
4444
yield new Builder\TypeAliasBuilder(\Stringable::class, $string);
45+
yield new Builder\TypeAliasBuilder('non-empty-string', $string, Reason::Temporary);
46+
yield new Builder\TypeAliasBuilder('lowercase-string', $string, Reason::Temporary);
47+
yield new Builder\TypeAliasBuilder('non-empty-lowercase-string', $string, Reason::Temporary);
48+
yield new Builder\TypeAliasBuilder('uppercase-string', $string, Reason::Temporary);
49+
yield new Builder\TypeAliasBuilder('non-empty-uppercase-string', $string, Reason::Temporary);
50+
yield new Builder\TypeAliasBuilder('numeric-string', $string, Reason::Temporary);
51+
yield new Builder\TypeAliasBuilder('literal-string', $string, Reason::Temporary);
52+
yield new Builder\TypeAliasBuilder('non-empty-literal-string', $string, Reason::Temporary);
53+
yield new Builder\TypeAliasBuilder('class-string', $string, Reason::Temporary);
54+
yield new Builder\TypeAliasBuilder('interface-string', $string, Reason::Temporary);
55+
yield new Builder\TypeAliasBuilder('trait-string', $string, Reason::Temporary);
56+
yield new Builder\TypeAliasBuilder('enum-string', $string, Reason::Temporary);
57+
yield new Builder\TypeAliasBuilder('callable-string', $string, Reason::Temporary);
58+
yield new Builder\TypeAliasBuilder('truthy-string', $string, Reason::Temporary);
59+
yield new Builder\TypeAliasBuilder('non-falsy-string', $string, Reason::Temporary);
4560

4661
// Adds support for the "int" type
4762
yield $int = new Builder\IntRangeTypeBuilder('int');
4863
yield new Builder\TypeAliasBuilder('integer', $int, Reason::NonCanonical);
64+
yield new Builder\TypeAliasBuilder('positive-int', $int, Reason::Temporary);
65+
yield new Builder\TypeAliasBuilder('non-positive-int', $int, Reason::Temporary);
66+
yield new Builder\TypeAliasBuilder('negative-int', $int, Reason::Temporary);
67+
yield new Builder\TypeAliasBuilder('non-negative-int', $int, Reason::Temporary);
68+
yield new Builder\TypeAliasBuilder('non-zero-int', $int, Reason::Temporary);
69+
yield new Builder\TypeAliasBuilder('number', $int, Reason::Temporary);
70+
yield new Builder\TypeAliasBuilder('numeric', $int, Reason::Temporary);
4971

5072
// Adds support for the "float" type
5173
yield $float = new Builder\SimpleTypeBuilder('float', Type\FloatType::class);
@@ -70,19 +92,11 @@ public function getTypes(): iterable
7092
// Adds support for the "?T" statement
7193
yield new Builder\NullableTypeBuilder();
7294

73-
// Adds support for the "null" literal and/or named type statement
95+
// Adds support for the literal types
7496
yield new Builder\NullTypeBuilder();
75-
76-
// Adds support for the "true" and "false" literals
7797
yield new Builder\BoolLiteralTypeBuilder();
78-
79-
// Adds support for the integer literal types
8098
yield new Builder\IntLiteralTypeBuilder();
81-
82-
// Adds support for the float literal types
8399
yield new Builder\FloatLiteralTypeBuilder();
84-
85-
// Adds support for the string literal types
86100
yield new Builder\StringLiteralTypeBuilder();
87101

88102
// Adds support for the "T[]" statement
@@ -91,33 +105,12 @@ public function getTypes(): iterable
91105
// Adds support for the "T|U" union types
92106
yield new Builder\UnionTypeBuilder();
93107

94-
// Temporary aliases
95-
yield new Builder\TypeAliasBuilder('non-empty-string', $string, Reason::Temporary);
96-
yield new Builder\TypeAliasBuilder('lowercase-string', $string, Reason::Temporary);
97-
yield new Builder\TypeAliasBuilder('non-empty-lowercase-string', $string, Reason::Temporary);
98-
yield new Builder\TypeAliasBuilder('uppercase-string', $string, Reason::Temporary);
99-
yield new Builder\TypeAliasBuilder('non-empty-uppercase-string', $string, Reason::Temporary);
100-
yield new Builder\TypeAliasBuilder('numeric-string', $string, Reason::Temporary);
101-
yield new Builder\TypeAliasBuilder('literal-string', $string, Reason::Temporary);
102-
yield new Builder\TypeAliasBuilder('non-empty-literal-string', $string, Reason::Temporary);
103-
yield new Builder\TypeAliasBuilder('class-string', $string, Reason::Temporary);
104-
yield new Builder\TypeAliasBuilder('interface-string', $string, Reason::Temporary);
105-
yield new Builder\TypeAliasBuilder('trait-string', $string, Reason::Temporary);
106-
yield new Builder\TypeAliasBuilder('enum-string', $string, Reason::Temporary);
107-
yield new Builder\TypeAliasBuilder('callable-string', $string, Reason::Temporary);
108-
yield new Builder\TypeAliasBuilder('truthy-string', $string, Reason::Temporary);
109-
yield new Builder\TypeAliasBuilder('non-falsy-string', $string, Reason::Temporary);
110-
111-
yield new Builder\TypeAliasBuilder('positive-int', $int, Reason::Temporary);
112-
yield new Builder\TypeAliasBuilder('non-positive-int', $int, Reason::Temporary);
113-
yield new Builder\TypeAliasBuilder('negative-int', $int, Reason::Temporary);
114-
yield new Builder\TypeAliasBuilder('non-negative-int', $int, Reason::Temporary);
115-
yield new Builder\TypeAliasBuilder('non-zero-int', $int, Reason::Temporary);
116-
117-
yield new Builder\TypeAliasBuilder('number', $int, Reason::Temporary);
118-
yield new Builder\TypeAliasBuilder('numeric', $int, Reason::Temporary);
108+
// Adds support for constants (and masks)
109+
yield new Builder\ConstMaskTypeBuilder();
110+
yield new Builder\ClassConstTypeBuilder();
111+
yield new Builder\ClassConstMaskTypeBuilder();
119112

120-
// Other
113+
// Other (asymmetric)
121114
yield $object = new Builder\ObjectTypeBuilder('object');
122115
yield new Builder\TypeAliasBuilder(\stdClass::class, $object);
123116
yield new Builder\UnitEnumTypeBuilder();
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace TypeLang\Mapper\Type\Builder;
6+
7+
use TypeLang\Mapper\Context\BuildingContext;
8+
use TypeLang\Mapper\Exception\Definition\InternalTypeException;
9+
use TypeLang\Mapper\Type\Builder\ConstMaskTypeBuilder\ConstFinder;
10+
use TypeLang\Mapper\Type\Builder\ConstMaskTypeBuilder\ConstFinderMode;
11+
use TypeLang\Mapper\Type\Builder\ConstMaskTypeBuilder\ConstGroupCreator;
12+
use TypeLang\Mapper\Type\UnionConstType;
13+
use TypeLang\Parser\Node\Name;
14+
use TypeLang\Parser\Node\Stmt\ClassConstMaskNode;
15+
use TypeLang\Parser\Node\Stmt\ClassConstNode;
16+
use TypeLang\Parser\Node\Stmt\TypeStatement;
17+
18+
/**
19+
* @template-implements TypeBuilderInterface<ClassConstMaskNode, UnionConstType<mixed, mixed>>
20+
*/
21+
final class ClassConstMaskTypeBuilder implements TypeBuilderInterface
22+
{
23+
private readonly ConstFinder $finder;
24+
private readonly ConstGroupCreator $groups;
25+
26+
public function __construct()
27+
{
28+
$this->finder = new ConstFinder();
29+
$this->groups = new ConstGroupCreator();
30+
}
31+
32+
public function isSupported(TypeStatement $stmt): bool
33+
{
34+
return $stmt instanceof ClassConstMaskNode
35+
&& !$stmt instanceof ClassConstNode;
36+
}
37+
38+
public function build(TypeStatement $stmt, BuildingContext $context): UnionConstType
39+
{
40+
/** @phpstan-ignore-next-line : Additional DbC assertion */
41+
assert($stmt instanceof ClassConstMaskNode);
42+
43+
return new UnionConstType(
44+
groups: $this->groups->create(
45+
values: $this->getConstants($stmt),
46+
context: $context,
47+
),
48+
);
49+
}
50+
51+
/**
52+
* @return list<mixed>
53+
*/
54+
private function getConstants(ClassConstMaskNode $stmt): array
55+
{
56+
$constants = $this->getAllConstants($stmt);
57+
58+
if ($stmt->constant === null) {
59+
return \array_values($constants);
60+
}
61+
62+
return $this->finder->find(
63+
constants: $constants,
64+
mask: new Name($stmt->constant),
65+
mode: ConstFinderMode::Prefix,
66+
);
67+
}
68+
69+
/**
70+
* @return array<string, mixed>
71+
*/
72+
private function getAllConstants(ClassConstMaskNode $stmt): array
73+
{
74+
/** @var class-string $class */
75+
$class = $stmt->class->toString();
76+
77+
try {
78+
return (new \ReflectionClass($class))
79+
->getConstants();
80+
} catch (\ReflectionException $e) {
81+
throw InternalTypeException::becauseInternalTypeErrorOccurs(
82+
type: $stmt,
83+
message: 'The type of a class constant {{type}} cannot be determined',
84+
previous: $e,
85+
);
86+
}
87+
}
88+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace TypeLang\Mapper\Type\Builder;
6+
7+
use TypeLang\Mapper\Context\BuildingContext;
8+
use TypeLang\Mapper\Exception\Definition\InternalTypeException;
9+
use TypeLang\Mapper\Type\ClassConstType;
10+
use TypeLang\Parser\Node\Stmt\ClassConstNode;
11+
use TypeLang\Parser\Node\Stmt\TypeStatement;
12+
13+
/**
14+
* @template-implements TypeBuilderInterface<ClassConstNode, ClassConstType>
15+
*/
16+
final class ClassConstTypeBuilder implements TypeBuilderInterface
17+
{
18+
public function isSupported(TypeStatement $stmt): bool
19+
{
20+
return $stmt instanceof ClassConstNode;
21+
}
22+
23+
public function build(TypeStatement $stmt, BuildingContext $context): ClassConstType
24+
{
25+
/** @phpstan-ignore-next-line : Additional DbC assertion */
26+
assert($stmt instanceof ClassConstNode);
27+
28+
/** @var class-string $class */
29+
$class = $stmt->class->toString();
30+
/** @phpstan-ignore-next-line : Constant name is always present */
31+
$constant = $stmt->constant->toString();
32+
33+
try {
34+
$reflection = new \ReflectionClassConstant($class, $constant);
35+
} catch (\Throwable $e) {
36+
throw InternalTypeException::becauseInternalTypeErrorOccurs(
37+
type: $stmt,
38+
message: 'The type of a class constant {{type}} cannot be determined',
39+
previous: $e,
40+
);
41+
}
42+
43+
return new ClassConstType(
44+
value: $reflection->getValue(),
45+
type: $context->getTypeByValue($reflection->getValue()),
46+
);
47+
}
48+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace TypeLang\Mapper\Type\Builder;
6+
7+
use TypeLang\Mapper\Context\BuildingContext;
8+
use TypeLang\Mapper\Type\Builder\ConstMaskTypeBuilder\ConstFinder;
9+
use TypeLang\Mapper\Type\Builder\ConstMaskTypeBuilder\ConstFinderMode;
10+
use TypeLang\Mapper\Type\Builder\ConstMaskTypeBuilder\ConstGroupCreator;
11+
use TypeLang\Mapper\Type\UnionConstType;
12+
use TypeLang\Parser\Node\Stmt\ConstMaskNode;
13+
use TypeLang\Parser\Node\Stmt\TypeStatement;
14+
15+
/**
16+
* @template-implements TypeBuilderInterface<ConstMaskNode, UnionConstType<mixed, mixed>>
17+
*/
18+
final class ConstMaskTypeBuilder implements TypeBuilderInterface
19+
{
20+
private readonly ConstFinder $finder;
21+
private readonly ConstGroupCreator $groups;
22+
23+
public function __construct()
24+
{
25+
$this->finder = new ConstFinder();
26+
$this->groups = new ConstGroupCreator();
27+
}
28+
29+
public function isSupported(TypeStatement $stmt): bool
30+
{
31+
return $stmt instanceof ConstMaskNode;
32+
}
33+
34+
public function build(TypeStatement $stmt, BuildingContext $context): UnionConstType
35+
{
36+
/** @phpstan-ignore-next-line : Additional DbC assertion */
37+
assert($stmt instanceof ConstMaskNode);
38+
39+
return new UnionConstType(
40+
groups: $this->groups->create(
41+
values: $this->getConstants($stmt),
42+
context: $context,
43+
),
44+
);
45+
}
46+
47+
/**
48+
* @return list<mixed>
49+
*/
50+
private function getConstants(ConstMaskNode $stmt): array
51+
{
52+
return $this->finder->find(
53+
constants: \get_defined_constants(),
54+
mask: $stmt->name,
55+
mode: ConstFinderMode::Prefix,
56+
);
57+
}
58+
}

0 commit comments

Comments
 (0)