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 getEnumCases(): array
144: {
145: return [];
146: }
147:
148: public function subtract(Type $type): Type
149: {
150: if ($type instanceof self) {
151: return new NeverType();
152: }
153: if ($this->subtractedType !== null) {
154: $type = TypeCombinator::union($this->subtractedType, $type);
155: }
156:
157: return new self($type);
158: }
159:
160: public function getTypeWithoutSubtractedType(): Type
161: {
162: return new self();
163: }
164:
165: public function changeSubtractedType(?Type $subtractedType): Type
166: {
167: return new self($subtractedType);
168: }
169:
170: public function getSubtractedType(): ?Type
171: {
172: return $this->subtractedType;
173: }
174:
175: public function traverse(callable $cb): Type
176: {
177: $subtractedType = $this->subtractedType !== null ? $cb($this->subtractedType) : null;
178:
179: if ($subtractedType !== $this->subtractedType) {
180: return new self($subtractedType);
181: }
182:
183: return $this;
184: }
185:
186: public function traverseSimultaneously(Type $right, callable $cb): Type
187: {
188: if ($this->subtractedType === null) {
189: return $this;
190: }
191:
192: return new self();
193: }
194:
195: public function tryRemove(Type $typeToRemove): ?Type
196: {
197: if ($this->isSuperTypeOf($typeToRemove)->yes()) {
198: return $this->subtract($typeToRemove);
199: }
200:
201: return null;
202: }
203:
204: public function exponentiate(Type $exponent): Type
205: {
206: if (!$exponent instanceof NeverType && !$this->isSuperTypeOf($exponent)->no()) {
207: return TypeCombinator::union($this, $exponent);
208: }
209:
210: return new BenevolentUnionType([
211: new FloatType(),
212: new IntegerType(),
213: ]);
214: }
215:
216: public function getFiniteTypes(): array
217: {
218: return [];
219: }
220:
221: public function toPhpDocNode(): TypeNode
222: {
223: return new IdentifierTypeNode('object');
224: }
225:
226: /**
227: * @param mixed[] $properties
228: */
229: public static function __set_state(array $properties): Type
230: {
231: return new self($properties['subtractedType'] ?? null);
232: }
233:
234: }
235: