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 hasMethod(string $methodName): TrinaryLogic
124: {
125: if ($this->getCanonicalMethodName() === strtolower($methodName)) {
126: return TrinaryLogic::createYes();
127: }
128:
129: return TrinaryLogic::createMaybe();
130: }
131:
132: public function getMethod(string $methodName, ClassMemberAccessAnswerer $scope): ExtendedMethodReflection
133: {
134: return $this->getUnresolvedMethodPrototype($methodName, $scope)->getTransformedMethod();
135: }
136:
137: public function getUnresolvedMethodPrototype(string $methodName, ClassMemberAccessAnswerer $scope): UnresolvedMethodPrototypeReflection
138: {
139: $method = new DummyMethodReflection($this->methodName);
140: return new CallbackUnresolvedMethodPrototypeReflection(
141: $method,
142: $method->getDeclaringClass(),
143: false,
144: static fn (Type $type): Type => $type,
145: );
146: }
147:
148: public function isCallable(): TrinaryLogic
149: {
150: if ($this->getCanonicalMethodName() === '__invoke') {
151: return TrinaryLogic::createYes();
152: }
153:
154: return TrinaryLogic::createMaybe();
155: }
156:
157: public function toString(): Type
158: {
159: if ($this->getCanonicalMethodName() === '__tostring') {
160: return new StringType();
161: }
162:
163: return new ErrorType();
164: }
165:
166: public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope): array
167: {
168: return [
169: new TrivialParametersAcceptor(),
170: ];
171: }
172:
173: public function getEnumCases(): array
174: {
175: return [];
176: }
177:
178: public function traverse(callable $cb): Type
179: {
180: return $this;
181: }
182:
183: public function traverseSimultaneously(Type $right, callable $cb): Type
184: {
185: return $this;
186: }
187:
188: public function exponentiate(Type $exponent): Type
189: {
190: return new ErrorType();
191: }
192:
193: public function getFiniteTypes(): array
194: {
195: return [];
196: }
197:
198: public static function __set_state(array $properties): Type
199: {
200: return new self($properties['methodName']);
201: }
202:
203: public function toPhpDocNode(): TypeNode
204: {
205: return new IdentifierTypeNode(''); // no PHPDoc representation
206: }
207:
208: }
209: