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