1: <?php declare(strict_types = 1);
2:
3: namespace PHPStan\Type\Accessory;
4:
5: use Closure;
6: use PHPStan\Php\PhpVersion;
7: use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
8: use PHPStan\PhpDocParser\Ast\Type\TypeNode;
9: use PHPStan\Reflection\ClassMemberAccessAnswerer;
10: use PHPStan\Reflection\Dummy\DummyMethodReflection;
11: use PHPStan\Reflection\ExtendedMethodReflection;
12: use PHPStan\Reflection\Type\CallbackUnresolvedMethodPrototypeReflection;
13: use PHPStan\Reflection\Type\UnresolvedMethodPrototypeReflection;
14: use PHPStan\TrinaryLogic;
15: use PHPStan\Type\AcceptsResult;
16: use PHPStan\Type\BooleanType;
17: use PHPStan\Type\CompoundType;
18: use PHPStan\Type\Enum\EnumCaseObjectType;
19: use PHPStan\Type\ErrorType;
20: use PHPStan\Type\Generic\GenericClassStringType;
21: use PHPStan\Type\IntersectionType;
22: use PHPStan\Type\IsSuperTypeOfResult;
23: use PHPStan\Type\MixedType;
24: use PHPStan\Type\ObjectType;
25: use PHPStan\Type\ObjectWithoutClassType;
26: use PHPStan\Type\StringType;
27: use PHPStan\Type\Traits\MaybeCallableTypeTrait;
28: use PHPStan\Type\Traits\MaybeIterableTypeTrait;
29: use PHPStan\Type\Traits\MaybeObjectTypeTrait;
30: use PHPStan\Type\Traits\MaybeOffsetAccessibleTypeTrait;
31: use PHPStan\Type\Traits\MaybeStringTypeTrait;
32: use PHPStan\Type\Traits\NonArrayTypeTrait;
33: use PHPStan\Type\Traits\NonGeneralizableTypeTrait;
34: use PHPStan\Type\Traits\NonGenericTypeTrait;
35: use PHPStan\Type\Traits\NonRemoveableTypeTrait;
36: use PHPStan\Type\Traits\TruthyBooleanTypeTrait;
37: use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait;
38: use PHPStan\Type\Type;
39: use PHPStan\Type\TypeCombinator;
40: use PHPStan\Type\UnionType;
41: use PHPStan\Type\VerbosityLevel;
42: use function sprintf;
43: use function strtolower;
44:
45: class HasMethodType implements AccessoryType, CompoundType
46: {
47:
48: use MaybeCallableTypeTrait;
49: use MaybeIterableTypeTrait;
50: use MaybeObjectTypeTrait;
51: use MaybeOffsetAccessibleTypeTrait;
52: use MaybeStringTypeTrait;
53: use NonArrayTypeTrait;
54: use TruthyBooleanTypeTrait;
55: use NonGenericTypeTrait;
56: use UndecidedComparisonCompoundTypeTrait;
57: use NonRemoveableTypeTrait;
58: use NonGeneralizableTypeTrait;
59:
60: /** @api */
61: public function __construct(private string $methodName)
62: {
63: }
64:
65: public function getReferencedClasses(): array
66: {
67: return [];
68: }
69:
70: public function getObjectClassNames(): array
71: {
72: return [];
73: }
74:
75: public function getObjectClassReflections(): array
76: {
77: return [];
78: }
79:
80: public function getClassStringType(): Type
81: {
82: return new GenericClassStringType($this);
83: }
84:
85: private function getCanonicalMethodName(): string
86: {
87: return strtolower($this->methodName);
88: }
89:
90: public function accepts(Type $type, bool $strictTypes): AcceptsResult
91: {
92: if ($type instanceof CompoundType) {
93: return $type->isAcceptedBy($this, $strictTypes);
94: }
95:
96: return AcceptsResult::createFromBoolean($this->equals($type));
97: }
98:
99: public function isSuperTypeOf(Type $type): IsSuperTypeOfResult
100: {
101: if ($type instanceof CompoundType) {
102: return $type->isSubTypeOf($this);
103: }
104:
105: return new IsSuperTypeOfResult($type->hasMethod($this->methodName), []);
106: }
107:
108: public function isSubTypeOf(Type $otherType): IsSuperTypeOfResult
109: {
110: if ($otherType instanceof UnionType || $otherType instanceof IntersectionType) {
111: return $otherType->isSuperTypeOf($this);
112: }
113:
114: if (
115: $this->isCallable()->yes()
116: && $otherType->isCallable()->yes()
117: && !(new ObjectType(Closure::class))->isSuperTypeOf($otherType)->yes()
118: ) {
119: return IsSuperTypeOfResult::createYes();
120: }
121:
122: if ($otherType instanceof self) {
123: $limit = TrinaryLogic::createYes();
124: } else {
125: $limit = TrinaryLogic::createMaybe();
126: }
127:
128: return new IsSuperTypeOfResult($limit->and($otherType->hasMethod($this->methodName)), []);
129: }
130:
131: public function isAcceptedBy(Type $acceptingType, bool $strictTypes): AcceptsResult
132: {
133: return $this->isSubTypeOf($acceptingType)->toAcceptsResult();
134: }
135:
136: public function equals(Type $type): bool
137: {
138: return $type instanceof self
139: && $this->getCanonicalMethodName() === $type->getCanonicalMethodName();
140: }
141:
142: public function describe(VerbosityLevel $level): string
143: {
144: return sprintf('hasMethod(%s)', $this->methodName);
145: }
146:
147: public function hasMethod(string $methodName): TrinaryLogic
148: {
149: if ($this->getCanonicalMethodName() === strtolower($methodName)) {
150: return TrinaryLogic::createYes();
151: }
152:
153: return TrinaryLogic::createMaybe();
154: }
155:
156: public function getMethod(string $methodName, ClassMemberAccessAnswerer $scope): ExtendedMethodReflection
157: {
158: return $this->getUnresolvedMethodPrototype($methodName, $scope)->getTransformedMethod();
159: }
160:
161: public function getUnresolvedMethodPrototype(string $methodName, ClassMemberAccessAnswerer $scope): UnresolvedMethodPrototypeReflection
162: {
163: $method = new DummyMethodReflection($this->methodName);
164: return new CallbackUnresolvedMethodPrototypeReflection(
165: $method,
166: $method->getDeclaringClass(),
167: false,
168: static fn (Type $type): Type => $type,
169: );
170: }
171:
172: public function isCallable(): TrinaryLogic
173: {
174: if ($this->getCanonicalMethodName() === '__invoke') {
175: return TrinaryLogic::createYes();
176: }
177:
178: return TrinaryLogic::createMaybe();
179: }
180:
181: public function isNull(): TrinaryLogic
182: {
183: return TrinaryLogic::createNo();
184: }
185:
186: public function isConstantValue(): TrinaryLogic
187: {
188: return TrinaryLogic::createNo();
189: }
190:
191: public function isConstantScalarValue(): TrinaryLogic
192: {
193: return TrinaryLogic::createNo();
194: }
195:
196: public function getConstantScalarTypes(): array
197: {
198: return [];
199: }
200:
201: public function getConstantScalarValues(): array
202: {
203: return [];
204: }
205:
206: public function isTrue(): TrinaryLogic
207: {
208: return TrinaryLogic::createNo();
209: }
210:
211: public function isFalse(): TrinaryLogic
212: {
213: return TrinaryLogic::createNo();
214: }
215:
216: public function isBoolean(): TrinaryLogic
217: {
218: return TrinaryLogic::createNo();
219: }
220:
221: public function isFloat(): TrinaryLogic
222: {
223: return TrinaryLogic::createNo();
224: }
225:
226: public function isInteger(): TrinaryLogic
227: {
228: return TrinaryLogic::createNo();
229: }
230:
231: public function getClassStringObjectType(): Type
232: {
233: return $this;
234: }
235:
236: public function getObjectTypeOrClassStringObjectType(): Type
237: {
238: return $this;
239: }
240:
241: public function isVoid(): TrinaryLogic
242: {
243: return TrinaryLogic::createNo();
244: }
245:
246: public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType
247: {
248: return new BooleanType();
249: }
250:
251: public function toNumber(): Type
252: {
253: return new ErrorType();
254: }
255:
256: public function toBitwiseNotType(): Type
257: {
258: return new ErrorType();
259: }
260:
261: public function toAbsoluteNumber(): Type
262: {
263: return new ErrorType();
264: }
265:
266: public function toString(): Type
267: {
268: if ($this->getCanonicalMethodName() === '__tostring') {
269: return new StringType();
270: }
271:
272: return new ErrorType();
273: }
274:
275: public function toInteger(): Type
276: {
277: return new ErrorType();
278: }
279:
280: public function toFloat(): Type
281: {
282: return new ErrorType();
283: }
284:
285: public function toArray(): Type
286: {
287: return new MixedType();
288: }
289:
290: public function toArrayKey(): Type
291: {
292: return new ErrorType();
293: }
294:
295: public function toCoercedArgumentType(bool $strictTypes): Type
296: {
297: if (!$strictTypes) {
298: return TypeCombinator::union($this, $this->toString());
299: }
300:
301: return $this;
302: }
303:
304: public function getEnumCases(): array
305: {
306: return [];
307: }
308:
309: public function getEnumCaseObject(): ?EnumCaseObjectType
310: {
311: return null;
312: }
313:
314: public function traverse(callable $cb): Type
315: {
316: return $this;
317: }
318:
319: public function traverseSimultaneously(Type $right, callable $cb): Type
320: {
321: return $this;
322: }
323:
324: public function exponentiate(Type $exponent): Type
325: {
326: return new ErrorType();
327: }
328:
329: public function getFiniteTypes(): array
330: {
331: return [];
332: }
333:
334: public function getDefaultBaseType(): Type
335: {
336: return new ObjectWithoutClassType();
337: }
338:
339: public function toPhpDocNode(): TypeNode
340: {
341: return new IdentifierTypeNode(''); // no PHPDoc representation
342: }
343:
344: public function hasTemplateOrLateResolvableType(): bool
345: {
346: return false;
347: }
348:
349: }
350: