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