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\MethodReflection;
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\CompoundType;
13: use PHPStan\Type\IntersectionType;
14: use PHPStan\Type\Traits\NonGeneralizableTypeTrait;
15: use PHPStan\Type\Traits\NonGenericTypeTrait;
16: use PHPStan\Type\Traits\NonRemoveableTypeTrait;
17: use PHPStan\Type\Traits\ObjectTypeTrait;
18: use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait;
19: use PHPStan\Type\Type;
20: use PHPStan\Type\UnionType;
21: use PHPStan\Type\VerbosityLevel;
22: use function sprintf;
23: use function strtolower;
24:
25: class HasMethodType implements AccessoryType, CompoundType
26: {
27:
28: use ObjectTypeTrait;
29: use NonGenericTypeTrait;
30: use UndecidedComparisonCompoundTypeTrait;
31: use NonRemoveableTypeTrait;
32: use NonGeneralizableTypeTrait;
33:
34: /** @api */
35: public function __construct(private string $methodName)
36: {
37: }
38:
39: public function getReferencedClasses(): array
40: {
41: return [];
42: }
43:
44: private function getCanonicalMethodName(): string
45: {
46: return strtolower($this->methodName);
47: }
48:
49: public function accepts(Type $type, bool $strictTypes): TrinaryLogic
50: {
51: return TrinaryLogic::createFromBoolean($this->equals($type));
52: }
53:
54: public function isSuperTypeOf(Type $type): TrinaryLogic
55: {
56: return $type->hasMethod($this->methodName);
57: }
58:
59: public function isSubTypeOf(Type $otherType): TrinaryLogic
60: {
61: if ($otherType instanceof UnionType || $otherType instanceof IntersectionType) {
62: return $otherType->isSuperTypeOf($this);
63: }
64:
65: if ($this->isCallable()->yes() && $otherType->isCallable()->yes()) {
66: return TrinaryLogic::createYes();
67: }
68:
69: if ($otherType instanceof self) {
70: $limit = TrinaryLogic::createYes();
71: } else {
72: $limit = TrinaryLogic::createMaybe();
73: }
74:
75: return $limit->and($otherType->hasMethod($this->methodName));
76: }
77:
78: public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic
79: {
80: return $this->isSubTypeOf($acceptingType);
81: }
82:
83: public function equals(Type $type): bool
84: {
85: return $type instanceof self
86: && $this->getCanonicalMethodName() === $type->getCanonicalMethodName();
87: }
88:
89: public function describe(VerbosityLevel $level): string
90: {
91: return sprintf('hasMethod(%s)', $this->methodName);
92: }
93:
94: public function hasMethod(string $methodName): TrinaryLogic
95: {
96: if ($this->getCanonicalMethodName() === strtolower($methodName)) {
97: return TrinaryLogic::createYes();
98: }
99:
100: return TrinaryLogic::createMaybe();
101: }
102:
103: public function getMethod(string $methodName, ClassMemberAccessAnswerer $scope): MethodReflection
104: {
105: return $this->getUnresolvedMethodPrototype($methodName, $scope)->getTransformedMethod();
106: }
107:
108: public function getUnresolvedMethodPrototype(string $methodName, ClassMemberAccessAnswerer $scope): UnresolvedMethodPrototypeReflection
109: {
110: $method = new DummyMethodReflection($this->methodName);
111: return new CallbackUnresolvedMethodPrototypeReflection(
112: $method,
113: $method->getDeclaringClass(),
114: false,
115: static fn (Type $type): Type => $type,
116: );
117: }
118:
119: public function isCallable(): TrinaryLogic
120: {
121: if ($this->getCanonicalMethodName() === '__invoke') {
122: return TrinaryLogic::createYes();
123: }
124:
125: return TrinaryLogic::createMaybe();
126: }
127:
128: public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope): array
129: {
130: return [
131: new TrivialParametersAcceptor(),
132: ];
133: }
134:
135: public function traverse(callable $cb): Type
136: {
137: return $this;
138: }
139:
140: public static function __set_state(array $properties): Type
141: {
142: return new self($properties['methodName']);
143: }
144:
145: }
146: