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