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