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\Generic\TemplateBenevolentUnionType;
10: use PHPStan\Type\Generic\TemplateType;
11: use PHPStan\Type\Generic\TemplateUnionType;
12: use function array_merge;
13:
14: /**
15: * @api
16: */
17: final class TypeUtils
18: {
19:
20: /**
21: * @return list<ConstantIntegerType>
22: */
23: public static function getConstantIntegers(Type $type): array
24: {
25: return self::map(ConstantIntegerType::class, $type, false);
26: }
27:
28: /**
29: * @return list<IntegerRangeType>
30: */
31: public static function getIntegerRanges(Type $type): array
32: {
33: return self::map(IntegerRangeType::class, $type, false);
34: }
35:
36: /**
37: * @return list<mixed>
38: */
39: private static function map(
40: string $typeClass,
41: Type $type,
42: bool $inspectIntersections,
43: bool $stopOnUnmatched = true,
44: ): array
45: {
46: if ($type instanceof $typeClass) {
47: return [$type];
48: }
49:
50: if ($type instanceof UnionType) {
51: $matchingTypes = [];
52: foreach ($type->getTypes() as $innerType) {
53: $matchingInner = self::map($typeClass, $innerType, $inspectIntersections, $stopOnUnmatched);
54:
55: if ($matchingInner === []) {
56: if ($stopOnUnmatched) {
57: return [];
58: }
59:
60: continue;
61: }
62:
63: foreach ($matchingInner as $innerMapped) {
64: $matchingTypes[] = $innerMapped;
65: }
66: }
67:
68: return $matchingTypes;
69: }
70:
71: if ($inspectIntersections && $type instanceof IntersectionType) {
72: $matchingTypes = [];
73: foreach ($type->getTypes() as $innerType) {
74: if (!$innerType instanceof $typeClass) {
75: if ($stopOnUnmatched) {
76: return [];
77: }
78:
79: continue;
80: }
81:
82: $matchingTypes[] = $innerType;
83: }
84:
85: return $matchingTypes;
86: }
87:
88: return [];
89: }
90:
91: public static function toBenevolentUnion(Type $type): Type
92: {
93: if ($type instanceof BenevolentUnionType) {
94: return $type;
95: }
96:
97: if ($type instanceof UnionType) {
98: return new BenevolentUnionType($type->getTypes());
99: }
100:
101: return $type;
102: }
103:
104: /**
105: * @return ($type is UnionType ? UnionType : Type)
106: */
107: public static function toStrictUnion(Type $type): Type
108: {
109: if ($type instanceof TemplateBenevolentUnionType) {
110: return new TemplateUnionType(
111: $type->getScope(),
112: $type->getStrategy(),
113: $type->getVariance(),
114: $type->getName(),
115: static::toStrictUnion($type->getBound()),
116: $type->getDefault(),
117: );
118: }
119:
120: if ($type instanceof BenevolentUnionType) {
121: return new UnionType($type->getTypes());
122: }
123:
124: return $type;
125: }
126:
127: /**
128: * @return Type[]
129: */
130: public static function flattenTypes(Type $type): array
131: {
132: if ($type instanceof ConstantArrayType) {
133: return $type->getAllArrays();
134: }
135:
136: if ($type instanceof UnionType) {
137: $types = [];
138: foreach ($type->getTypes() as $innerType) {
139: if ($innerType instanceof ConstantArrayType) {
140: foreach ($innerType->getAllArrays() as $array) {
141: $types[] = $array;
142: }
143: continue;
144: }
145:
146: $types[] = $innerType;
147: }
148:
149: return $types;
150: }
151:
152: return [$type];
153: }
154:
155: public static function findThisType(Type $type): ?ThisType
156: {
157: if ($type instanceof ThisType) {
158: return $type;
159: }
160:
161: if ($type instanceof UnionType || $type instanceof IntersectionType) {
162: foreach ($type->getTypes() as $innerType) {
163: $thisType = self::findThisType($innerType);
164: if ($thisType !== null) {
165: return $thisType;
166: }
167: }
168: }
169:
170: return null;
171: }
172:
173: public static function findCallableType(Type $type): ?Type
174: {
175: if ($type->isCallable()->yes()) {
176: return $type;
177: }
178:
179: if ($type instanceof UnionType) {
180: foreach ($type->getTypes() as $innerType) {
181: $callableType = self::findCallableType($innerType);
182: if ($callableType !== null) {
183: return $callableType;
184: }
185: }
186: }
187:
188: return null;
189: }
190:
191: /**
192: * @return HasPropertyType[]
193: */
194: public static function getHasPropertyTypes(Type $type): array
195: {
196: if ($type instanceof HasPropertyType) {
197: return [$type];
198: }
199:
200: if ($type instanceof UnionType || $type instanceof IntersectionType) {
201: $hasPropertyTypes = [[]];
202: foreach ($type->getTypes() as $innerType) {
203: $hasPropertyTypes[] = self::getHasPropertyTypes($innerType);
204: }
205:
206: return array_merge(...$hasPropertyTypes);
207: }
208:
209: return [];
210: }
211:
212: /**
213: * @return AccessoryType[]
214: */
215: public static function getAccessoryTypes(Type $type): array
216: {
217: return self::map(AccessoryType::class, $type, true, false);
218: }
219:
220: public static function containsTemplateType(Type $type): bool
221: {
222: $containsTemplateType = false;
223: TypeTraverser::map($type, static function (Type $type, callable $traverse) use (&$containsTemplateType): Type {
224: if ($type instanceof TemplateType) {
225: $containsTemplateType = true;
226: }
227:
228: return $containsTemplateType ? $type : $traverse($type);
229: });
230:
231: return $containsTemplateType;
232: }
233:
234: public static function resolveLateResolvableTypes(Type $type, bool $resolveUnresolvableTypes = true): Type
235: {
236: /** @var int $ignoreResolveUnresolvableTypesLevel */
237: $ignoreResolveUnresolvableTypesLevel = 0;
238:
239: return TypeTraverser::map($type, static function (Type $type, callable $traverse) use ($resolveUnresolvableTypes, &$ignoreResolveUnresolvableTypesLevel): Type {
240: while ($type instanceof LateResolvableType && (($resolveUnresolvableTypes && $ignoreResolveUnresolvableTypesLevel === 0) || $type->isResolvable())) {
241: $type = $type->resolve();
242: }
243:
244: if ($type instanceof CallableType || $type instanceof ClosureType) {
245: $ignoreResolveUnresolvableTypesLevel++;
246: $result = $traverse($type);
247: $ignoreResolveUnresolvableTypesLevel--;
248:
249: return $result;
250: }
251:
252: return $traverse($type);
253: });
254: }
255:
256: }
257: