1: <?php declare(strict_types = 1);
2:
3: namespace PHPStan\Type\Accessory;
4:
5: use PHPStan\Reflection\ClassMemberAccessAnswerer;
6: use PHPStan\Reflection\Dummy\DummyMethodReflection;
7: use PHPStan\Reflection\ExtendedMethodReflection;
8: use PHPStan\Reflection\TrivialParametersAcceptor;
9: use PHPStan\Reflection\Type\CallbackUnresolvedMethodPrototypeReflection;
10: use PHPStan\Reflection\Type\UnresolvedMethodPrototypeReflection;
11: use PHPStan\TrinaryLogic;
12: use PHPStan\Type\AcceptsResult;
13: use PHPStan\Type\CompoundType;
14: use PHPStan\Type\ErrorType;
15: use PHPStan\Type\IntersectionType;
16: use PHPStan\Type\StringType;
17: use PHPStan\Type\Traits\NonGeneralizableTypeTrait;
18: use PHPStan\Type\Traits\NonGenericTypeTrait;
19: use PHPStan\Type\Traits\NonRemoveableTypeTrait;
20: use PHPStan\Type\Traits\ObjectTypeTrait;
21: use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait;
22: use PHPStan\Type\Type;
23: use PHPStan\Type\UnionType;
24: use PHPStan\Type\VerbosityLevel;
25: use function sprintf;
26: use function strtolower;
27:
28: class HasMethodType implements AccessoryType, CompoundType
29: {
30:
31: use ObjectTypeTrait;
32: use NonGenericTypeTrait;
33: use UndecidedComparisonCompoundTypeTrait;
34: use NonRemoveableTypeTrait;
35: use NonGeneralizableTypeTrait;
36:
37: /** @api */
38: public function __construct(private string $methodName)
39: {
40: }
41:
42: public function getReferencedClasses(): array
43: {
44: return [];
45: }
46:
47: public function getObjectClassNames(): array
48: {
49: return [];
50: }
51:
52: public function getObjectClassReflections(): array
53: {
54: return [];
55: }
56:
57: private function getCanonicalMethodName(): string
58: {
59: return strtolower($this->methodName);
60: }
61:
62: public function accepts(Type $type, bool $strictTypes): TrinaryLogic
63: {
64: return $this->acceptsWithReason($type, $strictTypes)->result;
65: }
66:
67: public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult
68: {
69: if ($type instanceof CompoundType) {
70: return $type->isAcceptedWithReasonBy($this, $strictTypes);
71: }
72:
73: return AcceptsResult::createFromBoolean($this->equals($type));
74: }
75:
76: public function isSuperTypeOf(Type $type): TrinaryLogic
77: {
78: return $type->hasMethod($this->methodName);
79: }
80:
81: public function isSubTypeOf(Type $otherType): TrinaryLogic
82: {
83: if ($otherType instanceof UnionType || $otherType instanceof IntersectionType) {
84: return $otherType->isSuperTypeOf($this);
85: }
86:
87: if ($this->isCallable()->yes() && $otherType->isCallable()->yes()) {
88: return TrinaryLogic::createYes();
89: }
90:
91: if ($otherType instanceof self) {
92: $limit = TrinaryLogic::createYes();
93: } else {
94: $limit = TrinaryLogic::createMaybe();
95: }
96:
97: return $limit->and($otherType->hasMethod($this->methodName));
98: }
99:
100: public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic
101: {
102: return $this->isAcceptedWithReasonBy($acceptingType, $strictTypes)->result;
103: }
104:
105: public function isAcceptedWithReasonBy(Type $acceptingType, bool $strictTypes): AcceptsResult
106: {
107: return new AcceptsResult($this->isSubTypeOf($acceptingType), []);
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 traverse(callable $cb): Type
177: {
178: return $this;
179: }
180:
181: public function exponentiate(Type $exponent): Type
182: {
183: return new ErrorType();
184: }
185:
186: public static function __set_state(array $properties): Type
187: {
188: return new self($properties['methodName']);
189: }
190:
191: }
192: