1: | <?php declare(strict_types = 1); |
2: | |
3: | namespace PHPStan\Type; |
4: | |
5: | use PHPStan\Analyser\OutOfClassScope; |
6: | use PHPStan\Reflection\ClassMemberAccessAnswerer; |
7: | use PHPStan\Reflection\Native\NativeParameterReflection; |
8: | use PHPStan\Reflection\ParameterReflection; |
9: | use PHPStan\Reflection\ParametersAcceptor; |
10: | use PHPStan\ShouldNotHappenException; |
11: | use PHPStan\TrinaryLogic; |
12: | use PHPStan\Type\Generic\TemplateType; |
13: | use PHPStan\Type\Generic\TemplateTypeHelper; |
14: | use PHPStan\Type\Generic\TemplateTypeMap; |
15: | use PHPStan\Type\Generic\TemplateTypeVariance; |
16: | use PHPStan\Type\Traits\MaybeIterableTypeTrait; |
17: | use PHPStan\Type\Traits\MaybeObjectTypeTrait; |
18: | use PHPStan\Type\Traits\MaybeOffsetAccessibleTypeTrait; |
19: | use PHPStan\Type\Traits\NonGeneralizableTypeTrait; |
20: | use PHPStan\Type\Traits\NonRemoveableTypeTrait; |
21: | use PHPStan\Type\Traits\TruthyBooleanTypeTrait; |
22: | use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait; |
23: | use function array_map; |
24: | use function array_merge; |
25: | use function implode; |
26: | use function sprintf; |
27: | |
28: | |
29: | class CallableType implements CompoundType, ParametersAcceptor |
30: | { |
31: | |
32: | use MaybeIterableTypeTrait; |
33: | use MaybeObjectTypeTrait; |
34: | use MaybeOffsetAccessibleTypeTrait; |
35: | use TruthyBooleanTypeTrait; |
36: | use UndecidedComparisonCompoundTypeTrait; |
37: | use NonRemoveableTypeTrait; |
38: | use NonGeneralizableTypeTrait; |
39: | |
40: | |
41: | private array $parameters; |
42: | |
43: | private Type $returnType; |
44: | |
45: | private bool $isCommonCallable; |
46: | |
47: | |
48: | |
49: | |
50: | |
51: | public function __construct( |
52: | ?array $parameters = null, |
53: | ?Type $returnType = null, |
54: | private bool $variadic = true, |
55: | ) |
56: | { |
57: | $this->parameters = $parameters ?? []; |
58: | $this->returnType = $returnType ?? new MixedType(); |
59: | $this->isCommonCallable = $parameters === null && $returnType === null; |
60: | } |
61: | |
62: | |
63: | |
64: | |
65: | public function getReferencedClasses(): array |
66: | { |
67: | $classes = []; |
68: | foreach ($this->parameters as $parameter) { |
69: | $classes = array_merge($classes, $parameter->getType()->getReferencedClasses()); |
70: | } |
71: | |
72: | return array_merge($classes, $this->returnType->getReferencedClasses()); |
73: | } |
74: | |
75: | public function accepts(Type $type, bool $strictTypes): TrinaryLogic |
76: | { |
77: | if ($type instanceof CompoundType && !$type instanceof self) { |
78: | return $type->isAcceptedBy($this, $strictTypes); |
79: | } |
80: | |
81: | return $this->isSuperTypeOfInternal($type, true); |
82: | } |
83: | |
84: | public function isSuperTypeOf(Type $type): TrinaryLogic |
85: | { |
86: | if ($type instanceof CompoundType && !$type instanceof self) { |
87: | return $type->isSubTypeOf($this); |
88: | } |
89: | |
90: | return $this->isSuperTypeOfInternal($type, false); |
91: | } |
92: | |
93: | private function isSuperTypeOfInternal(Type $type, bool $treatMixedAsAny): TrinaryLogic |
94: | { |
95: | $isCallable = $type->isCallable(); |
96: | if ($isCallable->no() || $this->isCommonCallable) { |
97: | return $isCallable; |
98: | } |
99: | |
100: | static $scope; |
101: | if ($scope === null) { |
102: | $scope = new OutOfClassScope(); |
103: | } |
104: | |
105: | $variantsResult = null; |
106: | foreach ($type->getCallableParametersAcceptors($scope) as $variant) { |
107: | $isSuperType = CallableTypeHelper::isParametersAcceptorSuperTypeOf($this, $variant, $treatMixedAsAny); |
108: | if ($variantsResult === null) { |
109: | $variantsResult = $isSuperType; |
110: | } else { |
111: | $variantsResult = $variantsResult->or($isSuperType); |
112: | } |
113: | } |
114: | |
115: | if ($variantsResult === null) { |
116: | throw new ShouldNotHappenException(); |
117: | } |
118: | |
119: | return $isCallable->and($variantsResult); |
120: | } |
121: | |
122: | public function isSubTypeOf(Type $otherType): TrinaryLogic |
123: | { |
124: | if ($otherType instanceof IntersectionType || $otherType instanceof UnionType) { |
125: | return $otherType->isSuperTypeOf($this); |
126: | } |
127: | |
128: | return $otherType->isCallable() |
129: | ->and($otherType instanceof self ? TrinaryLogic::createYes() : TrinaryLogic::createMaybe()); |
130: | } |
131: | |
132: | public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic |
133: | { |
134: | return $this->isSubTypeOf($acceptingType); |
135: | } |
136: | |
137: | public function equals(Type $type): bool |
138: | { |
139: | return $type instanceof self; |
140: | } |
141: | |
142: | public function describe(VerbosityLevel $level): string |
143: | { |
144: | return $level->handle( |
145: | static fn (): string => 'callable', |
146: | fn (): string => sprintf( |
147: | 'callable(%s): %s', |
148: | implode(', ', array_map( |
149: | static fn (ParameterReflection $param): string => sprintf( |
150: | '%s%s%s', |
151: | $param->isVariadic() ? '...' : '', |
152: | $param->getType()->describe($level), |
153: | $param->isOptional() && !$param->isVariadic() ? '=' : '', |
154: | ), |
155: | $this->getParameters(), |
156: | )), |
157: | $this->returnType->describe($level), |
158: | ), |
159: | ); |
160: | } |
161: | |
162: | public function isCallable(): TrinaryLogic |
163: | { |
164: | return TrinaryLogic::createYes(); |
165: | } |
166: | |
167: | |
168: | |
169: | |
170: | public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope): array |
171: | { |
172: | return [$this]; |
173: | } |
174: | |
175: | public function toNumber(): Type |
176: | { |
177: | return new ErrorType(); |
178: | } |
179: | |
180: | public function toString(): Type |
181: | { |
182: | return new ErrorType(); |
183: | } |
184: | |
185: | public function toInteger(): Type |
186: | { |
187: | return new ErrorType(); |
188: | } |
189: | |
190: | public function toFloat(): Type |
191: | { |
192: | return new ErrorType(); |
193: | } |
194: | |
195: | public function toArray(): Type |
196: | { |
197: | return new ArrayType(new MixedType(), new MixedType()); |
198: | } |
199: | |
200: | public function getTemplateTypeMap(): TemplateTypeMap |
201: | { |
202: | return TemplateTypeMap::createEmpty(); |
203: | } |
204: | |
205: | public function getResolvedTemplateTypeMap(): TemplateTypeMap |
206: | { |
207: | return TemplateTypeMap::createEmpty(); |
208: | } |
209: | |
210: | |
211: | |
212: | |
213: | public function getParameters(): array |
214: | { |
215: | return $this->parameters; |
216: | } |
217: | |
218: | public function isVariadic(): bool |
219: | { |
220: | return $this->variadic; |
221: | } |
222: | |
223: | public function getReturnType(): Type |
224: | { |
225: | return $this->returnType; |
226: | } |
227: | |
228: | public function inferTemplateTypes(Type $receivedType): TemplateTypeMap |
229: | { |
230: | if ($receivedType instanceof UnionType || $receivedType instanceof IntersectionType) { |
231: | return $receivedType->inferTemplateTypesOn($this); |
232: | } |
233: | |
234: | if (! $receivedType->isCallable()->yes()) { |
235: | return TemplateTypeMap::createEmpty(); |
236: | } |
237: | |
238: | $parametersAcceptors = $receivedType->getCallableParametersAcceptors(new OutOfClassScope()); |
239: | |
240: | $typeMap = TemplateTypeMap::createEmpty(); |
241: | |
242: | foreach ($parametersAcceptors as $parametersAcceptor) { |
243: | $typeMap = $typeMap->union($this->inferTemplateTypesOnParametersAcceptor($parametersAcceptor)); |
244: | } |
245: | |
246: | return $typeMap; |
247: | } |
248: | |
249: | private function inferTemplateTypesOnParametersAcceptor(ParametersAcceptor $parametersAcceptor): TemplateTypeMap |
250: | { |
251: | $typeMap = TemplateTypeMap::createEmpty(); |
252: | $args = $parametersAcceptor->getParameters(); |
253: | $returnType = $parametersAcceptor->getReturnType(); |
254: | |
255: | foreach ($this->getParameters() as $i => $param) { |
256: | $paramType = $param->getType(); |
257: | if (isset($args[$i])) { |
258: | $argType = $args[$i]->getType(); |
259: | } elseif ($paramType instanceof TemplateType) { |
260: | $argType = TemplateTypeHelper::resolveToBounds($paramType); |
261: | } else { |
262: | $argType = new NeverType(); |
263: | } |
264: | |
265: | $typeMap = $typeMap->union($paramType->inferTemplateTypes($argType)->convertToLowerBoundTypes()); |
266: | } |
267: | |
268: | return $typeMap->union($this->getReturnType()->inferTemplateTypes($returnType)); |
269: | } |
270: | |
271: | public function getReferencedTemplateTypes(TemplateTypeVariance $positionVariance): array |
272: | { |
273: | $references = $this->getReturnType()->getReferencedTemplateTypes( |
274: | $positionVariance->compose(TemplateTypeVariance::createCovariant()), |
275: | ); |
276: | |
277: | $paramVariance = $positionVariance->compose(TemplateTypeVariance::createContravariant()); |
278: | |
279: | foreach ($this->getParameters() as $param) { |
280: | foreach ($param->getType()->getReferencedTemplateTypes($paramVariance) as $reference) { |
281: | $references[] = $reference; |
282: | } |
283: | } |
284: | |
285: | return $references; |
286: | } |
287: | |
288: | public function traverse(callable $cb): Type |
289: | { |
290: | if ($this->isCommonCallable) { |
291: | return $this; |
292: | } |
293: | |
294: | $parameters = array_map(static function (ParameterReflection $param) use ($cb): NativeParameterReflection { |
295: | $defaultValue = $param->getDefaultValue(); |
296: | return new NativeParameterReflection( |
297: | $param->getName(), |
298: | $param->isOptional(), |
299: | $cb($param->getType()), |
300: | $param->passedByReference(), |
301: | $param->isVariadic(), |
302: | $defaultValue !== null ? $cb($defaultValue) : null, |
303: | ); |
304: | }, $this->getParameters()); |
305: | |
306: | return new self( |
307: | $parameters, |
308: | $cb($this->getReturnType()), |
309: | $this->isVariadic(), |
310: | ); |
311: | } |
312: | |
313: | public function isArray(): TrinaryLogic |
314: | { |
315: | return TrinaryLogic::createMaybe(); |
316: | } |
317: | |
318: | public function isOversizedArray(): TrinaryLogic |
319: | { |
320: | return TrinaryLogic::createNo(); |
321: | } |
322: | |
323: | public function isString(): TrinaryLogic |
324: | { |
325: | return TrinaryLogic::createMaybe(); |
326: | } |
327: | |
328: | public function isNumericString(): TrinaryLogic |
329: | { |
330: | return TrinaryLogic::createNo(); |
331: | } |
332: | |
333: | public function isNonEmptyString(): TrinaryLogic |
334: | { |
335: | return TrinaryLogic::createMaybe(); |
336: | } |
337: | |
338: | public function isNonFalsyString(): TrinaryLogic |
339: | { |
340: | return TrinaryLogic::createMaybe(); |
341: | } |
342: | |
343: | public function isLiteralString(): TrinaryLogic |
344: | { |
345: | return TrinaryLogic::createMaybe(); |
346: | } |
347: | |
348: | public function isCommonCallable(): bool |
349: | { |
350: | return $this->isCommonCallable; |
351: | } |
352: | |
353: | |
354: | |
355: | |
356: | public static function __set_state(array $properties): Type |
357: | { |
358: | return new self( |
359: | (bool) $properties['isCommonCallable'] ? null : $properties['parameters'], |
360: | (bool) $properties['isCommonCallable'] ? null : $properties['returnType'], |
361: | $properties['variadic'], |
362: | ); |
363: | } |
364: | |
365: | } |
366: | |