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