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