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: * @deprecated Use Type::isConstantValue() or Type::generalize()
114: * @return ConstantType[]
115: */
116: public static function getConstantTypes(Type $type): array
117: {
118: return self::map(ConstantType::class, $type, false);
119: }
120:
121: /**
122: * @deprecated Use Type::isConstantValue() or Type::generalize()
123: * @return ConstantType[]
124: */
125: public static function getAnyConstantTypes(Type $type): array
126: {
127: return self::map(ConstantType::class, $type, false, false);
128: }
129:
130: /**
131: * @return ArrayType[]
132: *
133: * @deprecated Use PHPStan\Type\Type::getArrays() instead.
134: */
135: public static function getAnyArrays(Type $type): array
136: {
137: return self::map(ArrayType::class, $type, true, false);
138: }
139:
140: /**
141: * @deprecated Use PHPStan\Type\Type::generalize() instead.
142: */
143: public static function generalizeType(Type $type, GeneralizePrecision $precision): Type
144: {
145: return $type->generalize($precision);
146: }
147:
148: /**
149: * @return list<string>
150: *
151: * @deprecated Use Type::getObjectClassNames() instead.
152: */
153: public static function getDirectClassNames(Type $type): array
154: {
155: if ($type instanceof TypeWithClassName) {
156: return [$type->getClassName()];
157: }
158:
159: if ($type instanceof UnionType || $type instanceof IntersectionType) {
160: $classNames = [];
161: foreach ($type->getTypes() as $innerType) {
162: foreach (self::getDirectClassNames($innerType) as $n) {
163: $classNames[] = $n;
164: }
165: }
166:
167: return array_values(array_unique($classNames));
168: }
169:
170: return [];
171: }
172:
173: /**
174: * @return IntegerRangeType[]
175: */
176: public static function getIntegerRanges(Type $type): array
177: {
178: return self::map(IntegerRangeType::class, $type, false);
179: }
180:
181: /**
182: * @deprecated Use Type::isConstantScalarValue() or Type::getConstantScalarTypes() or Type::getConstantScalarValues()
183: * @return ConstantScalarType[]
184: */
185: public static function getConstantScalars(Type $type): array
186: {
187: return self::map(ConstantScalarType::class, $type, false);
188: }
189:
190: /**
191: * @deprecated Use Type::getEnumCases()
192: * @return EnumCaseObjectType[]
193: */
194: public static function getEnumCaseObjects(Type $type): array
195: {
196: return self::map(EnumCaseObjectType::class, $type, false);
197: }
198:
199: /**
200: * @internal
201: * @return ConstantArrayType[]
202: *
203: * @deprecated Use PHPStan\Type\Type::getConstantArrays().
204: */
205: public static function getOldConstantArrays(Type $type): array
206: {
207: return self::map(ConstantArrayType::class, $type, false);
208: }
209:
210: /**
211: * @return mixed[]
212: */
213: private static function map(
214: string $typeClass,
215: Type $type,
216: bool $inspectIntersections,
217: bool $stopOnUnmatched = true,
218: ): array
219: {
220: if ($type instanceof $typeClass) {
221: return [$type];
222: }
223:
224: if ($type instanceof UnionType) {
225: $matchingTypes = [];
226: foreach ($type->getTypes() as $innerType) {
227: $matchingInner = self::map($typeClass, $innerType, $inspectIntersections, $stopOnUnmatched);
228:
229: if ($matchingInner === []) {
230: if ($stopOnUnmatched) {
231: return [];
232: }
233:
234: continue;
235: }
236:
237: foreach ($matchingInner as $innerMapped) {
238: $matchingTypes[] = $innerMapped;
239: }
240: }
241:
242: return $matchingTypes;
243: }
244:
245: if ($inspectIntersections && $type instanceof IntersectionType) {
246: $matchingTypes = [];
247: foreach ($type->getTypes() as $innerType) {
248: if (!$innerType instanceof $typeClass) {
249: if ($stopOnUnmatched) {
250: return [];
251: }
252:
253: continue;
254: }
255:
256: $matchingTypes[] = $innerType;
257: }
258:
259: return $matchingTypes;
260: }
261:
262: return [];
263: }
264:
265: public static function toBenevolentUnion(Type $type): Type
266: {
267: if ($type instanceof BenevolentUnionType) {
268: return $type;
269: }
270:
271: if ($type instanceof UnionType) {
272: return new BenevolentUnionType($type->getTypes());
273: }
274:
275: return $type;
276: }
277:
278: /**
279: * @return Type[]
280: */
281: public static function flattenTypes(Type $type): array
282: {
283: if ($type instanceof ConstantArrayType) {
284: return $type->getAllArrays();
285: }
286:
287: if ($type instanceof UnionType) {
288: $types = [];
289: foreach ($type->getTypes() as $innerType) {
290: if ($innerType instanceof ConstantArrayType) {
291: foreach ($innerType->getAllArrays() as $array) {
292: $types[] = $array;
293: }
294: continue;
295: }
296:
297: $types[] = $innerType;
298: }
299:
300: return $types;
301: }
302:
303: return [$type];
304: }
305:
306: public static function findThisType(Type $type): ?ThisType
307: {
308: if ($type instanceof ThisType) {
309: return $type;
310: }
311:
312: if ($type instanceof UnionType || $type instanceof IntersectionType) {
313: foreach ($type->getTypes() as $innerType) {
314: $thisType = self::findThisType($innerType);
315: if ($thisType !== null) {
316: return $thisType;
317: }
318: }
319: }
320:
321: return null;
322: }
323:
324: /**
325: * @return HasPropertyType[]
326: */
327: public static function getHasPropertyTypes(Type $type): array
328: {
329: if ($type instanceof HasPropertyType) {
330: return [$type];
331: }
332:
333: if ($type instanceof UnionType || $type instanceof IntersectionType) {
334: $hasPropertyTypes = [[]];
335: foreach ($type->getTypes() as $innerType) {
336: $hasPropertyTypes[] = self::getHasPropertyTypes($innerType);
337: }
338:
339: return array_merge(...$hasPropertyTypes);
340: }
341:
342: return [];
343: }
344:
345: /**
346: * @return AccessoryType[]
347: */
348: public static function getAccessoryTypes(Type $type): array
349: {
350: return self::map(AccessoryType::class, $type, true, false);
351: }
352:
353: /** @deprecated Use PHPStan\Type\Type::isCallable() instead. */
354: public static function containsCallable(Type $type): bool
355: {
356: if ($type->isCallable()->yes()) {
357: return true;
358: }
359:
360: if ($type instanceof UnionType) {
361: foreach ($type->getTypes() as $innerType) {
362: if ($innerType->isCallable()->yes()) {
363: return true;
364: }
365: }
366: }
367:
368: return false;
369: }
370:
371: public static function containsTemplateType(Type $type): bool
372: {
373: $containsTemplateType = false;
374: TypeTraverser::map($type, static function (Type $type, callable $traverse) use (&$containsTemplateType): Type {
375: if ($type instanceof TemplateType) {
376: $containsTemplateType = true;
377: }
378:
379: return $containsTemplateType ? $type : $traverse($type);
380: });
381:
382: return $containsTemplateType;
383: }
384:
385: public static function resolveLateResolvableTypes(Type $type, bool $resolveUnresolvableTypes = true): Type
386: {
387: return TypeTraverser::map($type, static function (Type $type, callable $traverse) use ($resolveUnresolvableTypes): Type {
388: $type = $traverse($type);
389:
390: if ($type instanceof LateResolvableType && ($resolveUnresolvableTypes || $type->isResolvable())) {
391: $type = $type->resolve();
392: }
393:
394: return $type;
395: });
396: }
397:
398: }
399: