1: <?php declare(strict_types = 1);
2:
3: namespace PHPStan\Type\Constant;
4:
5: use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprFloatNode;
6: use PHPStan\PhpDocParser\Ast\Type\ConstTypeNode;
7: use PHPStan\PhpDocParser\Ast\Type\TypeNode;
8: use PHPStan\TrinaryLogic;
9: use PHPStan\Type\CompoundType;
10: use PHPStan\Type\ConstantScalarType;
11: use PHPStan\Type\FloatType;
12: use PHPStan\Type\GeneralizePrecision;
13: use PHPStan\Type\Traits\ConstantNumericComparisonTypeTrait;
14: use PHPStan\Type\Traits\ConstantScalarTypeTrait;
15: use PHPStan\Type\Type;
16: use PHPStan\Type\VerbosityLevel;
17: use function abs;
18: use function ini_get;
19: use function ini_set;
20: use function is_finite;
21: use function str_contains;
22: use const PHP_FLOAT_EPSILON;
23:
24: /** @api */
25: class ConstantFloatType extends FloatType implements ConstantScalarType
26: {
27:
28: use ConstantScalarTypeTrait;
29: use ConstantScalarToBooleanTrait;
30: use ConstantNumericComparisonTypeTrait;
31:
32: /** @api */
33: public function __construct(private float $value)
34: {
35: parent::__construct();
36: }
37:
38: public function getValue(): float
39: {
40: return $this->value;
41: }
42:
43: public function equals(Type $type): bool
44: {
45: return $type instanceof self && abs($this->value - $type->value) < PHP_FLOAT_EPSILON;
46: }
47:
48: private function castFloatToString(float $value): string
49: {
50: $precisionBackup = ini_get('precision');
51: ini_set('precision', '-1');
52: try {
53: $valueStr = (string) $value;
54: if (is_finite($value) && !str_contains($valueStr, '.')) {
55: $valueStr .= '.0';
56: }
57:
58: return $valueStr;
59: } finally {
60: ini_set('precision', $precisionBackup);
61: }
62: }
63:
64: public function describe(VerbosityLevel $level): string
65: {
66: return $level->handle(
67: static fn (): string => 'float',
68: fn (): string => $this->castFloatToString($this->value),
69: );
70: }
71:
72: public function isSuperTypeOf(Type $type): TrinaryLogic
73: {
74: if ($type instanceof self) {
75: if (!$this->equals($type)) {
76: if (abs($this->value - $type->value) < PHP_FLOAT_EPSILON) {
77: return TrinaryLogic::createMaybe();
78: }
79:
80: return TrinaryLogic::createNo();
81: }
82:
83: return TrinaryLogic::createYes();
84: }
85:
86: if ($type instanceof parent) {
87: return TrinaryLogic::createMaybe();
88: }
89:
90: if ($type instanceof CompoundType) {
91: return $type->isSubTypeOf($this);
92: }
93:
94: return TrinaryLogic::createNo();
95: }
96:
97: public function toString(): Type
98: {
99: return new ConstantStringType((string) $this->value);
100: }
101:
102: public function toInteger(): Type
103: {
104: return new ConstantIntegerType((int) $this->value);
105: }
106:
107: public function toArrayKey(): Type
108: {
109: return new ConstantIntegerType((int) $this->value);
110: }
111:
112: public function generalize(GeneralizePrecision $precision): Type
113: {
114: return new FloatType();
115: }
116:
117: /**
118: * @return ConstTypeNode
119: */
120: public function toPhpDocNode(): TypeNode
121: {
122: return new ConstTypeNode(new ConstExprFloatNode($this->castFloatToString($this->value)));
123: }
124:
125: /**
126: * @param mixed[] $properties
127: */
128: public static function __set_state(array $properties): Type
129: {
130: return new self($properties['value']);
131: }
132:
133: }
134: