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