PocketMine-MP 5.39.3 git-21ae710729750cd637333d673bbbbbc598fc659e
Loading...
Searching...
No Matches
Prototype.php
1<?php declare(strict_types=1);
2
3namespace DaveRandom\CallbackValidator;
4
6use DaveRandom\CallbackValidator\Type\BuiltInType;
10use function array_map;
11use function get_class;
12
13final class Prototype{
14 private ReturnInfo $returnInfo;
15
17 private array $parameters;
18
19 private int $requiredParameterCount;
20
26 private static function convertReflectionTypeArray(array $types) : array{
27 return array_map(fn(\ReflectionType $innerType) => self::convertReflectionTypeInner($innerType), $types);
28 }
29
30 private static function convertReflectionTypeInner(\ReflectionType $type) : BaseType{
31 return match (true) {
32 $type instanceof \ReflectionNamedType =>
33 //simple nullable types are still represented by named types despite technically being unions of Type|null
34 //convert these to unions so MatchTester can handle them properly
35 //mixed types are considered nullable by the reflection API
36 $type->allowsNull() && $type->getName() !== BuiltInType::MIXED->value ?
37 new UnionType([new NamedType($type->getName()), new NamedType(BuiltInType::NULL)]) :
38 new NamedType($type->getName()),
39 $type instanceof \ReflectionUnionType => new UnionType(self::convertReflectionTypeArray($type->getTypes())),
40 $type instanceof \ReflectionIntersectionType => new IntersectionType(self::convertReflectionTypeArray($type->getTypes())),
41 default => throw new \AssertionError("Unhandled reflection type " . get_class($type))
42 };
43 }
44
45 private static function convertReflectionType(?\ReflectionType $type) : ?BaseType{
46 return $type === null ? null : self::convertReflectionTypeInner($type);
47 }
48
49 public static function fromClosure(\Closure $callable) : Prototype{
50 $reflection = new \ReflectionFunction($callable);
51
52 $returnType = new ReturnInfo(self::convertReflectionType($reflection->getReturnType()), $reflection->returnsReference());
53
54 $parameters = [];
55
56 foreach($reflection->getParameters() as $parameterReflection){
57 $parameters[] = new ParameterInfo(
58 $parameterReflection->getName(),
59 self::convertReflectionType($parameterReflection->getType()),
60 $parameterReflection->isPassedByReference(),
61 $parameterReflection->isOptional(),
62 $parameterReflection->isVariadic()
63 );
64 }
65
66 return new Prototype($returnType, ...$parameters);
67 }
68
69 public function __construct(ReturnInfo $returnType, ParameterInfo ...$parameters){
70 $this->returnInfo = $returnType;
71 $this->parameters = $parameters;
72 $this->requiredParameterCount = 0;
73 foreach($parameters as $parameter){
74 if(!$parameter->isOptional && !$parameter->isVariadic){
75 $this->requiredParameterCount++;
76 }
77 }
78 }
79
83 public function getParameterInfo() : array{
84 return $this->parameters;
85 }
86
87 public function getRequiredParameterCount() : int{
88 return $this->requiredParameterCount;
89 }
90
91 public function getReturnInfo() : ReturnInfo{
92 return $this->returnInfo;
93 }
94
95 public function isSatisfiedBy(Prototype $callable) : bool{
96 if(!$this->returnInfo->isSatisfiedBy($callable->returnInfo)){
97 return false;
98 }
99
100 if($callable->requiredParameterCount > $this->requiredParameterCount){
101 return false;
102 }
103
104 $last = null;
105
106 foreach($callable->parameters as $position => $parameter){
107 // Parameters that exist in the prototype must always be satisfied directly
108 if(isset($this->parameters[$position])){
109 if(!$this->parameters[$position]->isSatisfiedBy($parameter)){
110 return false;
111 }
112
113 $last = $this->parameters[$position];
114 continue;
115 }
116
117 // Candidates can accept additional args that are not in the prototype as long as they are not mandatory
118 if(!$parameter->isOptional && !$parameter->isVariadic){
119 return false;
120 }
121
122 // If the last arg of the prototype is variadic, any additional args the candidate accepts must satisfy it
123 if($last !== null && $last->isVariadic && !$last->isSatisfiedBy($parameter)){
124 return false;
125 }
126 }
127
128 return true;
129 }
130
134 public function __toString() : string{
135 $string = 'function ';
136
137 if($this->returnInfo->byReference){
138 $string .= '& ';
139 }
140
141 $string .= '( ';
142
143 $i = $o = 0;
144 $l = count($this->parameters) - 1;
145 for(; $i < $l; $i++){
146 $string .= $this->parameters[$i];
147
148 if($o === 0 && !($this->parameters[$i + 1]->isOptional)){
149 $string .= ', ';
150 continue;
151 }
152
153 $string .= ' [, ';
154 $o++;
155 }
156
157 if(isset($this->parameters[$l])){
158 $string .= $this->parameters[$i] . ' ';
159 }
160
161 if($o !== 0){
162 $string .= str_repeat(']', $o) . ' ';
163 }
164
165 $string .= ')';
166
167 if($this->returnInfo->type !== null){
168 $string .= ' : ' . $this->returnInfo->type->stringify();
169 }
170
171 return $string;
172 }
173}