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: /** @api */
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: /** @var array<int, ParameterReflection> */
43: private array $parameters;
44:
45: private Type $returnType;
46:
47: private bool $isCommonCallable;
48:
49: /**
50: * @api
51: * @param array<int, ParameterReflection> $parameters
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: * @return string[]
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: * @return ParametersAcceptor[]
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: * @return array<int, ParameterReflection>
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: * @param mixed[] $properties
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: