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_filter;
14: use function array_map;
15: use function array_merge;
16: use function iterator_to_array;
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: $newTypes = [];
151: foreach ($constantArrays as $constantArray) {
152: $newTypes[] = $constantArray->getAllArrays();
153: }
154:
155: return array_filter(
156: array_map(
157: static fn (array $types): Type => TypeCombinator::intersect(...$types),
158: iterator_to_array(CombinationsHelper::combinations($newTypes)),
159: ),
160: static fn (Type $type): bool => !$type instanceof NeverType,
161: );
162: }
163:
164: return [$type];
165: }
166:
167: public static function findThisType(Type $type): ?ThisType
168: {
169: if ($type instanceof ThisType) {
170: return $type;
171: }
172:
173: if ($type instanceof UnionType || $type instanceof IntersectionType) {
174: foreach ($type->getTypes() as $innerType) {
175: $thisType = self::findThisType($innerType);
176: if ($thisType !== null) {
177: return $thisType;
178: }
179: }
180: }
181:
182: return null;
183: }
184:
185: public static function findCallableType(Type $type): ?Type
186: {
187: if ($type->isCallable()->yes()) {
188: return $type;
189: }
190:
191: if ($type instanceof UnionType) {
192: foreach ($type->getTypes() as $innerType) {
193: $callableType = self::findCallableType($innerType);
194: if ($callableType !== null) {
195: return $callableType;
196: }
197: }
198: }
199:
200: return null;
201: }
202:
203: /**
204: * @return HasPropertyType[]
205: */
206: public static function getHasPropertyTypes(Type $type): array
207: {
208: if ($type instanceof HasPropertyType) {
209: return [$type];
210: }
211:
212: if ($type instanceof UnionType || $type instanceof IntersectionType) {
213: $hasPropertyTypes = [[]];
214: foreach ($type->getTypes() as $innerType) {
215: $hasPropertyTypes[] = self::getHasPropertyTypes($innerType);
216: }
217:
218: return array_merge(...$hasPropertyTypes);
219: }
220:
221: return [];
222: }
223:
224: /**
225: * @return list<AccessoryType>
226: */
227: public static function getAccessoryTypes(Type $type): array
228: {
229: return self::map(AccessoryType::class, $type, inspectIntersections: true, stopOnUnmatched: false);
230: }
231:
232: public static function containsTemplateType(Type $type): bool
233: {
234: $containsTemplateType = false;
235: TypeTraverser::map($type, static function (Type $type, callable $traverse) use (&$containsTemplateType): Type {
236: if ($type instanceof TemplateType) {
237: $containsTemplateType = true;
238: }
239:
240: return $containsTemplateType ? $type : $traverse($type);
241: });
242:
243: return $containsTemplateType;
244: }
245:
246: public static function resolveLateResolvableTypes(Type $type, bool $resolveUnresolvableTypes = true): Type
247: {
248: if (!$type->hasTemplateOrLateResolvableType()) {
249: return $type;
250: }
251:
252: return TypeTraverser::map($type, new LateResolvableTraverser($resolveUnresolvableTypes));
253: }
254:
255: }
256: