1: <?php declare(strict_types = 1);
2:
3: namespace PHPStan\Type\Enum;
4:
5: use PHPStan\Reflection\ClassMemberAccessAnswerer;
6: use PHPStan\Reflection\ClassReflection;
7: use PHPStan\Reflection\Php\EnumPropertyReflection;
8: use PHPStan\Reflection\Php\EnumUnresolvedPropertyPrototypeReflection;
9: use PHPStan\Reflection\Type\UnresolvedPropertyPrototypeReflection;
10: use PHPStan\ShouldNotHappenException;
11: use PHPStan\TrinaryLogic;
12: use PHPStan\Type\AcceptsResult;
13: use PHPStan\Type\CompoundType;
14: use PHPStan\Type\Constant\ConstantStringType;
15: use PHPStan\Type\GeneralizePrecision;
16: use PHPStan\Type\ObjectType;
17: use PHPStan\Type\Type;
18: use PHPStan\Type\VerbosityLevel;
19: use function sprintf;
20:
21: /** @api */
22: class EnumCaseObjectType extends ObjectType
23: {
24:
25: /** @api */
26: public function __construct(
27: string $className,
28: private string $enumCaseName,
29: ?ClassReflection $classReflection = null,
30: )
31: {
32: parent::__construct($className, null, $classReflection);
33: }
34:
35: public function getEnumCaseName(): string
36: {
37: return $this->enumCaseName;
38: }
39:
40: public function describe(VerbosityLevel $level): string
41: {
42: $parent = parent::describe($level);
43:
44: return sprintf('%s::%s', $parent, $this->enumCaseName);
45: }
46:
47: public function equals(Type $type): bool
48: {
49: if (!$type instanceof self) {
50: return false;
51: }
52:
53: return $this->getClassName() === $type->getClassName()
54: && $this->enumCaseName === $type->enumCaseName;
55: }
56:
57: public function accepts(Type $type, bool $strictTypes): TrinaryLogic
58: {
59: return $this->acceptsWithReason($type, $strictTypes)->result;
60: }
61:
62: public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult
63: {
64: return new AcceptsResult($this->isSuperTypeOf($type), []);
65: }
66:
67: public function isSuperTypeOf(Type $type): TrinaryLogic
68: {
69: if ($type instanceof self) {
70: return TrinaryLogic::createFromBoolean(
71: $this->getClassName() === $type->getClassName()
72: && $this->enumCaseName === $type->enumCaseName,
73: );
74: }
75:
76: if ($type instanceof CompoundType) {
77: return $type->isSubTypeOf($this);
78: }
79:
80: $parent = new parent($this->getClassName(), $this->getSubtractedType(), $this->getClassReflection());
81:
82: return $parent->isSuperTypeOf($type)->and(TrinaryLogic::createMaybe());
83: }
84:
85: public function subtract(Type $type): Type
86: {
87: return $this;
88: }
89:
90: public function getTypeWithoutSubtractedType(): Type
91: {
92: return $this;
93: }
94:
95: public function changeSubtractedType(?Type $subtractedType): Type
96: {
97: return $this;
98: }
99:
100: public function getSubtractedType(): ?Type
101: {
102: return null;
103: }
104:
105: public function tryRemove(Type $typeToRemove): ?Type
106: {
107: if ($this->isSuperTypeOf($typeToRemove)->yes()) {
108: return $this->subtract($typeToRemove);
109: }
110:
111: return null;
112: }
113:
114: public function getUnresolvedPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection
115: {
116: $classReflection = $this->getClassReflection();
117: if ($classReflection === null) {
118: return parent::getUnresolvedPropertyPrototype($propertyName, $scope);
119:
120: }
121: if ($propertyName === 'name') {
122: return new EnumUnresolvedPropertyPrototypeReflection(
123: new EnumPropertyReflection($classReflection, new ConstantStringType($this->enumCaseName)),
124: );
125: }
126:
127: if ($classReflection->isBackedEnum() && $propertyName === 'value') {
128: if ($classReflection->hasEnumCase($this->enumCaseName)) {
129: $enumCase = $classReflection->getEnumCase($this->enumCaseName);
130: $valueType = $enumCase->getBackingValueType();
131: if ($valueType === null) {
132: throw new ShouldNotHappenException();
133: }
134:
135: return new EnumUnresolvedPropertyPrototypeReflection(
136: new EnumPropertyReflection($classReflection, $valueType),
137: );
138: }
139: }
140:
141: return parent::getUnresolvedPropertyPrototype($propertyName, $scope);
142: }
143:
144: public function getBackingValueType(): ?Type
145: {
146: $classReflection = $this->getClassReflection();
147: if ($classReflection === null) {
148: return null;
149: }
150:
151: if (!$classReflection->isBackedEnum()) {
152: return null;
153: }
154:
155: if ($classReflection->hasEnumCase($this->enumCaseName)) {
156: $enumCase = $classReflection->getEnumCase($this->enumCaseName);
157:
158: return $enumCase->getBackingValueType();
159: }
160:
161: return null;
162: }
163:
164: public function generalize(GeneralizePrecision $precision): Type
165: {
166: return new parent($this->getClassName(), null, $this->getClassReflection());
167: }
168:
169: public function isSmallerThan(Type $otherType): TrinaryLogic
170: {
171: return TrinaryLogic::createNo();
172: }
173:
174: public function isSmallerThanOrEqual(Type $otherType): TrinaryLogic
175: {
176: return TrinaryLogic::createNo();
177: }
178:
179: public function getEnumCases(): array
180: {
181: return [$this];
182: }
183:
184: /**
185: * @param mixed[] $properties
186: */
187: public static function __set_state(array $properties): Type
188: {
189: return new self($properties['className'], $properties['enumCaseName'], null);
190: }
191:
192: }
193: