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