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: /**
174: * @return HasPropertyType[]
175: */
176: public static function getHasPropertyTypes(Type $type): array
177: {
178: if ($type instanceof HasPropertyType) {
179: return [$type];
180: }
181:
182: if ($type instanceof UnionType || $type instanceof IntersectionType) {
183: $hasPropertyTypes = [[]];
184: foreach ($type->getTypes() as $innerType) {
185: $hasPropertyTypes[] = self::getHasPropertyTypes($innerType);
186: }
187:
188: return array_merge(...$hasPropertyTypes);
189: }
190:
191: return [];
192: }
193:
194: /**
195: * @return AccessoryType[]
196: */
197: public static function getAccessoryTypes(Type $type): array
198: {
199: return self::map(AccessoryType::class, $type, true, false);
200: }
201:
202: public static function containsTemplateType(Type $type): bool
203: {
204: $containsTemplateType = false;
205: TypeTraverser::map($type, static function (Type $type, callable $traverse) use (&$containsTemplateType): Type {
206: if ($type instanceof TemplateType) {
207: $containsTemplateType = true;
208: }
209:
210: return $containsTemplateType ? $type : $traverse($type);
211: });
212:
213: return $containsTemplateType;
214: }
215:
216: public static function resolveLateResolvableTypes(Type $type, bool $resolveUnresolvableTypes = true): Type
217: {
218: /** @var int $ignoreResolveUnresolvableTypesLevel */
219: $ignoreResolveUnresolvableTypesLevel = 0;
220:
221: return TypeTraverser::map($type, static function (Type $type, callable $traverse) use ($resolveUnresolvableTypes, &$ignoreResolveUnresolvableTypesLevel): Type {
222: while ($type instanceof LateResolvableType && (($resolveUnresolvableTypes && $ignoreResolveUnresolvableTypesLevel === 0) || $type->isResolvable())) {
223: $type = $type->resolve();
224: }
225:
226: if ($type instanceof CallableType || $type instanceof ClosureType) {
227: $ignoreResolveUnresolvableTypesLevel++;
228: $result = $traverse($type);
229: $ignoreResolveUnresolvableTypesLevel--;
230:
231: return $result;
232: }
233:
234: return $traverse($type);
235: });
236: }
237:
238: }
239: