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