1: <?php declare(strict_types = 1);
2:
3: namespace PHPStan\Type;
4:
5: use PhpParser\Node\Identifier;
6: use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
7: use PHPStan\PhpDocParser\Ast\Type\TypeNode;
8: use PHPStan\Turbo\ShadowedByTurboExtension;
9: use PHPStan\Type\Enum\EnumCaseObjectType;
10: use PHPStan\Type\Traits\NonGeneralizableTypeTrait;
11: use PHPStan\Type\Traits\NonGenericTypeTrait;
12: use PHPStan\Type\Traits\ObjectTypeTrait;
13: use PHPStan\Type\Traits\SubstractableTypeTrait;
14: use PHPStan\Type\Traits\UndecidedComparisonTypeTrait;
15:
16: /** @api */
17: #[InstanceofDeprecated(insteadUse: 'Type::isObject()')]
18: #[ShadowedByTurboExtension(implementation: __DIR__ . '/../../turbo-ext/src/ObjectWithoutClassType.cpp')]
19: class ObjectWithoutClassType implements SubtractableType
20: {
21:
22: use ObjectTypeTrait;
23: use NonGenericTypeTrait;
24: use UndecidedComparisonTypeTrait;
25: use NonGeneralizableTypeTrait;
26: use SubstractableTypeTrait;
27:
28: private ?Type $subtractedType;
29:
30: /** @api */
31: public function __construct(
32: ?Type $subtractedType = null,
33: )
34: {
35: if ($subtractedType instanceof NeverType) {
36: $subtractedType = null;
37: }
38:
39: $this->subtractedType = $subtractedType;
40: }
41:
42: public function getReferencedClasses(): array
43: {
44: return [];
45: }
46:
47: public function getObjectClassNames(): array
48: {
49: return [];
50: }
51:
52: public function getObjectClassReflections(): array
53: {
54: return [];
55: }
56:
57: public function getClassStringType(): Type
58: {
59: return new ClassStringType();
60: }
61:
62: public function accepts(Type $type, bool $strictTypes): AcceptsResult
63: {
64: if ($type instanceof CompoundType) {
65: return $type->isAcceptedBy($this, $strictTypes);
66: }
67:
68: return AcceptsResult::createFromBoolean(
69: $type instanceof self || $type instanceof ObjectShapeType || $type->getObjectClassNames() !== [],
70: );
71: }
72:
73: public function isSuperTypeOf(Type $type): IsSuperTypeOfResult
74: {
75: if ($type instanceof CompoundType) {
76: return $type->isSubTypeOf($this);
77: }
78:
79: if ($type instanceof self) {
80: if ($this->subtractedType === null) {
81: return IsSuperTypeOfResult::createYes();
82: }
83: if ($type->subtractedType !== null) {
84: $isSuperType = $type->subtractedType->isSuperTypeOf($this->subtractedType);
85: if ($isSuperType->yes()) {
86: return $isSuperType;
87: }
88: }
89:
90: return IsSuperTypeOfResult::createMaybe();
91: }
92:
93: if ($type instanceof ObjectShapeType) {
94: return IsSuperTypeOfResult::createYes();
95: }
96:
97: if ($type->getObjectClassNames() === []) {
98: return IsSuperTypeOfResult::createNo();
99: }
100:
101: if ($this->subtractedType === null) {
102: return IsSuperTypeOfResult::createYes();
103: }
104:
105: return $this->subtractedType->isSuperTypeOf($type)->negate();
106: }
107:
108: public function equals(Type $type): bool
109: {
110: if (!$type instanceof self) {
111: return false;
112: }
113:
114: if ($this->subtractedType === null) {
115: if ($type->subtractedType === null) {
116: return true;
117: }
118:
119: return false;
120: }
121:
122: if ($type->subtractedType === null) {
123: return false;
124: }
125:
126: return $this->subtractedType->equals($type->subtractedType);
127: }
128:
129: public function describe(VerbosityLevel $level): string
130: {
131: return $level->handle(
132: static fn (): string => 'object',
133: static fn (): string => 'object',
134: fn (): string => 'object' . $this->describeSubtractedType($this->subtractedType, $level),
135: );
136: }
137:
138: public function getEnumCases(): array
139: {
140: return [];
141: }
142:
143: public function getEnumCaseObject(): ?EnumCaseObjectType
144: {
145: return null;
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: // object is the top of the object hierarchy, so removal is exactly the
198: // subtraction - also when an earlier subtraction already lowered
199: // isSuperTypeOf() from yes to maybe, which used to remove nothing at all
200: if ($this->isSuperTypeOf($typeToRemove)->no()) {
201: return null;
202: }
203:
204: return $this->subtract($typeToRemove);
205: }
206:
207: public function exponentiate(Type $exponent): Type
208: {
209: if (!$exponent instanceof NeverType && !$this->isSuperTypeOf($exponent)->no()) {
210: return TypeCombinator::union($this, $exponent);
211: }
212:
213: return new BenevolentUnionType([
214: new FloatType(),
215: new IntegerType(),
216: ]);
217: }
218:
219: public function getFiniteTypes(): array
220: {
221: return [];
222: }
223:
224: public function toPhpDocNode(): TypeNode
225: {
226: return new IdentifierTypeNode('object');
227: }
228:
229: public function toNativePhpTypeNode(NativeTypePosition $position)
230: {
231: return $position->isObjectAllowed() ? new Identifier('object') : null;
232: }
233:
234: public function hasTemplateOrLateResolvableType(): bool
235: {
236: return false;
237: }
238:
239: }
240: