ef8f3568ed294e998fcc48fa5cbd881d76dfd99f
[GitHub/WoltLab/WCF.git] /
1 <?php
2
3 declare(strict_types=1);
4
5 namespace CuyZ\Valinor\Definition\Repository\Reflection;
6
7 use CuyZ\Valinor\Definition\Exception\TypesDoNotMatch;
8 use CuyZ\Valinor\Type\Parser\Exception\InvalidType;
9 use CuyZ\Valinor\Type\Parser\TypeParser;
10 use CuyZ\Valinor\Type\Type;
11 use CuyZ\Valinor\Type\Types\ArrayKeyType;
12 use CuyZ\Valinor\Type\Types\ArrayType;
13 use CuyZ\Valinor\Type\Types\MixedType;
14 use CuyZ\Valinor\Type\Types\UnresolvableType;
15 use CuyZ\Valinor\Utility\Reflection\DocParser;
16 use CuyZ\Valinor\Utility\Reflection\Reflection;
17 use ReflectionFunctionAbstract;
18 use ReflectionParameter;
19 use ReflectionProperty;
20
21 /** @internal */
22 final class ReflectionTypeResolver
23 {
24 public function __construct(
25 private TypeParser $nativeParser,
26 private TypeParser $advancedParser
27 ) {}
28
29 public function resolveType(ReflectionProperty|ReflectionParameter|ReflectionFunctionAbstract $reflection): Type
30 {
31 $nativeType = $this->nativeType($reflection);
32 $typeFromDocBlock = $this->typeFromDocBlock($reflection);
33
34 if (! $nativeType && ! $typeFromDocBlock) {
35 return MixedType::get();
36 }
37
38 if (! $nativeType) {
39 /** @var Type $typeFromDocBlock */
40 return $typeFromDocBlock;
41 }
42
43 if (! $typeFromDocBlock) {
44 return $nativeType;
45 }
46
47 if (! $typeFromDocBlock instanceof UnresolvableType
48 && ! $nativeType instanceof UnresolvableType
49 && ! $typeFromDocBlock->matches($nativeType)
50 ) {
51 throw new TypesDoNotMatch($reflection, $typeFromDocBlock, $nativeType);
52 }
53
54 return $typeFromDocBlock;
55 }
56
57 private function typeFromDocBlock(ReflectionProperty|ReflectionParameter|ReflectionFunctionAbstract $reflection): ?Type
58 {
59 if ($reflection instanceof ReflectionFunctionAbstract) {
60 $type = DocParser::functionReturnType($reflection);
61 } elseif ($reflection instanceof ReflectionProperty) {
62 $type = DocParser::propertyType($reflection);
63 } else {
64 $type = null;
65
66 if ($reflection->isPromoted()) {
67 // @phpstan-ignore-next-line / parameter is promoted so class exists for sure
68 $type = DocParser::propertyType($reflection->getDeclaringClass()->getProperty($reflection->name));
69 }
70
71 if ($type === null) {
72 $type = DocParser::parameterType($reflection);
73 }
74 }
75
76 if ($type === null) {
77 return null;
78 }
79
80 $type = $this->parseType($type, $reflection, $this->advancedParser);
81
82 return $this->handleVariadicType($reflection, $type);
83 }
84
85 private function nativeType(ReflectionProperty|ReflectionParameter|ReflectionFunctionAbstract $reflection): ?Type
86 {
87 $reflectionType = $reflection instanceof ReflectionFunctionAbstract
88 ? $reflection->getReturnType()
89 : $reflection->getType();
90
91 if (! $reflectionType) {
92 return null;
93 }
94
95 $type = Reflection::flattenType($reflectionType);
96 $type = $this->parseType($type, $reflection, $this->nativeParser);
97
98 return $this->handleVariadicType($reflection, $type);
99 }
100
101 private function parseType(string $raw, ReflectionProperty|ReflectionParameter|ReflectionFunctionAbstract $reflection, TypeParser $parser): Type
102 {
103 try {
104 return $parser->parse($raw);
105 } catch (InvalidType $exception) {
106 $raw = trim($raw);
107 $signature = Reflection::signature($reflection);
108
109 if ($reflection instanceof ReflectionProperty) {
110 return UnresolvableType::forProperty($raw, $signature, $exception);
111 }
112
113 if ($reflection instanceof ReflectionParameter) {
114 return UnresolvableType::forParameter($raw, $signature, $exception);
115 }
116
117 return UnresolvableType::forMethodReturnType($raw, $signature, $exception);
118 }
119 }
120
121 private function handleVariadicType(ReflectionProperty|ReflectionParameter|ReflectionFunctionAbstract $reflection, Type $type): Type
122 {
123 if (! $reflection instanceof ReflectionParameter || ! $reflection->isVariadic()) {
124 return $type;
125 }
126
127 return new ArrayType(ArrayKeyType::default(), $type);
128 }
129 }