PocketMine-MP 5.33.2 git-919492bdcad8510eb6606439eb77e1c604f1d1ea
Loading...
Searching...
No Matches
MatchTester.php
1<?php declare(strict_types=1);
2
3namespace DaveRandom\CallbackValidator;
4
5use DaveRandom\CallbackValidator\BuiltInType;
6
7final class MatchTester{
11 private function __construct(){}
12
13 public static function isCovariant(?\ReflectionType $acceptingType, ?\ReflectionType $givenType) : bool{
14 // If the super type is unspecified, anything is a match
15 if($acceptingType === null || ($acceptingType instanceof \ReflectionNamedType && $acceptingType->getName() === BuiltInType::VOID->value)){
16 return true;
17 }
18
19 // If the sub type is unspecified, nothing is a match
20 if($givenType === null){
21 return false;
22 }
23
24 // given type cannot accept null if the accepting type does not
25 // we don't want to consider allowsNull except at the top level
26 if(!$acceptingType->allowsNull() && $givenType->allowsNull()){
27 return false;
28 }
29
30 return self::isCompositeTypeCovariant($acceptingType, $givenType);
31 }
32
33 private static function isCompositeTypeCovariant(\ReflectionType $acceptingType, \ReflectionType $givenType) : bool{
34 //composite type acceptance:
35 //super named type -> named, union (all parts must be accepted by super), intersection (at least 1 part must be accepted by super)
36 //super union -> named (must be accepted by at least 1 super), union (all parts must be accepted by at least 1 super), intersection (at least 1 part must be accepted by at least 1 super?)
37 //super intersection -> named (must be accepted by all supers), union (all parts must be accepted by all supers), intersection (all parts must be accepted by all supers)
38
39 //given union is handled the same no matter what the accepting type is
40 //ensure all parts are covariant with the accepting type
41 if($givenType instanceof \ReflectionUnionType){
42 foreach($givenType->getTypes() as $type){
43 if(!self::isCompositeTypeCovariant($acceptingType, $type)){
44 return false;
45 }
46 }
47
48 return true;
49 }
50
51 if($acceptingType instanceof \ReflectionNamedType){
52 //at least 1 part of a given intersection must be covariant with the accepting type
53 //given intersection can only be compared with a named type to validate variance - the parts cannot
54 //be individually tested against a composite type
55 if($givenType instanceof \ReflectionIntersectionType){
56 foreach($givenType->getTypes() as $type){
57 if(self::isCompositeTypeCovariant($acceptingType, $type)){
58 return true;
59 }
60 }
61
62 return false;
63 }
64
65 if($givenType instanceof \ReflectionNamedType){
66 $acceptingTypeName = $acceptingType->getName();
67 $givenTypeName = $givenType->getName();
68 if($acceptingTypeName === $givenTypeName){
69 // Exact match
70 return true;
71 }
72
73 if($acceptingTypeName === BuiltInType::MIXED->value && $givenTypeName !== BuiltInType::VOID->value){
74 //anything is covariant with mixed except void
75 return true;
76 }
77
78 if($acceptingTypeName === BuiltInType::FLOAT->value && $givenTypeName === BuiltInType::INT->value){
79 //int is covariant with float even in strict mode
80 return true;
81 }
82
83 // Check iterable
84 if($acceptingTypeName === BuiltInType::ITERABLE->value){
85 return $givenTypeName === BuiltInType::ARRAY->value
86 || $givenTypeName === \Traversable::class
87 || \is_subclass_of($givenTypeName, \Traversable::class);
88 }
89
90 // Check callable
91 if($acceptingTypeName === BuiltInType::CALLABLE->value){
92 return $givenTypeName === \Closure::class
93 || \method_exists($givenTypeName, '__invoke')
94 || \is_subclass_of($givenTypeName, \Closure::class);
95 }
96
97 if($acceptingTypeName === BuiltInType::OBJECT->value){
98 //a class type is covariant with object
99 return BuiltInType::tryFrom($givenTypeName) === null;
100 }
101
102 return is_string($givenTypeName) && is_string($acceptingTypeName) && \is_subclass_of($givenTypeName, $acceptingTypeName);
103 }
104
105 throw new \AssertionError("Unhandled reflection type " . get_class($givenType));
106 }
107
108 if($acceptingType instanceof \ReflectionUnionType){
109 //accepting union - the given type must be covariant with at least 1 part
110 foreach($acceptingType->getTypes() as $type){
111 if(self::isCompositeTypeCovariant($type, $givenType)){
112 return true;
113 }
114 }
115
116 return false;
117 }
118
119 if($acceptingType instanceof \ReflectionIntersectionType){
120 //accepting intersection - the given type must be covariant with all parts
121 foreach($acceptingType->getTypes() as $type){
122 if(!self::isCompositeTypeCovariant($type, $givenType)){
123 return false;
124 }
125 }
126
127 return true;
128 }
129
130 return false;
131 }
132}