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