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