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