1: <?php declare(strict_types = 1);
2:
3: namespace PHPStan\Type\Accessory;
4:
5: use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
6: use PHPStan\PhpDocParser\Ast\Type\TypeNode;
7: use PHPStan\Reflection\ClassMemberAccessAnswerer;
8: use PHPStan\Reflection\Dummy\DummyMethodReflection;
9: use PHPStan\Reflection\ExtendedMethodReflection;
10: use PHPStan\Reflection\TrivialParametersAcceptor;
11: use PHPStan\Reflection\Type\CallbackUnresolvedMethodPrototypeReflection;
12: use PHPStan\Reflection\Type\UnresolvedMethodPrototypeReflection;
13: use PHPStan\TrinaryLogic;
14: use PHPStan\Type\AcceptsResult;
15: use PHPStan\Type\CompoundType;
16: use PHPStan\Type\ErrorType;
17: use PHPStan\Type\IntersectionType;
18: use PHPStan\Type\IsSuperTypeOfResult;
19: use PHPStan\Type\StringType;
20: use PHPStan\Type\Traits\NonGeneralizableTypeTrait;
21: use PHPStan\Type\Traits\NonGenericTypeTrait;
22: use PHPStan\Type\Traits\NonRemoveableTypeTrait;
23: use PHPStan\Type\Traits\ObjectTypeTrait;
24: use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait;
25: use PHPStan\Type\Type;
26: use PHPStan\Type\UnionType;
27: use PHPStan\Type\VerbosityLevel;
28: use function sprintf;
29: use function strtolower;
30:
31: class HasMethodType implements AccessoryType, CompoundType
32: {
33:
34: use ObjectTypeTrait;
35: use NonGenericTypeTrait;
36: use UndecidedComparisonCompoundTypeTrait;
37: use NonRemoveableTypeTrait;
38: use NonGeneralizableTypeTrait;
39:
40: /** @api */
41: public function __construct(private string $methodName)
42: {
43: }
44:
45: public function getReferencedClasses(): array
46: {
47: return [];
48: }
49:
50: public function getObjectClassNames(): array
51: {
52: return [];
53: }
54:
55: public function getObjectClassReflections(): array
56: {
57: return [];
58: }
59:
60: private function getCanonicalMethodName(): string
61: {
62: return strtolower($this->methodName);
63: }
64:
65: public function accepts(Type $type, bool $strictTypes): AcceptsResult
66: {
67: if ($type instanceof CompoundType) {
68: return $type->isAcceptedBy($this, $strictTypes);
69: }
70:
71: return AcceptsResult::createFromBoolean($this->equals($type));
72: }
73:
74: public function isSuperTypeOf(Type $type): IsSuperTypeOfResult
75: {
76: return new IsSuperTypeOfResult($type->hasMethod($this->methodName), []);
77: }
78:
79: public function isSubTypeOf(Type $otherType): IsSuperTypeOfResult
80: {
81: if ($otherType instanceof UnionType || $otherType instanceof IntersectionType) {
82: return $otherType->isSuperTypeOf($this);
83: }
84:
85: if ($this->isCallable()->yes() && $otherType->isCallable()->yes()) {
86: return IsSuperTypeOfResult::createYes();
87: }
88:
89: if ($otherType instanceof self) {
90: $limit = IsSuperTypeOfResult::createYes();
91: } else {
92: $limit = IsSuperTypeOfResult::createMaybe();
93: }
94:
95: return $limit->and(new IsSuperTypeOfResult($otherType->hasMethod($this->methodName), []));
96: }
97:
98: public function isAcceptedBy(Type $acceptingType, bool $strictTypes): AcceptsResult
99: {
100: return $this->isSubTypeOf($acceptingType)->toAcceptsResult();
101: }
102:
103: public function equals(Type $type): bool
104: {
105: return $type instanceof self
106: && $this->getCanonicalMethodName() === $type->getCanonicalMethodName();
107: }
108:
109: public function describe(VerbosityLevel $level): string
110: {
111: return sprintf('hasMethod(%s)', $this->methodName);
112: }
113:
114: public function isOffsetAccessLegal(): TrinaryLogic
115: {
116: return TrinaryLogic::createMaybe();
117: }
118:
119: public function hasMethod(string $methodName): TrinaryLogic
120: {
121: if ($this->getCanonicalMethodName() === strtolower($methodName)) {
122: return TrinaryLogic::createYes();
123: }
124:
125: return TrinaryLogic::createMaybe();
126: }
127:
128: public function getMethod(string $methodName, ClassMemberAccessAnswerer $scope): ExtendedMethodReflection
129: {
130: return $this->getUnresolvedMethodPrototype($methodName, $scope)->getTransformedMethod();
131: }
132:
133: public function getUnresolvedMethodPrototype(string $methodName, ClassMemberAccessAnswerer $scope): UnresolvedMethodPrototypeReflection
134: {
135: $method = new DummyMethodReflection($this->methodName);
136: return new CallbackUnresolvedMethodPrototypeReflection(
137: $method,
138: $method->getDeclaringClass(),
139: false,
140: static fn (Type $type): Type => $type,
141: );
142: }
143:
144: public function isCallable(): TrinaryLogic
145: {
146: if ($this->getCanonicalMethodName() === '__invoke') {
147: return TrinaryLogic::createYes();
148: }
149:
150: return TrinaryLogic::createMaybe();
151: }
152:
153: public function toString(): Type
154: {
155: if ($this->getCanonicalMethodName() === '__tostring') {
156: return new StringType();
157: }
158:
159: return new ErrorType();
160: }
161:
162: public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope): array
163: {
164: return [
165: new TrivialParametersAcceptor(),
166: ];
167: }
168:
169: public function getEnumCases(): array
170: {
171: return [];
172: }
173:
174: public function traverse(callable $cb): Type
175: {
176: return $this;
177: }
178:
179: public function traverseSimultaneously(Type $right, callable $cb): Type
180: {
181: return $this;
182: }
183:
184: public function exponentiate(Type $exponent): Type
185: {
186: return new ErrorType();
187: }
188:
189: public function getFiniteTypes(): array
190: {
191: return [];
192: }
193:
194: public function toPhpDocNode(): TypeNode
195: {
196: return new IdentifierTypeNode(''); // no PHPDoc representation
197: }
198:
199: }
200: