Skip to content

Commit ddb64aa

Browse files
author
Kirill Nesmeyanov
committed
Add attribute reader
1 parent b621559 commit ddb64aa

26 files changed

+282
-31
lines changed

src/Attribute/MapType.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\Reader\Attribute;
6+
7+
// If you write it like this, PHPStorm will screw up.
8+
// #[\Attribute(\Attribute::TARGET_ALL & ~\Attribute::TARGET_CLASS)]
9+
// I believe that someday it will begin to understand PHP code...
10+
#[\Attribute(\Attribute::TARGET_FUNCTION
11+
| \Attribute::TARGET_METHOD
12+
| \Attribute::TARGET_PROPERTY
13+
| \Attribute::TARGET_CLASS_CONSTANT
14+
| \Attribute::TARGET_PARAMETER)]
15+
class MapType
16+
{
17+
/**
18+
* @param non-empty-string $type
19+
*/
20+
public function __construct(
21+
public readonly string $type,
22+
) {}
23+
}

src/AttributeReader.php

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace TypeLang\Reader;
6+
7+
use TypeLang\Parser\Parser;
8+
use TypeLang\Parser\Node\Stmt\TypeStatement;
9+
use TypeLang\Parser\ParserInterface;
10+
use TypeLang\Reader\AttributeReader\AttributeProviderInterface;
11+
use TypeLang\Reader\AttributeReader\DefaultAttributeProvider;
12+
use TypeLang\Reader\Exception\TypeReadingException;
13+
14+
class AttributeReader implements ReaderInterface
15+
{
16+
/**
17+
* @param AttributeProviderInterface<object> $provider
18+
*/
19+
public function __construct(
20+
private readonly ParserInterface $parser = new Parser(),
21+
// @phpstan-ignore-next-line
22+
private readonly AttributeProviderInterface $provider = new DefaultAttributeProvider(),
23+
) {}
24+
25+
/**
26+
* @param \ReflectionProperty|\ReflectionParameter|\ReflectionFunctionAbstract|\ReflectionClassConstant $reflector
27+
* @throws TypeReadingException
28+
*/
29+
private function tryRead(\Reflector $reflector): ?TypeStatement
30+
{
31+
$attributes = $reflector->getAttributes($this->provider->getAttribute(), \ReflectionAttribute::IS_INSTANCEOF);
32+
33+
foreach ($attributes as $attribute) {
34+
$instance = $attribute->newInstance();
35+
36+
$definition = $this->provider->getTypeFromAttribute($instance);
37+
38+
try {
39+
$statement = $this->parser->parse($definition);
40+
} catch (\Throwable $e) {
41+
throw TypeReadingException::fromInternalError($e);
42+
}
43+
44+
return $statement;
45+
}
46+
47+
return null;
48+
}
49+
50+
public function findPropertyType(\ReflectionProperty $property): ?TypeStatement
51+
{
52+
return $this->tryRead($property);
53+
}
54+
55+
public function findConstantType(\ReflectionClassConstant $constant): ?TypeStatement
56+
{
57+
return $this->tryRead($constant);
58+
}
59+
60+
public function findFunctionType(\ReflectionFunctionAbstract $function): ?TypeStatement
61+
{
62+
return $this->tryRead($function);
63+
}
64+
65+
public function findParameterType(\ReflectionParameter $parameter): ?TypeStatement
66+
{
67+
return $this->tryRead($parameter);
68+
}
69+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace TypeLang\Reader\AttributeReader;
6+
7+
use TypeLang\Parser\Node\Stmt\TypeStatement;
8+
9+
/**
10+
* @template T of object
11+
* @template-implements AttributeProviderInterface<T>
12+
*/
13+
abstract class AttributeProvider implements AttributeProviderInterface
14+
{
15+
public function process(object $attribute, TypeStatement $statement): TypeStatement
16+
{
17+
return $statement;
18+
}
19+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace TypeLang\Reader\AttributeReader;
6+
7+
use TypeLang\Parser\Node\Stmt\TypeStatement;
8+
9+
/**
10+
* @template T of object
11+
*/
12+
interface AttributeProviderInterface
13+
{
14+
/**
15+
* @return class-string<T>
16+
*/
17+
public function getAttribute(): string;
18+
19+
/**
20+
* @param T $attribute
21+
*/
22+
public function getTypeFromAttribute(object $attribute): string;
23+
24+
/**
25+
* @param T $attribute
26+
*/
27+
public function process(object $attribute, TypeStatement $statement): TypeStatement;
28+
}
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\Reader\AttributeReader;
6+
7+
use TypeLang\Reader\Attribute\MapType;
8+
9+
/**
10+
* @template-extends AttributeProvider<MapType>
11+
*/
12+
final class DefaultAttributeProvider extends AttributeProvider
13+
{
14+
public function getAttribute(): string
15+
{
16+
return MapType::class;
17+
}
18+
19+
public function getTypeFromAttribute(object $attribute): string
20+
{
21+
return $attribute->type;
22+
}
23+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace TypeLang\Reader\Exception;
6+
7+
class TypeReadingException extends ReaderException {}

src/Exception/UnrecognizedReflectionTypeException.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
namespace TypeLang\Reader\Exception;
66

7-
class UnrecognizedReflectionTypeException extends ReaderException
7+
class UnrecognizedReflectionTypeException extends TypeReadingException
88
{
99
final public const ERROR_CODE_INVALID_TYPE = 0x01 + parent::CODE_LAST;
1010

src/ReflectionReader.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,10 +86,10 @@ public function findParameterType(\ReflectionParameter $parameter): ?TypeStateme
8686
}
8787

8888
/**
89-
* @api
90-
*
9189
* @throws ReaderExceptionInterface
9290
* @throws UnrecognizedReflectionTypeException
91+
* @api
92+
*
9393
*/
9494
public function getType(\ReflectionType $type): TypeStatement
9595
{

tests/Unit/Reader/ConstantReaderTest.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public function testSimpleType(ConstantReaderInterface $reader): void
2727
constant: new \ReflectionClassConstant(ConstantReaderStub::class, 'SINGLE'),
2828
);
2929

30-
self::assertEquals(new NamedTypeNode(
30+
self::assertSameType(new NamedTypeNode(
3131
name: new Identifier('int'),
3232
), $type);
3333
}
@@ -40,7 +40,7 @@ public function testUnionType(ConstantReaderInterface $reader): void
4040
constant: new \ReflectionClassConstant(ConstantReaderStub::class, 'UNION'),
4141
);
4242

43-
self::assertEquals(new UnionTypeNode(
43+
self::assertSameType(new UnionTypeNode(
4444
a: new NamedTypeNode('string'),
4545
b: new NamedTypeNode('int'),
4646
), $type);
@@ -54,7 +54,7 @@ public function testIntersectionType(ConstantReaderInterface $reader): void
5454
constant: new \ReflectionClassConstant(ConstantReaderStub::class, 'INTERSECTION'),
5555
);
5656

57-
self::assertEquals(new IntersectionTypeNode(
57+
self::assertSameType(new IntersectionTypeNode(
5858
a: new NamedTypeNode(new FullQualifiedName(__ConstantReaderEnum::class)),
5959
b: new NamedTypeNode(new FullQualifiedName(\BackedEnum::class)),
6060
), $type);
@@ -68,7 +68,7 @@ public function testCompositeType(ConstantReaderInterface $reader): void
6868
constant: new \ReflectionClassConstant(ConstantReaderStub::class, 'COMPOSITE'),
6969
);
7070

71-
self::assertEquals(new UnionTypeNode(
71+
self::assertSameType(new UnionTypeNode(
7272
a: new IntersectionTypeNode(
7373
a: new NamedTypeNode(new FullQualifiedName(__ConstantReaderEnum::class)),
7474
b: new NamedTypeNode(new FullQualifiedName(\BackedEnum::class)),

tests/Unit/Reader/FunctionReaderTest.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public function testSimpleType(FunctionReaderInterface $reader): void
2626
function: new \ReflectionFunction('TypeLang\Reader\Tests\Unit\Reader\Stub\get_single_type'),
2727
);
2828

29-
self::assertEquals(new NamedTypeNode(
29+
self::assertSameType(new NamedTypeNode(
3030
name: new Identifier('int'),
3131
), $type);
3232
}
@@ -40,7 +40,7 @@ public function testUnionType(FunctionReaderInterface $reader): void
4040
function: new \ReflectionFunction('TypeLang\Reader\Tests\Unit\Reader\Stub\get_union_type'),
4141
);
4242

43-
self::assertEquals(new UnionTypeNode(
43+
self::assertSameType(new UnionTypeNode(
4444
a: new NamedTypeNode('string'),
4545
b: new NamedTypeNode('int'),
4646
), $type);
@@ -55,7 +55,7 @@ public function testIntersectionType(FunctionReaderInterface $reader): void
5555
function: new \ReflectionFunction('TypeLang\Reader\Tests\Unit\Reader\Stub\get_intersection_type'),
5656
);
5757

58-
self::assertEquals(new IntersectionTypeNode(
58+
self::assertSameType(new IntersectionTypeNode(
5959
a: new NamedTypeNode(new FullQualifiedName(\ArrayAccess::class)),
6060
b: new NamedTypeNode(new FullQualifiedName(\Traversable::class)),
6161
), $type);
@@ -71,7 +71,7 @@ public function testCompositeType(FunctionReaderInterface $reader): void
7171
function: new \ReflectionFunction('TypeLang\Reader\Tests\Unit\Reader\Stub\get_composite_type'),
7272
);
7373

74-
self::assertEquals(new UnionTypeNode(
74+
self::assertSameType(new UnionTypeNode(
7575
a: new IntersectionTypeNode(
7676
a: new NamedTypeNode(new FullQualifiedName(\ArrayAccess::class)),
7777
b: new NamedTypeNode(new FullQualifiedName(\Traversable::class)),

0 commit comments

Comments
 (0)