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