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