PocketMine-MP 5.37.1 git-da6732df2656426fbd1b7898ed06c8286969d2f1
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
80 public function isSatisfiedBy(Prototype $callable) : bool{
81 if(!$this->returnInfo->isSatisfiedBy($callable->returnInfo)){
82 return false;
83 }
84
85 if($callable->requiredParameterCount > $this->requiredParameterCount){
86 return false;
87 }
88
89 $last = null;
90
91 foreach($callable->parameters as $position => $parameter){
92 // Parameters that exist in the prototype must always be satisfied directly
93 if(isset($this->parameters[$position])){
94 if(!$this->parameters[$position]->isSatisfiedBy($parameter)){
95 return false;
96 }
97
98 $last = $this->parameters[$position];
99 continue;
100 }
101
102 // Candidates can accept additional args that are not in the prototype as long as they are not mandatory
103 if(!$parameter->isOptional && !$parameter->isVariadic){
104 return false;
105 }
106
107 // If the last arg of the prototype is variadic, any additional args the candidate accepts must satisfy it
108 if($last !== null && $last->isVariadic && !$last->isSatisfiedBy($parameter)){
109 return false;
110 }
111 }
112
113 return true;
114 }
115
119 public function __toString() : string{
120 $string = 'function ';
121
122 if($this->returnInfo->byReference){
123 $string .= '& ';
124 }
125
126 $string .= '( ';
127
128 $i = $o = 0;
129 $l = count($this->parameters) - 1;
130 for(; $i < $l; $i++){
131 $string .= $this->parameters[$i];
132
133 if($o === 0 && !($this->parameters[$i + 1]->isOptional)){
134 $string .= ', ';
135 continue;
136 }
137
138 $string .= ' [, ';
139 $o++;
140 }
141
142 if(isset($this->parameters[$l])){
143 $string .= $this->parameters[$i] . ' ';
144 }
145
146 if($o !== 0){
147 $string .= str_repeat(']', $o) . ' ';
148 }
149
150 $string .= ')';
151
152 if($this->returnInfo->type !== null){
153 $string .= ' : ' . $this->returnInfo->type->stringify();
154 }
155
156 return $string;
157 }
158}