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