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