| 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\Constant\ConstantStringType; | 
| 10: | use PHPStan\Type\Enum\EnumCaseObjectType; | 
| 11: | use PHPStan\Type\Generic\TemplateType; | 
| 12: | use function array_merge; | 
| 13: | use function array_unique; | 
| 14: | use function array_values; | 
| 15: |  | 
| 16: |  | 
| 17: | class TypeUtils | 
| 18: | { | 
| 19: |  | 
| 20: |  | 
| 21: |  | 
| 22: |  | 
| 23: |  | 
| 24: |  | 
| 25: | public static function getArrays(Type $type): array | 
| 26: | { | 
| 27: | if ($type instanceof ConstantArrayType) { | 
| 28: | return $type->getAllArrays(); | 
| 29: | } | 
| 30: |  | 
| 31: | if ($type instanceof ArrayType) { | 
| 32: | return [$type]; | 
| 33: | } | 
| 34: |  | 
| 35: | if ($type instanceof UnionType) { | 
| 36: | $matchingTypes = []; | 
| 37: | foreach ($type->getTypes() as $innerType) { | 
| 38: | if (!$innerType instanceof ArrayType) { | 
| 39: | return []; | 
| 40: | } | 
| 41: | foreach (self::getArrays($innerType) as $innerInnerType) { | 
| 42: | $matchingTypes[] = $innerInnerType; | 
| 43: | } | 
| 44: | } | 
| 45: |  | 
| 46: | return $matchingTypes; | 
| 47: | } | 
| 48: |  | 
| 49: | if ($type instanceof IntersectionType) { | 
| 50: | $matchingTypes = []; | 
| 51: | foreach ($type->getTypes() as $innerType) { | 
| 52: | if (!$innerType instanceof ArrayType) { | 
| 53: | continue; | 
| 54: | } | 
| 55: | foreach (self::getArrays($innerType) as $innerInnerType) { | 
| 56: | $matchingTypes[] = $innerInnerType; | 
| 57: | } | 
| 58: | } | 
| 59: |  | 
| 60: | return $matchingTypes; | 
| 61: | } | 
| 62: |  | 
| 63: | return []; | 
| 64: | } | 
| 65: |  | 
| 66: |  | 
| 67: |  | 
| 68: |  | 
| 69: |  | 
| 70: |  | 
| 71: | public static function getConstantArrays(Type $type): array | 
| 72: | { | 
| 73: | if ($type instanceof ConstantArrayType) { | 
| 74: | return $type->getAllArrays(); | 
| 75: | } | 
| 76: |  | 
| 77: | if ($type instanceof UnionType) { | 
| 78: | $matchingTypes = []; | 
| 79: | foreach ($type->getTypes() as $innerType) { | 
| 80: | if (!$innerType instanceof ConstantArrayType) { | 
| 81: | return []; | 
| 82: | } | 
| 83: | foreach (self::getConstantArrays($innerType) as $innerInnerType) { | 
| 84: | $matchingTypes[] = $innerInnerType; | 
| 85: | } | 
| 86: | } | 
| 87: |  | 
| 88: | return $matchingTypes; | 
| 89: | } | 
| 90: |  | 
| 91: | return []; | 
| 92: | } | 
| 93: |  | 
| 94: |  | 
| 95: |  | 
| 96: |  | 
| 97: |  | 
| 98: |  | 
| 99: | public static function getConstantStrings(Type $type): array | 
| 100: | { | 
| 101: | return self::map(ConstantStringType::class, $type, false); | 
| 102: | } | 
| 103: |  | 
| 104: |  | 
| 105: |  | 
| 106: |  | 
| 107: | public static function getConstantIntegers(Type $type): array | 
| 108: | { | 
| 109: | return self::map(ConstantIntegerType::class, $type, false); | 
| 110: | } | 
| 111: |  | 
| 112: |  | 
| 113: |  | 
| 114: |  | 
| 115: | public static function getConstantTypes(Type $type): array | 
| 116: | { | 
| 117: | return self::map(ConstantType::class, $type, false); | 
| 118: | } | 
| 119: |  | 
| 120: |  | 
| 121: |  | 
| 122: |  | 
| 123: | public static function getAnyConstantTypes(Type $type): array | 
| 124: | { | 
| 125: | return self::map(ConstantType::class, $type, false, false); | 
| 126: | } | 
| 127: |  | 
| 128: |  | 
| 129: |  | 
| 130: |  | 
| 131: |  | 
| 132: |  | 
| 133: | public static function getAnyArrays(Type $type): array | 
| 134: | { | 
| 135: | return self::map(ArrayType::class, $type, true, false); | 
| 136: | } | 
| 137: |  | 
| 138: |  | 
| 139: |  | 
| 140: |  | 
| 141: | public static function generalizeType(Type $type, GeneralizePrecision $precision): Type | 
| 142: | { | 
| 143: | return $type->generalize($precision); | 
| 144: | } | 
| 145: |  | 
| 146: |  | 
| 147: |  | 
| 148: |  | 
| 149: | public static function getDirectClassNames(Type $type): array | 
| 150: | { | 
| 151: | if ($type instanceof TypeWithClassName) { | 
| 152: | return [$type->getClassName()]; | 
| 153: | } | 
| 154: |  | 
| 155: | if ($type instanceof UnionType || $type instanceof IntersectionType) { | 
| 156: | $classNames = []; | 
| 157: | foreach ($type->getTypes() as $innerType) { | 
| 158: | foreach (self::getDirectClassNames($innerType) as $n) { | 
| 159: | $classNames[] = $n; | 
| 160: | } | 
| 161: | } | 
| 162: |  | 
| 163: | return array_values(array_unique($classNames)); | 
| 164: | } | 
| 165: |  | 
| 166: | return []; | 
| 167: | } | 
| 168: |  | 
| 169: |  | 
| 170: |  | 
| 171: |  | 
| 172: | public static function getIntegerRanges(Type $type): array | 
| 173: | { | 
| 174: | return self::map(IntegerRangeType::class, $type, false); | 
| 175: | } | 
| 176: |  | 
| 177: |  | 
| 178: |  | 
| 179: |  | 
| 180: | public static function getConstantScalars(Type $type): array | 
| 181: | { | 
| 182: | return self::map(ConstantScalarType::class, $type, false); | 
| 183: | } | 
| 184: |  | 
| 185: |  | 
| 186: |  | 
| 187: |  | 
| 188: | public static function getEnumCaseObjects(Type $type): array | 
| 189: | { | 
| 190: | return self::map(EnumCaseObjectType::class, $type, false); | 
| 191: | } | 
| 192: |  | 
| 193: |  | 
| 194: |  | 
| 195: |  | 
| 196: |  | 
| 197: |  | 
| 198: |  | 
| 199: | public static function getOldConstantArrays(Type $type): array | 
| 200: | { | 
| 201: | return self::map(ConstantArrayType::class, $type, false); | 
| 202: | } | 
| 203: |  | 
| 204: |  | 
| 205: |  | 
| 206: |  | 
| 207: | private static function map( | 
| 208: | string $typeClass, | 
| 209: | Type $type, | 
| 210: | bool $inspectIntersections, | 
| 211: | bool $stopOnUnmatched = true, | 
| 212: | ): array | 
| 213: | { | 
| 214: | if ($type instanceof $typeClass) { | 
| 215: | return [$type]; | 
| 216: | } | 
| 217: |  | 
| 218: | if ($type instanceof UnionType) { | 
| 219: | $matchingTypes = []; | 
| 220: | foreach ($type->getTypes() as $innerType) { | 
| 221: | $matchingInner = self::map($typeClass, $innerType, $inspectIntersections, $stopOnUnmatched); | 
| 222: |  | 
| 223: | if ($matchingInner === []) { | 
| 224: | if ($stopOnUnmatched) { | 
| 225: | return []; | 
| 226: | } | 
| 227: |  | 
| 228: | continue; | 
| 229: | } | 
| 230: |  | 
| 231: | foreach ($matchingInner as $innerMapped) { | 
| 232: | $matchingTypes[] = $innerMapped; | 
| 233: | } | 
| 234: | } | 
| 235: |  | 
| 236: | return $matchingTypes; | 
| 237: | } | 
| 238: |  | 
| 239: | if ($inspectIntersections && $type instanceof IntersectionType) { | 
| 240: | $matchingTypes = []; | 
| 241: | foreach ($type->getTypes() as $innerType) { | 
| 242: | if (!$innerType instanceof $typeClass) { | 
| 243: | if ($stopOnUnmatched) { | 
| 244: | return []; | 
| 245: | } | 
| 246: |  | 
| 247: | continue; | 
| 248: | } | 
| 249: |  | 
| 250: | $matchingTypes[] = $innerType; | 
| 251: | } | 
| 252: |  | 
| 253: | return $matchingTypes; | 
| 254: | } | 
| 255: |  | 
| 256: | return []; | 
| 257: | } | 
| 258: |  | 
| 259: | public static function toBenevolentUnion(Type $type): Type | 
| 260: | { | 
| 261: | if ($type instanceof BenevolentUnionType) { | 
| 262: | return $type; | 
| 263: | } | 
| 264: |  | 
| 265: | if ($type instanceof UnionType) { | 
| 266: | return new BenevolentUnionType($type->getTypes()); | 
| 267: | } | 
| 268: |  | 
| 269: | return $type; | 
| 270: | } | 
| 271: |  | 
| 272: |  | 
| 273: |  | 
| 274: |  | 
| 275: | public static function flattenTypes(Type $type): array | 
| 276: | { | 
| 277: | if ($type instanceof ConstantArrayType) { | 
| 278: | return $type->getAllArrays(); | 
| 279: | } | 
| 280: |  | 
| 281: | if ($type instanceof UnionType) { | 
| 282: | $types = []; | 
| 283: | foreach ($type->getTypes() as $innerType) { | 
| 284: | if ($innerType instanceof ConstantArrayType) { | 
| 285: | foreach ($innerType->getAllArrays() as $array) { | 
| 286: | $types[] = $array; | 
| 287: | } | 
| 288: | continue; | 
| 289: | } | 
| 290: |  | 
| 291: | $types[] = $innerType; | 
| 292: | } | 
| 293: |  | 
| 294: | return $types; | 
| 295: | } | 
| 296: |  | 
| 297: | return [$type]; | 
| 298: | } | 
| 299: |  | 
| 300: | public static function findThisType(Type $type): ?ThisType | 
| 301: | { | 
| 302: | if ($type instanceof ThisType) { | 
| 303: | return $type; | 
| 304: | } | 
| 305: |  | 
| 306: | if ($type instanceof UnionType || $type instanceof IntersectionType) { | 
| 307: | foreach ($type->getTypes() as $innerType) { | 
| 308: | $thisType = self::findThisType($innerType); | 
| 309: | if ($thisType !== null) { | 
| 310: | return $thisType; | 
| 311: | } | 
| 312: | } | 
| 313: | } | 
| 314: |  | 
| 315: | return null; | 
| 316: | } | 
| 317: |  | 
| 318: |  | 
| 319: |  | 
| 320: |  | 
| 321: | public static function getHasPropertyTypes(Type $type): array | 
| 322: | { | 
| 323: | if ($type instanceof HasPropertyType) { | 
| 324: | return [$type]; | 
| 325: | } | 
| 326: |  | 
| 327: | if ($type instanceof UnionType || $type instanceof IntersectionType) { | 
| 328: | $hasPropertyTypes = [[]]; | 
| 329: | foreach ($type->getTypes() as $innerType) { | 
| 330: | $hasPropertyTypes[] = self::getHasPropertyTypes($innerType); | 
| 331: | } | 
| 332: |  | 
| 333: | return array_merge(...$hasPropertyTypes); | 
| 334: | } | 
| 335: |  | 
| 336: | return []; | 
| 337: | } | 
| 338: |  | 
| 339: |  | 
| 340: |  | 
| 341: |  | 
| 342: | public static function getAccessoryTypes(Type $type): array | 
| 343: | { | 
| 344: | return self::map(AccessoryType::class, $type, true, false); | 
| 345: | } | 
| 346: |  | 
| 347: |  | 
| 348: | public static function containsCallable(Type $type): bool | 
| 349: | { | 
| 350: | if ($type->isCallable()->yes()) { | 
| 351: | return true; | 
| 352: | } | 
| 353: |  | 
| 354: | if ($type instanceof UnionType) { | 
| 355: | foreach ($type->getTypes() as $innerType) { | 
| 356: | if ($innerType->isCallable()->yes()) { | 
| 357: | return true; | 
| 358: | } | 
| 359: | } | 
| 360: | } | 
| 361: |  | 
| 362: | return false; | 
| 363: | } | 
| 364: |  | 
| 365: | public static function containsTemplateType(Type $type): bool | 
| 366: | { | 
| 367: | $containsTemplateType = false; | 
| 368: | TypeTraverser::map($type, static function (Type $type, callable $traverse) use (&$containsTemplateType): Type { | 
| 369: | if ($type instanceof TemplateType) { | 
| 370: | $containsTemplateType = true; | 
| 371: | } | 
| 372: |  | 
| 373: | return $containsTemplateType ? $type : $traverse($type); | 
| 374: | }); | 
| 375: |  | 
| 376: | return $containsTemplateType; | 
| 377: | } | 
| 378: |  | 
| 379: | public static function resolveLateResolvableTypes(Type $type, bool $resolveUnresolvableTypes = true): Type | 
| 380: | { | 
| 381: | return TypeTraverser::map($type, static function (Type $type, callable $traverse) use ($resolveUnresolvableTypes): Type { | 
| 382: | $type = $traverse($type); | 
| 383: |  | 
| 384: | if ($type instanceof LateResolvableType && ($resolveUnresolvableTypes || $type->isResolvable())) { | 
| 385: | $type = $type->resolve(); | 
| 386: | } | 
| 387: |  | 
| 388: | return $type; | 
| 389: | }); | 
| 390: | } | 
| 391: |  | 
| 392: | } | 
| 393: |  |