1: | <?php declare(strict_types = 1); |
2: | |
3: | namespace PHPStan\Type; |
4: | |
5: | use PHPStan\Type\Accessory\AccessoryType; |
6: | use PHPStan\Type\Accessory\HasPropertyType; |
7: | use PHPStan\Type\Constant\ConstantArrayType; |
8: | use PHPStan\Type\Constant\ConstantIntegerType; |
9: | use PHPStan\Type\Generic\TemplateBenevolentUnionType; |
10: | use PHPStan\Type\Generic\TemplateType; |
11: | use PHPStan\Type\Generic\TemplateUnionType; |
12: | use function array_merge; |
13: | |
14: | |
15: | |
16: | |
17: | final class TypeUtils |
18: | { |
19: | |
20: | |
21: | |
22: | |
23: | public static function getConstantIntegers(Type $type): array |
24: | { |
25: | return self::map(ConstantIntegerType::class, $type, false); |
26: | } |
27: | |
28: | |
29: | |
30: | |
31: | public static function getIntegerRanges(Type $type): array |
32: | { |
33: | return self::map(IntegerRangeType::class, $type, false); |
34: | } |
35: | |
36: | |
37: | |
38: | |
39: | private static function map( |
40: | string $typeClass, |
41: | Type $type, |
42: | bool $inspectIntersections, |
43: | bool $stopOnUnmatched = true, |
44: | ): array |
45: | { |
46: | if ($type instanceof $typeClass) { |
47: | return [$type]; |
48: | } |
49: | |
50: | if ($type instanceof UnionType) { |
51: | $matchingTypes = []; |
52: | foreach ($type->getTypes() as $innerType) { |
53: | $matchingInner = self::map($typeClass, $innerType, $inspectIntersections, $stopOnUnmatched); |
54: | |
55: | if ($matchingInner === []) { |
56: | if ($stopOnUnmatched) { |
57: | return []; |
58: | } |
59: | |
60: | continue; |
61: | } |
62: | |
63: | foreach ($matchingInner as $innerMapped) { |
64: | $matchingTypes[] = $innerMapped; |
65: | } |
66: | } |
67: | |
68: | return $matchingTypes; |
69: | } |
70: | |
71: | if ($inspectIntersections && $type instanceof IntersectionType) { |
72: | $matchingTypes = []; |
73: | foreach ($type->getTypes() as $innerType) { |
74: | if (!$innerType instanceof $typeClass) { |
75: | if ($stopOnUnmatched) { |
76: | return []; |
77: | } |
78: | |
79: | continue; |
80: | } |
81: | |
82: | $matchingTypes[] = $innerType; |
83: | } |
84: | |
85: | return $matchingTypes; |
86: | } |
87: | |
88: | return []; |
89: | } |
90: | |
91: | public static function toBenevolentUnion(Type $type): Type |
92: | { |
93: | if ($type instanceof BenevolentUnionType) { |
94: | return $type; |
95: | } |
96: | |
97: | if ($type instanceof UnionType) { |
98: | return new BenevolentUnionType($type->getTypes()); |
99: | } |
100: | |
101: | return $type; |
102: | } |
103: | |
104: | |
105: | |
106: | |
107: | public static function toStrictUnion(Type $type): Type |
108: | { |
109: | if ($type instanceof TemplateBenevolentUnionType) { |
110: | return new TemplateUnionType( |
111: | $type->getScope(), |
112: | $type->getStrategy(), |
113: | $type->getVariance(), |
114: | $type->getName(), |
115: | static::toStrictUnion($type->getBound()), |
116: | $type->getDefault(), |
117: | ); |
118: | } |
119: | |
120: | if ($type instanceof BenevolentUnionType) { |
121: | return new UnionType($type->getTypes()); |
122: | } |
123: | |
124: | return $type; |
125: | } |
126: | |
127: | |
128: | |
129: | |
130: | public static function flattenTypes(Type $type): array |
131: | { |
132: | if ($type instanceof ConstantArrayType) { |
133: | return $type->getAllArrays(); |
134: | } |
135: | |
136: | if ($type instanceof UnionType) { |
137: | $types = []; |
138: | foreach ($type->getTypes() as $innerType) { |
139: | if ($innerType instanceof ConstantArrayType) { |
140: | foreach ($innerType->getAllArrays() as $array) { |
141: | $types[] = $array; |
142: | } |
143: | continue; |
144: | } |
145: | |
146: | $types[] = $innerType; |
147: | } |
148: | |
149: | return $types; |
150: | } |
151: | |
152: | return [$type]; |
153: | } |
154: | |
155: | public static function findThisType(Type $type): ?ThisType |
156: | { |
157: | if ($type instanceof ThisType) { |
158: | return $type; |
159: | } |
160: | |
161: | if ($type instanceof UnionType || $type instanceof IntersectionType) { |
162: | foreach ($type->getTypes() as $innerType) { |
163: | $thisType = self::findThisType($innerType); |
164: | if ($thisType !== null) { |
165: | return $thisType; |
166: | } |
167: | } |
168: | } |
169: | |
170: | return null; |
171: | } |
172: | |
173: | |
174: | |
175: | |
176: | public static function getHasPropertyTypes(Type $type): array |
177: | { |
178: | if ($type instanceof HasPropertyType) { |
179: | return [$type]; |
180: | } |
181: | |
182: | if ($type instanceof UnionType || $type instanceof IntersectionType) { |
183: | $hasPropertyTypes = [[]]; |
184: | foreach ($type->getTypes() as $innerType) { |
185: | $hasPropertyTypes[] = self::getHasPropertyTypes($innerType); |
186: | } |
187: | |
188: | return array_merge(...$hasPropertyTypes); |
189: | } |
190: | |
191: | return []; |
192: | } |
193: | |
194: | |
195: | |
196: | |
197: | public static function getAccessoryTypes(Type $type): array |
198: | { |
199: | return self::map(AccessoryType::class, $type, true, false); |
200: | } |
201: | |
202: | public static function containsTemplateType(Type $type): bool |
203: | { |
204: | $containsTemplateType = false; |
205: | TypeTraverser::map($type, static function (Type $type, callable $traverse) use (&$containsTemplateType): Type { |
206: | if ($type instanceof TemplateType) { |
207: | $containsTemplateType = true; |
208: | } |
209: | |
210: | return $containsTemplateType ? $type : $traverse($type); |
211: | }); |
212: | |
213: | return $containsTemplateType; |
214: | } |
215: | |
216: | public static function resolveLateResolvableTypes(Type $type, bool $resolveUnresolvableTypes = true): Type |
217: | { |
218: | |
219: | $ignoreResolveUnresolvableTypesLevel = 0; |
220: | |
221: | return TypeTraverser::map($type, static function (Type $type, callable $traverse) use ($resolveUnresolvableTypes, &$ignoreResolveUnresolvableTypesLevel): Type { |
222: | while ($type instanceof LateResolvableType && (($resolveUnresolvableTypes && $ignoreResolveUnresolvableTypesLevel === 0) || $type->isResolvable())) { |
223: | $type = $type->resolve(); |
224: | } |
225: | |
226: | if ($type instanceof CallableType || $type instanceof ClosureType) { |
227: | $ignoreResolveUnresolvableTypesLevel++; |
228: | $result = $traverse($type); |
229: | $ignoreResolveUnresolvableTypesLevel--; |
230: | |
231: | return $result; |
232: | } |
233: | |
234: | return $traverse($type); |
235: | }); |
236: | } |
237: | |
238: | } |
239: | |