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