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