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