1: <?php declare(strict_types = 1);
2:
3: namespace PHPStan\Type;
4:
5: use PHPStan\Internal\CombinationsHelper;
6: use PHPStan\Turbo\ShadowedByTurboExtension;
7: use PHPStan\Type\Accessory\AccessoryType;
8: use PHPStan\Type\Accessory\HasPropertyType;
9: use PHPStan\Type\Constant\ConstantIntegerType;
10: use PHPStan\Type\Generic\TemplateBenevolentUnionType;
11: use PHPStan\Type\Generic\TemplateType;
12: use PHPStan\Type\Generic\TemplateUnionType;
13: use PHPStan\Type\Traverser\LateResolvableTraverser;
14: use function array_merge;
15: use function count;
16: use function max;
17: use const PHP_INT_MAX;
18:
19: /**
20: * @api
21: */
22: #[ShadowedByTurboExtension(implementation: __DIR__ . '/../../turbo-ext/src/TypeUtils.cpp')]
23: final class TypeUtils
24: {
25:
26: /**
27: * @return list<ConstantIntegerType>
28: */
29: public static function getConstantIntegers(Type $type): array
30: {
31: return self::map(ConstantIntegerType::class, $type, false);
32: }
33:
34: /**
35: * @return list<IntegerRangeType>
36: */
37: public static function getIntegerRanges(Type $type): array
38: {
39: return self::map(IntegerRangeType::class, $type, false);
40: }
41:
42: /**
43: * @return list<mixed>
44: */
45: private static function map(
46: string $typeClass,
47: Type $type,
48: bool $inspectIntersections,
49: bool $stopOnUnmatched = true,
50: ): array
51: {
52: if ($type instanceof $typeClass) {
53: return [$type];
54: }
55:
56: if ($type instanceof UnionType) {
57: $matchingTypes = [];
58: foreach ($type->getTypes() as $innerType) {
59: $matchingInner = self::map($typeClass, $innerType, $inspectIntersections, $stopOnUnmatched);
60:
61: if ($matchingInner === []) {
62: if ($stopOnUnmatched) {
63: return [];
64: }
65:
66: continue;
67: }
68:
69: foreach ($matchingInner as $innerMapped) {
70: $matchingTypes[] = $innerMapped;
71: }
72: }
73:
74: return $matchingTypes;
75: }
76:
77: if ($inspectIntersections && $type instanceof IntersectionType) {
78: $matchingTypes = [];
79: foreach ($type->getTypes() as $innerType) {
80: if (!$innerType instanceof $typeClass) {
81: if ($stopOnUnmatched) {
82: return [];
83: }
84:
85: continue;
86: }
87:
88: $matchingTypes[] = $innerType;
89: }
90:
91: return $matchingTypes;
92: }
93:
94: return [];
95: }
96:
97: public static function toBenevolentUnion(Type $type): Type
98: {
99: if ($type instanceof BenevolentUnionType) {
100: return $type;
101: }
102:
103: if ($type instanceof UnionType) {
104: return new BenevolentUnionType($type->getTypes());
105: }
106:
107: return $type;
108: }
109:
110: /**
111: * @return ($type is UnionType ? UnionType : Type)
112: */
113: public static function toStrictUnion(Type $type): Type
114: {
115: if ($type instanceof TemplateBenevolentUnionType) {
116: return new TemplateUnionType(
117: $type->getScope(),
118: $type->getStrategy(),
119: $type->getVariance(),
120: $type->getName(),
121: static::toStrictUnion($type->getBound()),
122: $type->getDefault(),
123: );
124: }
125:
126: if ($type instanceof BenevolentUnionType) {
127: return new UnionType($type->getTypes());
128: }
129:
130: return $type;
131: }
132:
133: /**
134: * @return Type[]
135: */
136: public static function flattenTypes(Type $type): array
137: {
138: if ($type instanceof UnionType) {
139: $types = [];
140: foreach ($type->getTypes() as $innerType) {
141: $flattenTypes = self::flattenTypes($innerType);
142: foreach ($flattenTypes as $flattenType) {
143: $types[] = $flattenType;
144: }
145: }
146:
147: return $types;
148: }
149:
150: $constantArrays = $type->getConstantArrays();
151: if ($constantArrays !== []) {
152: // Estimate the total number of power-set variants before expanding.
153: // Each ConstantArrayType with N optional keys produces 2^N variants
154: // from getAllArrays(). The cartesian product across multiple constant
155: // arrays multiplies these counts. Bail out to avoid O(2^N) allocation
156: // when the total would be large.
157: $estimatedCount = 1;
158: $bail = false;
159: foreach ($constantArrays as $constantArray) {
160: $optionalCount = count($constantArray->getOptionalKeys());
161: $arrayCount = $optionalCount <= 20 ? (1 << $optionalCount) : PHP_INT_MAX;
162: if ($arrayCount > 16384 || $estimatedCount > 16384 / max($arrayCount, 1)) {
163: $bail = true;
164: break;
165: }
166: $estimatedCount *= $arrayCount;
167: }
168:
169: if ($bail) {
170: return [$type];
171: }
172:
173: $newTypes = [];
174: foreach ($constantArrays as $constantArray) {
175: $newTypes[] = $constantArray->getAllArrays();
176: }
177:
178: $result = [];
179: foreach (CombinationsHelper::combinations($newTypes) as $combination) {
180: $intersected = $combination[0];
181: for ($i = 1, $count = count($combination); $i < $count; $i++) {
182: $intersected = TypeCombinator::intersect($intersected, $combination[$i]);
183: }
184: if ($intersected instanceof NeverType) {
185: continue;
186: }
187:
188: $result[] = $intersected;
189: }
190:
191: return $result;
192: }
193:
194: return [$type];
195: }
196:
197: public static function findThisType(Type $type): ?ThisType
198: {
199: if ($type instanceof ThisType) {
200: return $type;
201: }
202:
203: if ($type instanceof UnionType || $type instanceof IntersectionType) {
204: foreach ($type->getTypes() as $innerType) {
205: $thisType = self::findThisType($innerType);
206: if ($thisType !== null) {
207: return $thisType;
208: }
209: }
210: }
211:
212: return null;
213: }
214:
215: public static function findCallableType(Type $type): ?Type
216: {
217: if ($type->isCallable()->yes()) {
218: return $type;
219: }
220:
221: if ($type instanceof UnionType) {
222: foreach ($type->getTypes() as $innerType) {
223: $callableType = self::findCallableType($innerType);
224: if ($callableType !== null) {
225: return $callableType;
226: }
227: }
228: }
229:
230: return null;
231: }
232:
233: /**
234: * @return HasPropertyType[]
235: */
236: public static function getHasPropertyTypes(Type $type): array
237: {
238: if ($type instanceof HasPropertyType) {
239: return [$type];
240: }
241:
242: if ($type instanceof UnionType || $type instanceof IntersectionType) {
243: $hasPropertyTypes = [[]];
244: foreach ($type->getTypes() as $innerType) {
245: $hasPropertyTypes[] = self::getHasPropertyTypes($innerType);
246: }
247:
248: return array_merge(...$hasPropertyTypes);
249: }
250:
251: return [];
252: }
253:
254: /**
255: * @return list<AccessoryType>
256: */
257: public static function getAccessoryTypes(Type $type): array
258: {
259: return self::map(AccessoryType::class, $type, inspectIntersections: true, stopOnUnmatched: false);
260: }
261:
262: public static function containsTemplateType(Type $type): bool
263: {
264: $containsTemplateType = false;
265: TypeTraverser::map($type, static function (Type $type, callable $traverse) use (&$containsTemplateType): Type {
266: if ($type instanceof TemplateType) {
267: $containsTemplateType = true;
268: }
269:
270: return $containsTemplateType ? $type : $traverse($type);
271: });
272:
273: return $containsTemplateType;
274: }
275:
276: public static function resolveLateResolvableTypes(Type $type, bool $resolveUnresolvableTypes = true): Type
277: {
278: if (!$type->hasTemplateOrLateResolvableType()) {
279: return $type;
280: }
281:
282: return TypeTraverser::map($type, new LateResolvableTraverser($resolveUnresolvableTypes));
283: }
284:
285: }
286: