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