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