1: <?php declare(strict_types = 1);
2:
3: namespace PHPStan\Type;
4:
5: use PHPStan\Turbo\ShadowedByTurboExtension;
6: use PHPStan\Type\Accessory\AccessoryArrayListType;
7: use PHPStan\Type\Accessory\NonEmptyArrayType;
8: use PHPStan\Type\Accessory\OversizedArrayType;
9: use PHPStan\Type\Constant\ConstantArrayTypeBuilder;
10: use PHPStan\Type\Constant\ConstantBooleanType;
11: use PHPStan\Type\Constant\ConstantFloatType;
12: use PHPStan\Type\Constant\ConstantIntegerType;
13: use PHPStan\Type\Constant\ConstantStringType;
14: use PHPStan\Type\Enum\EnumCaseObjectType;
15: use UnitEnum;
16: use function array_is_list;
17: use function count;
18: use function function_exists;
19: use function get_class;
20: use function is_array;
21: use function is_bool;
22: use function is_float;
23: use function is_int;
24: use function is_object;
25: use function is_string;
26:
27: /**
28: * @api
29: */
30: #[ShadowedByTurboExtension(implementation: __DIR__ . '/../../turbo-ext/src/ConstantTypeHelper.cpp')]
31: final class ConstantTypeHelper
32: {
33:
34: /**
35: * @param mixed $value
36: */
37: public static function getTypeFromValue($value): Type
38: {
39: if (is_int($value)) {
40: return new ConstantIntegerType($value);
41: } elseif (is_float($value)) {
42: return new ConstantFloatType($value);
43: } elseif (is_bool($value)) {
44: return new ConstantBooleanType($value);
45: } elseif ($value === null) {
46: return new NullType();
47: } elseif (is_string($value)) {
48: return new ConstantStringType($value);
49: } elseif (is_array($value)) {
50: if (count($value) > ConstantArrayTypeBuilder::ARRAY_COUNT_LIMIT) {
51: return self::getOversizedArrayType($value);
52: }
53: $arrayBuilder = ConstantArrayTypeBuilder::createEmpty();
54: foreach ($value as $k => $v) {
55: $arrayBuilder->setOffsetValueType(self::getTypeFromValue($k), self::getTypeFromValue($v));
56: }
57: return $arrayBuilder->getArray();
58: } elseif (is_object($value)) {
59: $class = get_class($value);
60: /** phpcs:disable SlevomatCodingStandard.Namespaces.ReferenceUsedNamesOnly.ReferenceViaFullyQualifiedName */
61: if (function_exists('enum_exists') && \enum_exists($class)) {
62: /** @var UnitEnum $value */
63: return new EnumCaseObjectType($class, $value->name);
64: }
65: /** phpcs:enable */
66:
67: return new ObjectType(get_class($value));
68: }
69:
70: return new MixedType();
71: }
72:
73: /**
74: * Generalizes keys and values like OversizedArrayBuilder does for array literals,
75: * so that the result does not carry unions of thousands of constant types.
76: *
77: * @param non-empty-array<mixed> $value
78: */
79: private static function getOversizedArrayType(array $value): Type
80: {
81: $precision = GeneralizePrecision::moreSpecific();
82: $keyType = new NeverType();
83: $valueType = new NeverType();
84: foreach ($value as $k => $v) {
85: $keyType = self::unionGeneralized($keyType, self::getTypeFromValue($k), $precision);
86: $valueType = self::unionGeneralized($valueType, self::getTypeFromValue($v), $precision);
87: }
88:
89: $accessories = [new NonEmptyArrayType(), new OversizedArrayType()];
90: if (array_is_list($value)) {
91: $accessories[] = new AccessoryArrayListType();
92: }
93:
94: return TypeCombinator::intersect(new ArrayType($keyType, $valueType), ...$accessories);
95: }
96:
97: /** Skips the union when the generalized type is already the accumulated one, which is the common case. */
98: private static function unionGeneralized(Type $accumulated, Type $type, GeneralizePrecision $precision): Type
99: {
100: $generalized = $type->generalize($precision);
101: if ($generalized->equals($accumulated)) {
102: return $accumulated;
103: }
104:
105: return TypeCombinator::union($accumulated, $generalized);
106: }
107:
108: }
109: