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