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