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