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