1: <?php declare(strict_types = 1);
2:
3: namespace PHPStan\Type;
4:
5: use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
6: use PHPStan\PhpDocParser\Ast\Type\TypeNode;
7: use PHPStan\TrinaryLogic;
8: use PHPStan\Type\Traits\NonGeneralizableTypeTrait;
9: use PHPStan\Type\Traits\NonGenericTypeTrait;
10: use PHPStan\Type\Traits\ObjectTypeTrait;
11: use PHPStan\Type\Traits\UndecidedComparisonTypeTrait;
12: use function sprintf;
13:
14: /** @api */
15: class ObjectWithoutClassType implements SubtractableType
16: {
17:
18: use ObjectTypeTrait;
19: use NonGenericTypeTrait;
20: use UndecidedComparisonTypeTrait;
21: use NonGeneralizableTypeTrait;
22:
23: private ?Type $subtractedType;
24:
25: /** @api */
26: public function __construct(
27: ?Type $subtractedType = null,
28: )
29: {
30: if ($subtractedType instanceof NeverType) {
31: $subtractedType = null;
32: }
33:
34: $this->subtractedType = $subtractedType;
35: }
36:
37: /**
38: * @return string[]
39: */
40: public function getReferencedClasses(): array
41: {
42: return [];
43: }
44:
45: public function getObjectClassNames(): array
46: {
47: return [];
48: }
49:
50: public function getObjectClassReflections(): array
51: {
52: return [];
53: }
54:
55: public function accepts(Type $type, bool $strictTypes): TrinaryLogic
56: {
57: return $this->acceptsWithReason($type, $strictTypes)->result;
58: }
59:
60: public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult
61: {
62: if ($type instanceof CompoundType) {
63: return $type->isAcceptedWithReasonBy($this, $strictTypes);
64: }
65:
66: return AcceptsResult::createFromBoolean(
67: $type instanceof self || $type instanceof ObjectShapeType || $type->getObjectClassNames() !== [],
68: );
69: }
70:
71: public function isSuperTypeOf(Type $type): TrinaryLogic
72: {
73: if ($type instanceof CompoundType) {
74: return $type->isSubTypeOf($this);
75: }
76:
77: if ($type instanceof self) {
78: if ($this->subtractedType === null) {
79: return TrinaryLogic::createYes();
80: }
81: if ($type->subtractedType !== null) {
82: $isSuperType = $type->subtractedType->isSuperTypeOf($this->subtractedType);
83: if ($isSuperType->yes()) {
84: return TrinaryLogic::createYes();
85: }
86: }
87:
88: return TrinaryLogic::createMaybe();
89: }
90:
91: if ($type instanceof ObjectShapeType) {
92: return TrinaryLogic::createYes();
93: }
94:
95: if ($type->getObjectClassNames() === []) {
96: return TrinaryLogic::createNo();
97: }
98:
99: if ($this->subtractedType === null) {
100: return TrinaryLogic::createYes();
101: }
102:
103: return $this->subtractedType->isSuperTypeOf($type)->negate();
104: }
105:
106: public function equals(Type $type): bool
107: {
108: if (!$type instanceof self) {
109: return false;
110: }
111:
112: if ($this->subtractedType === null) {
113: if ($type->subtractedType === null) {
114: return true;
115: }
116:
117: return false;
118: }
119:
120: if ($type->subtractedType === null) {
121: return false;
122: }
123:
124: return $this->subtractedType->equals($type->subtractedType);
125: }
126:
127: public function describe(VerbosityLevel $level): string
128: {
129: return $level->handle(
130: static fn (): string => 'object',
131: static fn (): string => 'object',
132: function () use ($level): string {
133: $description = 'object';
134: if ($this->subtractedType !== null) {
135: $description .= sprintf('~%s', $this->subtractedType->describe($level));
136: }
137:
138: return $description;
139: },
140: );
141: }
142:
143: public function isOffsetAccessLegal(): TrinaryLogic
144: {
145: return TrinaryLogic::createMaybe();
146: }
147:
148: public function getEnumCases(): array
149: {
150: return [];
151: }
152:
153: public function subtract(Type $type): Type
154: {
155: if ($type instanceof self) {
156: return new NeverType();
157: }
158: if ($this->subtractedType !== null) {
159: $type = TypeCombinator::union($this->subtractedType, $type);
160: }
161:
162: return new self($type);
163: }
164:
165: public function getTypeWithoutSubtractedType(): Type
166: {
167: return new self();
168: }
169:
170: public function changeSubtractedType(?Type $subtractedType): Type
171: {
172: return new self($subtractedType);
173: }
174:
175: public function getSubtractedType(): ?Type
176: {
177: return $this->subtractedType;
178: }
179:
180: public function traverse(callable $cb): Type
181: {
182: $subtractedType = $this->subtractedType !== null ? $cb($this->subtractedType) : null;
183:
184: if ($subtractedType !== $this->subtractedType) {
185: return new self($subtractedType);
186: }
187:
188: return $this;
189: }
190:
191: public function traverseSimultaneously(Type $right, callable $cb): Type
192: {
193: if ($this->subtractedType === null) {
194: return $this;
195: }
196:
197: return new self();
198: }
199:
200: public function tryRemove(Type $typeToRemove): ?Type
201: {
202: if ($this->isSuperTypeOf($typeToRemove)->yes()) {
203: return $this->subtract($typeToRemove);
204: }
205:
206: return null;
207: }
208:
209: public function exponentiate(Type $exponent): Type
210: {
211: if (!$exponent instanceof NeverType && !$this->isSuperTypeOf($exponent)->no()) {
212: return TypeCombinator::union($this, $exponent);
213: }
214:
215: return new BenevolentUnionType([
216: new FloatType(),
217: new IntegerType(),
218: ]);
219: }
220:
221: public function getFiniteTypes(): array
222: {
223: return [];
224: }
225:
226: public function toPhpDocNode(): TypeNode
227: {
228: return new IdentifierTypeNode('object');
229: }
230:
231: /**
232: * @param mixed[] $properties
233: */
234: public static function __set_state(array $properties): Type
235: {
236: return new self($properties['subtractedType'] ?? null);
237: }
238:
239: }
240: