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