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