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\Constant\ConstantStringType;
10: use PHPStan\Type\Enum\EnumCaseObjectType;
11: use PHPStan\Type\Generic\TemplateType;
12: use function array_merge;
13: use function array_unique;
14: use function array_values;
15:
16: /** @api */
17: class TypeUtils
18: {
19:
20: /**
21: * @return ArrayType[]
22: *
23: * @deprecated Use PHPStan\Type\Type::getArrays() instead and handle optional ConstantArrayType keys if necessary.
24: */
25: public static function getArrays(Type $type): array
26: {
27: if ($type instanceof ConstantArrayType) {
28: return $type->getAllArrays();
29: }
30:
31: if ($type instanceof ArrayType) {
32: return [$type];
33: }
34:
35: if ($type instanceof UnionType) {
36: $matchingTypes = [];
37: foreach ($type->getTypes() as $innerType) {
38: if (!$innerType instanceof ArrayType) {
39: return [];
40: }
41: foreach (self::getArrays($innerType) as $innerInnerType) {
42: $matchingTypes[] = $innerInnerType;
43: }
44: }
45:
46: return $matchingTypes;
47: }
48:
49: if ($type instanceof IntersectionType) {
50: $matchingTypes = [];
51: foreach ($type->getTypes() as $innerType) {
52: if (!$innerType instanceof ArrayType) {
53: continue;
54: }
55: foreach (self::getArrays($innerType) as $innerInnerType) {
56: $matchingTypes[] = $innerInnerType;
57: }
58: }
59:
60: return $matchingTypes;
61: }
62:
63: return [];
64: }
65:
66: /**
67: * @return ConstantArrayType[]
68: *
69: * @deprecated Use PHPStan\Type\Type::getConstantArrays() instead and handle optional keys if necessary.
70: */
71: public static function getConstantArrays(Type $type): array
72: {
73: if ($type instanceof ConstantArrayType) {
74: return $type->getAllArrays();
75: }
76:
77: if ($type instanceof UnionType) {
78: $matchingTypes = [];
79: foreach ($type->getTypes() as $innerType) {
80: if (!$innerType instanceof ConstantArrayType) {
81: return [];
82: }
83: foreach (self::getConstantArrays($innerType) as $innerInnerType) {
84: $matchingTypes[] = $innerInnerType;
85: }
86: }
87:
88: return $matchingTypes;
89: }
90:
91: return [];
92: }
93:
94: /**
95: * @return ConstantStringType[]
96: *
97: * @deprecated Use PHPStan\Type\Type::getConstantStrings() instead
98: */
99: public static function getConstantStrings(Type $type): array
100: {
101: return self::map(ConstantStringType::class, $type, false);
102: }
103:
104: /**
105: * @return ConstantIntegerType[]
106: */
107: public static function getConstantIntegers(Type $type): array
108: {
109: return self::map(ConstantIntegerType::class, $type, false);
110: }
111:
112: /**
113: * @return ConstantType[]
114: */
115: public static function getConstantTypes(Type $type): array
116: {
117: return self::map(ConstantType::class, $type, false);
118: }
119:
120: /**
121: * @return ConstantType[]
122: */
123: public static function getAnyConstantTypes(Type $type): array
124: {
125: return self::map(ConstantType::class, $type, false, false);
126: }
127:
128: /**
129: * @return ArrayType[]
130: *
131: * @deprecated Use PHPStan\Type\Type::getArrays() instead.
132: */
133: public static function getAnyArrays(Type $type): array
134: {
135: return self::map(ArrayType::class, $type, true, false);
136: }
137:
138: /**
139: * @deprecated Use PHPStan\Type\Type::generalize() instead.
140: */
141: public static function generalizeType(Type $type, GeneralizePrecision $precision): Type
142: {
143: return $type->generalize($precision);
144: }
145:
146: /**
147: * @return list<string>
148: */
149: public static function getDirectClassNames(Type $type): array
150: {
151: if ($type instanceof TypeWithClassName) {
152: return [$type->getClassName()];
153: }
154:
155: if ($type instanceof UnionType || $type instanceof IntersectionType) {
156: $classNames = [];
157: foreach ($type->getTypes() as $innerType) {
158: foreach (self::getDirectClassNames($innerType) as $n) {
159: $classNames[] = $n;
160: }
161: }
162:
163: return array_values(array_unique($classNames));
164: }
165:
166: return [];
167: }
168:
169: /**
170: * @return IntegerRangeType[]
171: */
172: public static function getIntegerRanges(Type $type): array
173: {
174: return self::map(IntegerRangeType::class, $type, false);
175: }
176:
177: /**
178: * @return ConstantScalarType[]
179: */
180: public static function getConstantScalars(Type $type): array
181: {
182: return self::map(ConstantScalarType::class, $type, false);
183: }
184:
185: /**
186: * @return EnumCaseObjectType[]
187: */
188: public static function getEnumCaseObjects(Type $type): array
189: {
190: return self::map(EnumCaseObjectType::class, $type, false);
191: }
192:
193: /**
194: * @internal
195: * @return ConstantArrayType[]
196: *
197: * @deprecated Use PHPStan\Type\Type::getConstantArrays().
198: */
199: public static function getOldConstantArrays(Type $type): array
200: {
201: return self::map(ConstantArrayType::class, $type, false);
202: }
203:
204: /**
205: * @return mixed[]
206: */
207: private static function map(
208: string $typeClass,
209: Type $type,
210: bool $inspectIntersections,
211: bool $stopOnUnmatched = true,
212: ): array
213: {
214: if ($type instanceof $typeClass) {
215: return [$type];
216: }
217:
218: if ($type instanceof UnionType) {
219: $matchingTypes = [];
220: foreach ($type->getTypes() as $innerType) {
221: $matchingInner = self::map($typeClass, $innerType, $inspectIntersections, $stopOnUnmatched);
222:
223: if ($matchingInner === []) {
224: if ($stopOnUnmatched) {
225: return [];
226: }
227:
228: continue;
229: }
230:
231: foreach ($matchingInner as $innerMapped) {
232: $matchingTypes[] = $innerMapped;
233: }
234: }
235:
236: return $matchingTypes;
237: }
238:
239: if ($inspectIntersections && $type instanceof IntersectionType) {
240: $matchingTypes = [];
241: foreach ($type->getTypes() as $innerType) {
242: if (!$innerType instanceof $typeClass) {
243: if ($stopOnUnmatched) {
244: return [];
245: }
246:
247: continue;
248: }
249:
250: $matchingTypes[] = $innerType;
251: }
252:
253: return $matchingTypes;
254: }
255:
256: return [];
257: }
258:
259: public static function toBenevolentUnion(Type $type): Type
260: {
261: if ($type instanceof BenevolentUnionType) {
262: return $type;
263: }
264:
265: if ($type instanceof UnionType) {
266: return new BenevolentUnionType($type->getTypes());
267: }
268:
269: return $type;
270: }
271:
272: /**
273: * @return Type[]
274: */
275: public static function flattenTypes(Type $type): array
276: {
277: if ($type instanceof ConstantArrayType) {
278: return $type->getAllArrays();
279: }
280:
281: if ($type instanceof UnionType) {
282: $types = [];
283: foreach ($type->getTypes() as $innerType) {
284: if ($innerType instanceof ConstantArrayType) {
285: foreach ($innerType->getAllArrays() as $array) {
286: $types[] = $array;
287: }
288: continue;
289: }
290:
291: $types[] = $innerType;
292: }
293:
294: return $types;
295: }
296:
297: return [$type];
298: }
299:
300: public static function findThisType(Type $type): ?ThisType
301: {
302: if ($type instanceof ThisType) {
303: return $type;
304: }
305:
306: if ($type instanceof UnionType || $type instanceof IntersectionType) {
307: foreach ($type->getTypes() as $innerType) {
308: $thisType = self::findThisType($innerType);
309: if ($thisType !== null) {
310: return $thisType;
311: }
312: }
313: }
314:
315: return null;
316: }
317:
318: /**
319: * @return HasPropertyType[]
320: */
321: public static function getHasPropertyTypes(Type $type): array
322: {
323: if ($type instanceof HasPropertyType) {
324: return [$type];
325: }
326:
327: if ($type instanceof UnionType || $type instanceof IntersectionType) {
328: $hasPropertyTypes = [[]];
329: foreach ($type->getTypes() as $innerType) {
330: $hasPropertyTypes[] = self::getHasPropertyTypes($innerType);
331: }
332:
333: return array_merge(...$hasPropertyTypes);
334: }
335:
336: return [];
337: }
338:
339: /**
340: * @return AccessoryType[]
341: */
342: public static function getAccessoryTypes(Type $type): array
343: {
344: return self::map(AccessoryType::class, $type, true, false);
345: }
346:
347: /** @deprecated Use PHPStan\Type\Type::isCallable() instead. */
348: public static function containsCallable(Type $type): bool
349: {
350: if ($type->isCallable()->yes()) {
351: return true;
352: }
353:
354: if ($type instanceof UnionType) {
355: foreach ($type->getTypes() as $innerType) {
356: if ($innerType->isCallable()->yes()) {
357: return true;
358: }
359: }
360: }
361:
362: return false;
363: }
364:
365: public static function containsTemplateType(Type $type): bool
366: {
367: $containsTemplateType = false;
368: TypeTraverser::map($type, static function (Type $type, callable $traverse) use (&$containsTemplateType): Type {
369: if ($type instanceof TemplateType) {
370: $containsTemplateType = true;
371: }
372:
373: return $containsTemplateType ? $type : $traverse($type);
374: });
375:
376: return $containsTemplateType;
377: }
378:
379: public static function resolveLateResolvableTypes(Type $type, bool $resolveUnresolvableTypes = true): Type
380: {
381: return TypeTraverser::map($type, static function (Type $type, callable $traverse) use ($resolveUnresolvableTypes): Type {
382: $type = $traverse($type);
383:
384: if ($type instanceof LateResolvableType && ($resolveUnresolvableTypes || $type->isResolvable())) {
385: $type = $type->resolve();
386: }
387:
388: return $type;
389: });
390: }
391:
392: }
393: