1: <?php declare(strict_types = 1);
2:
3: namespace PHPStan\Type;
4:
5: use Closure;
6: use PHPStan\Analyser\OutOfClassScope;
7: use PHPStan\Node\InvalidateExprNode;
8: use PHPStan\Php\PhpVersion;
9: use PHPStan\PhpDoc\Tag\TemplateTag;
10: use PHPStan\PhpDocParser\Ast\PhpDoc\TemplateTagValueNode;
11: use PHPStan\PhpDocParser\Ast\Type\CallableTypeNode;
12: use PHPStan\PhpDocParser\Ast\Type\CallableTypeParameterNode;
13: use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
14: use PHPStan\PhpDocParser\Ast\Type\TypeNode;
15: use PHPStan\PhpDocParser\Printer\Printer;
16: use PHPStan\Reflection\Assertions;
17: use PHPStan\Reflection\Callables\CallableParametersAcceptor;
18: use PHPStan\Reflection\Callables\SimpleImpurePoint;
19: use PHPStan\Reflection\Callables\SimpleThrowPoint;
20: use PHPStan\Reflection\ClassConstantReflection;
21: use PHPStan\Reflection\ClassMemberAccessAnswerer;
22: use PHPStan\Reflection\ClassReflection;
23: use PHPStan\Reflection\ExtendedMethodReflection;
24: use PHPStan\Reflection\ExtendedParameterReflection;
25: use PHPStan\Reflection\ExtendedPropertyReflection;
26: use PHPStan\Reflection\Native\NativeParameterReflection;
27: use PHPStan\Reflection\ParameterReflection;
28: use PHPStan\Reflection\ParametersAcceptor;
29: use PHPStan\Reflection\ParametersAcceptorSelector;
30: use PHPStan\Reflection\PassedByReference;
31: use PHPStan\Reflection\Php\ClosureCallUnresolvedMethodPrototypeReflection;
32: use PHPStan\Reflection\Php\DummyParameter;
33: use PHPStan\Reflection\ReflectionProvider;
34: use PHPStan\Reflection\Type\UnresolvedMethodPrototypeReflection;
35: use PHPStan\Reflection\Type\UnresolvedPropertyPrototypeReflection;
36: use PHPStan\TrinaryLogic;
37: use PHPStan\Turbo\ShadowedByTurboExtension;
38: use PHPStan\Type\Constant\ConstantArrayType;
39: use PHPStan\Type\Constant\ConstantBooleanType;
40: use PHPStan\Type\Constant\ConstantIntegerType;
41: use PHPStan\Type\Enum\EnumCaseObjectType;
42: use PHPStan\Type\Generic\TemplateType;
43: use PHPStan\Type\Generic\TemplateTypeHelper;
44: use PHPStan\Type\Generic\TemplateTypeMap;
45: use PHPStan\Type\Generic\TemplateTypeVariance;
46: use PHPStan\Type\Generic\TemplateTypeVarianceMap;
47: use PHPStan\Type\Generic\UnresolvedTemplateArgumentType;
48: use PHPStan\Type\Traits\NonArrayTypeTrait;
49: use PHPStan\Type\Traits\NonGeneralizableTypeTrait;
50: use PHPStan\Type\Traits\NonIterableTypeTrait;
51: use PHPStan\Type\Traits\NonOffsetAccessibleTypeTrait;
52: use PHPStan\Type\Traits\NonRemoveableTypeTrait;
53: use PHPStan\Type\Traits\UndecidedComparisonTypeTrait;
54: use function array_key_exists;
55: use function array_map;
56: use function array_merge;
57: use function count;
58:
59: /** @api */
60: #[ShadowedByTurboExtension(implementation: __DIR__ . '/../../turbo-ext/src/ClosureType.cpp')]
61: class ClosureType implements TypeWithClassName, CallableParametersAcceptor
62: {
63:
64: use NonArrayTypeTrait;
65: use NonIterableTypeTrait;
66: use UndecidedComparisonTypeTrait;
67: use NonOffsetAccessibleTypeTrait;
68: use NonRemoveableTypeTrait;
69: use NonGeneralizableTypeTrait;
70:
71: /** @var list<ParameterReflection> */
72: private array $parameters;
73:
74: private Type $returnType;
75:
76: private bool $isCommonCallable;
77:
78: private ObjectType $objectType;
79:
80: private TemplateTypeMap $templateTypeMap;
81:
82: private TemplateTypeMap $resolvedTemplateTypeMap;
83:
84: private TemplateTypeVarianceMap $callSiteVarianceMap;
85:
86: /** @var SimpleImpurePoint[] */
87: private array $impurePoints;
88:
89: private TrinaryLogic $acceptsNamedArguments;
90:
91: private TrinaryLogic $mustUseReturnValue;
92:
93: private Assertions $assertions;
94:
95: private TrinaryLogic $isStatic;
96:
97: /** @var array<int, string> */
98: private array $cachedDescriptions = [];
99:
100: /**
101: * @api
102: * @param list<ParameterReflection>|null $parameters
103: * @param array<non-empty-string, TemplateTag> $templateTags
104: * @param SimpleThrowPoint[] $throwPoints
105: * @param ?SimpleImpurePoint[] $impurePoints
106: * @param InvalidateExprNode[] $invalidateExpressions
107: * @param string[] $usedVariables
108: * @param array<string, Type> $byRefUseTypes per by-ref use of a closure whose effects apply where it is invoked, the marker of its site (see ClosureSignatureInference)
109: */
110: public function __construct(
111: ?array $parameters = null,
112: ?Type $returnType = null,
113: private bool $variadic = true,
114: ?TemplateTypeMap $templateTypeMap = null,
115: ?TemplateTypeMap $resolvedTemplateTypeMap = null,
116: ?TemplateTypeVarianceMap $callSiteVarianceMap = null,
117: private array $templateTags = [],
118: private array $throwPoints = [],
119: ?array $impurePoints = null,
120: private array $invalidateExpressions = [],
121: private array $usedVariables = [],
122: ?TrinaryLogic $acceptsNamedArguments = null,
123: ?TrinaryLogic $mustUseReturnValue = null,
124: ?Assertions $assertions = null,
125: ?TrinaryLogic $isStatic = null,
126: private array $byRefUseTypes = [],
127: )
128: {
129: if ($acceptsNamedArguments === null) {
130: $acceptsNamedArguments = TrinaryLogic::createYes();
131: }
132: $this->acceptsNamedArguments = $acceptsNamedArguments;
133: if ($mustUseReturnValue === null) {
134: $mustUseReturnValue = TrinaryLogic::createMaybe();
135: }
136: $this->mustUseReturnValue = $mustUseReturnValue;
137:
138: $this->parameters = $parameters ?? [];
139: $this->returnType = $returnType ?? new MixedType();
140: $this->isCommonCallable = $parameters === null && $returnType === null;
141: $this->objectType = new ObjectType(Closure::class);
142: $this->templateTypeMap = $templateTypeMap ?? TemplateTypeMap::createEmpty();
143: $this->resolvedTemplateTypeMap = $resolvedTemplateTypeMap ?? TemplateTypeMap::createEmpty();
144: $this->callSiteVarianceMap = $callSiteVarianceMap ?? TemplateTypeVarianceMap::createEmpty();
145: $this->impurePoints = $impurePoints ?? [new SimpleImpurePoint('functionCall', 'call to an unknown Closure', false)];
146: $this->assertions = $assertions ?? Assertions::createEmpty();
147: $this->isStatic = $isStatic ?? TrinaryLogic::createMaybe();
148: }
149:
150: public function getAsserts(): Assertions
151: {
152: return $this->assertions;
153: }
154:
155: /**
156: * @return array<non-empty-string, TemplateTag>
157: */
158: public function getTemplateTags(): array
159: {
160: return $this->templateTags;
161: }
162:
163: public static function createPure(): self
164: {
165: return new self(
166: parameters: null,
167: returnType: null,
168: variadic: true,
169: templateTypeMap: null,
170: resolvedTemplateTypeMap: null,
171: callSiteVarianceMap: null,
172: templateTags: [],
173: throwPoints: [],
174: impurePoints: [],
175: );
176: }
177:
178: public function isPure(): TrinaryLogic
179: {
180: $impurePoints = $this->getImpurePoints();
181: if (count($impurePoints) === 0) {
182: return TrinaryLogic::createYes();
183: }
184:
185: $certainCount = 0;
186: foreach ($impurePoints as $impurePoint) {
187: if (!$impurePoint->isCertain()) {
188: continue;
189: }
190:
191: $certainCount++;
192: }
193:
194: return $certainCount > 0 ? TrinaryLogic::createNo() : TrinaryLogic::createMaybe();
195: }
196:
197: public function getClassName(): string
198: {
199: return $this->objectType->getClassName();
200: }
201:
202: public function getClassReflection(): ?ClassReflection
203: {
204: return $this->objectType->getClassReflection();
205: }
206:
207: public function getAncestorWithClassName(string $className): ?TypeWithClassName
208: {
209: return $this->objectType->getAncestorWithClassName($className);
210: }
211:
212: public function getReferencedClasses(): array
213: {
214: $classes = $this->objectType->getReferencedClasses();
215: foreach ($this->parameters as $parameter) {
216: $classes = array_merge($classes, $parameter->getType()->getReferencedClasses());
217: }
218: foreach ($this->assertions->getAll() as $assertTag) {
219: $classes = array_merge($classes, $assertTag->getType()->getReferencedClasses());
220: }
221:
222: return array_merge($classes, $this->returnType->getReferencedClasses());
223: }
224:
225: public function getObjectClassNames(): array
226: {
227: return $this->objectType->getObjectClassNames();
228: }
229:
230: public function getObjectClassReflections(): array
231: {
232: return $this->objectType->getObjectClassReflections();
233: }
234:
235: public function accepts(Type $type, bool $strictTypes): AcceptsResult
236: {
237: if ($type instanceof CompoundType) {
238: return $type->isAcceptedBy($this, $strictTypes);
239: }
240:
241: if (!$type instanceof ClosureType) {
242: return $this->objectType->accepts($type, $strictTypes);
243: }
244:
245: return $this->isSuperTypeOfInternal($type, true, $strictTypes)->toAcceptsResult();
246: }
247:
248: public function isSuperTypeOf(Type $type): IsSuperTypeOfResult
249: {
250: if ($type instanceof CompoundType) {
251: return $type->isSubTypeOf($this);
252: }
253:
254: return $this->isSuperTypeOfInternal($type, false);
255: }
256:
257: private function isSuperTypeOfInternal(Type $type, bool $treatMixedAsAny, bool $strictTypes = true): IsSuperTypeOfResult
258: {
259: if ($type instanceof self) {
260: $parameterTypes = array_map(static fn ($parameter) => $parameter->getType(), $this->getParameters());
261: $variant = ParametersAcceptorSelector::selectFromTypes($parameterTypes, [$type], false);
262: if (!$variant instanceof CallableParametersAcceptor) {
263: return IsSuperTypeOfResult::createNo([]);
264: }
265: return CallableTypeHelper::isParametersAcceptorSuperTypeOf(
266: $this,
267: $variant,
268: $treatMixedAsAny,
269: $strictTypes,
270: );
271: }
272:
273: if ($type->getObjectClassNames() === [Closure::class]) {
274: return IsSuperTypeOfResult::createMaybe();
275: }
276:
277: return $this->objectType->isSuperTypeOf($type);
278: }
279:
280: public function equals(Type $type): bool
281: {
282: if (!$type instanceof self) {
283: return false;
284: }
285:
286: return $this->describe(VerbosityLevel::precise()) === $type->describe(VerbosityLevel::precise())
287: && $this->isPure()->equals($type->isPure())
288: && $this->isStatic->equals($type->isStatic)
289: && $this->hasEqualUnresolvedSignature($type);
290: }
291:
292: /**
293: * An unresolved template argument describes as its delegate, but a closure
294: * whose signature is still being inferred is not the same closure as one
295: * with the delegate types (see ClosureSignatureInference).
296: */
297: private function hasEqualUnresolvedSignature(self $other): bool
298: {
299: $pairs = [[$this->returnType, $other->returnType]];
300: foreach ($this->parameters as $i => $parameter) {
301: if (!isset($other->parameters[$i])) {
302: continue;
303: }
304: $pairs[] = [$parameter->getType(), $other->parameters[$i]->getType()];
305: }
306: foreach ($pairs as [$ours, $theirs]) {
307: if (!$ours instanceof UnresolvedTemplateArgumentType && !$theirs instanceof UnresolvedTemplateArgumentType) {
308: continue;
309: }
310: if (!$ours->equals($theirs)) {
311: return false;
312: }
313: }
314: if (count($this->byRefUseTypes) !== count($other->byRefUseTypes)) {
315: return false;
316: }
317: foreach ($this->byRefUseTypes as $name => $type) {
318: if (!isset($other->byRefUseTypes[$name]) || !$type->equals($other->byRefUseTypes[$name])) {
319: return false;
320: }
321: }
322:
323: return true;
324: }
325:
326: public function describe(VerbosityLevel $level): string
327: {
328: return $this->cachedDescriptions[$level->getLevelValue()] ??= $level->handle(
329: static fn (): string => 'Closure',
330: function (): string {
331: if ($this->isCommonCallable) {
332: $prefix = $this->isStatic->yes() ? 'static-' : '';
333: $name = $this->isPure()->yes() ? 'pure-Closure' : 'Closure';
334: return $prefix . $name;
335: }
336:
337: return $this->describeCallable();
338: },
339: function (): string {
340: $prefix = $this->isStatic->yes() ? 'static-' : '';
341:
342: if ($this->isCommonCallable) {
343: $name = $this->isPure()->yes() ? 'pure-Closure' : 'Closure';
344: return $prefix . $name;
345: }
346:
347: return $prefix . $this->describeCallable();
348: },
349: );
350: }
351:
352: private function describeCallable(): string
353: {
354: $printer = new Printer();
355: $assertedParameterNames = [];
356: foreach ($this->assertions->getAll() as $assertTag) {
357: $assertedParameterNames[$assertTag->getParameter()->getParameterName()] = true;
358: }
359: $selfWithoutParameterNames = new self(
360: array_map(static fn (ParameterReflection $p): ParameterReflection => new DummyParameter(
361: array_key_exists('$' . $p->getName(), $assertedParameterNames) ? $p->getName() : '',
362: $p->getType(),
363: optional: $p->isOptional() && !$p->isVariadic(),
364: passedByReference: PassedByReference::createNo(),
365: variadic: $p->isVariadic(),
366: defaultValue: $p->getDefaultValue(),
367: ), $this->parameters),
368: $this->returnType,
369: $this->variadic,
370: $this->templateTypeMap,
371: $this->resolvedTemplateTypeMap,
372: $this->callSiteVarianceMap,
373: $this->templateTags,
374: $this->throwPoints,
375: $this->impurePoints,
376: $this->invalidateExpressions,
377: $this->usedVariables,
378: $this->acceptsNamedArguments,
379: $this->mustUseReturnValue,
380: $this->assertions,
381: $this->isStatic,
382: );
383:
384: return $printer->print($selfWithoutParameterNames->toPhpDocNode());
385: }
386:
387: public function isOffsetAccessLegal(): TrinaryLogic
388: {
389: return TrinaryLogic::createNo();
390: }
391:
392: public function isObject(): TrinaryLogic
393: {
394: return $this->objectType->isObject();
395: }
396:
397: public function getClassStringType(): Type
398: {
399: return $this->objectType->getClassStringType();
400: }
401:
402: public function isEnum(): TrinaryLogic
403: {
404: return $this->objectType->isEnum();
405: }
406:
407: public function getTemplateType(string $ancestorClassName, string $templateTypeName): Type
408: {
409: return $this->objectType->getTemplateType($ancestorClassName, $templateTypeName);
410: }
411:
412: public function canAccessProperties(): TrinaryLogic
413: {
414: return $this->objectType->canAccessProperties();
415: }
416:
417: public function hasProperty(string $propertyName): TrinaryLogic
418: {
419: return $this->objectType->hasProperty($propertyName);
420: }
421:
422: public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection
423: {
424: return $this->objectType->getProperty($propertyName, $scope);
425: }
426:
427: public function getUnresolvedPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection
428: {
429: return $this->objectType->getUnresolvedPropertyPrototype($propertyName, $scope);
430: }
431:
432: public function hasInstanceProperty(string $propertyName): TrinaryLogic
433: {
434: return $this->objectType->hasInstanceProperty($propertyName);
435: }
436:
437: public function getInstanceProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection
438: {
439: return $this->objectType->getInstanceProperty($propertyName, $scope);
440: }
441:
442: public function getUnresolvedInstancePropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection
443: {
444: return $this->objectType->getUnresolvedInstancePropertyPrototype($propertyName, $scope);
445: }
446:
447: public function hasStaticProperty(string $propertyName): TrinaryLogic
448: {
449: return $this->objectType->hasStaticProperty($propertyName);
450: }
451:
452: public function getStaticProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection
453: {
454: return $this->objectType->getStaticProperty($propertyName, $scope);
455: }
456:
457: public function getUnresolvedStaticPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection
458: {
459: return $this->objectType->getUnresolvedStaticPropertyPrototype($propertyName, $scope);
460: }
461:
462: public function canCallMethods(): TrinaryLogic
463: {
464: return $this->objectType->canCallMethods();
465: }
466:
467: public function hasMethod(string $methodName): TrinaryLogic
468: {
469: return $this->objectType->hasMethod($methodName);
470: }
471:
472: public function getMethod(string $methodName, ClassMemberAccessAnswerer $scope): ExtendedMethodReflection
473: {
474: return $this->getUnresolvedMethodPrototype($methodName, $scope)->getTransformedMethod();
475: }
476:
477: public function getUnresolvedMethodPrototype(string $methodName, ClassMemberAccessAnswerer $scope): UnresolvedMethodPrototypeReflection
478: {
479: if ($methodName === 'call') {
480: return new ClosureCallUnresolvedMethodPrototypeReflection(
481: $this->objectType->getUnresolvedMethodPrototype($methodName, $scope),
482: $this,
483: );
484: }
485:
486: return $this->objectType->getUnresolvedMethodPrototype($methodName, $scope);
487: }
488:
489: public function canAccessConstants(): TrinaryLogic
490: {
491: return $this->objectType->canAccessConstants();
492: }
493:
494: public function hasConstant(string $constantName): TrinaryLogic
495: {
496: return $this->objectType->hasConstant($constantName);
497: }
498:
499: public function getConstant(string $constantName): ClassConstantReflection
500: {
501: return $this->objectType->getConstant($constantName);
502: }
503:
504: public function getConstantStrings(): array
505: {
506: return [];
507: }
508:
509: public function isIterable(): TrinaryLogic
510: {
511: return TrinaryLogic::createNo();
512: }
513:
514: public function isIterableAtLeastOnce(): TrinaryLogic
515: {
516: return TrinaryLogic::createNo();
517: }
518:
519: public function isCallable(): TrinaryLogic
520: {
521: return TrinaryLogic::createYes();
522: }
523:
524: public function getEnumCases(): array
525: {
526: return [];
527: }
528:
529: public function getEnumCaseObject(): ?EnumCaseObjectType
530: {
531: return null;
532: }
533:
534: public function isCommonCallable(): bool
535: {
536: return $this->isCommonCallable;
537: }
538:
539: public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope): array
540: {
541: return [$this];
542: }
543:
544: public function getThrowPoints(): array
545: {
546: return $this->throwPoints;
547: }
548:
549: public function getImpurePoints(): array
550: {
551: return $this->impurePoints;
552: }
553:
554: public function getInvalidateExpressions(): array
555: {
556: return $this->invalidateExpressions;
557: }
558:
559: public function getUsedVariables(): array
560: {
561: return $this->usedVariables;
562: }
563:
564: /** @return array<string, Type> */
565: public function getByRefUseTypes(): array
566: {
567: return $this->byRefUseTypes;
568: }
569:
570: public function acceptsNamedArguments(): TrinaryLogic
571: {
572: return $this->acceptsNamedArguments;
573: }
574:
575: public function mustUseReturnValue(): TrinaryLogic
576: {
577: return $this->mustUseReturnValue;
578: }
579:
580: public function isStaticClosure(): TrinaryLogic
581: {
582: return $this->isStatic;
583: }
584:
585: public function isCloneable(): TrinaryLogic
586: {
587: return TrinaryLogic::createYes();
588: }
589:
590: public function toBoolean(): BooleanType
591: {
592: return new ConstantBooleanType(true);
593: }
594:
595: public function toNumber(): Type
596: {
597: return new ErrorType();
598: }
599:
600: public function toBitwiseNotType(): Type
601: {
602: return new ErrorType();
603: }
604:
605: public function toGetClassResultType(): Type
606: {
607: return $this->getClassStringType();
608: }
609:
610: public function toClassConstantType(ReflectionProvider $reflectionProvider): Type
611: {
612: return $this->objectType->toClassConstantType($reflectionProvider);
613: }
614:
615: public function toObjectTypeForInstanceofCheck(): ClassNameToObjectTypeResult
616: {
617: return new ClassNameToObjectTypeResult($this, true);
618: }
619:
620: public function toObjectTypeForIsACheck(Type $objectOrClassType, bool $allowString, bool $allowSameClass): ClassNameToObjectTypeResult
621: {
622: if ($allowString) {
623: return new ClassNameToObjectTypeResult(
624: new UnionType([new ObjectWithoutClassType(), new ClassStringType()]),
625: false,
626: );
627: }
628:
629: return new ClassNameToObjectTypeResult(new ObjectWithoutClassType(), false);
630: }
631:
632: public function toAbsoluteNumber(): Type
633: {
634: return new ErrorType();
635: }
636:
637: public function toInteger(): Type
638: {
639: return new ErrorType();
640: }
641:
642: public function toFloat(): Type
643: {
644: return new ErrorType();
645: }
646:
647: public function toString(): Type
648: {
649: return new ErrorType();
650: }
651:
652: public function toArray(): Type
653: {
654: return new ConstantArrayType(
655: [new ConstantIntegerType(0)],
656: [$this],
657: [1],
658: isList: TrinaryLogic::createYes(),
659: );
660: }
661:
662: public function toArrayKey(): Type
663: {
664: return new ErrorType();
665: }
666:
667: public function toCoercedArgumentType(bool $strictTypes): Type
668: {
669: return TypeCombinator::union($this, new CallableType());
670: }
671:
672: public function getTemplateTypeMap(): TemplateTypeMap
673: {
674: return $this->templateTypeMap;
675: }
676:
677: public function getResolvedTemplateTypeMap(): TemplateTypeMap
678: {
679: return $this->resolvedTemplateTypeMap;
680: }
681:
682: public function getCallSiteVarianceMap(): TemplateTypeVarianceMap
683: {
684: return $this->callSiteVarianceMap;
685: }
686:
687: /**
688: * @return list<ParameterReflection>
689: */
690: public function getParameters(): array
691: {
692: return $this->parameters;
693: }
694:
695: public function isVariadic(): bool
696: {
697: return $this->variadic;
698: }
699:
700: public function getReturnType(): Type
701: {
702: return $this->returnType;
703: }
704:
705: public function inferTemplateTypes(Type $receivedType): TemplateTypeMap
706: {
707: if ($receivedType instanceof UnionType || $receivedType instanceof IntersectionType) {
708: return $receivedType->inferTemplateTypesOn($this);
709: }
710:
711: if ($receivedType->isCallable()->no() || ! $receivedType instanceof self) {
712: return TemplateTypeMap::createEmpty();
713: }
714:
715: $parametersAcceptors = $receivedType->getCallableParametersAcceptors(new OutOfClassScope());
716:
717: $typeMap = TemplateTypeMap::createEmpty();
718:
719: foreach ($parametersAcceptors as $parametersAcceptor) {
720: $typeMap = $typeMap->union($this->inferTemplateTypesOnParametersAcceptor($parametersAcceptor));
721: }
722:
723: return $typeMap;
724: }
725:
726: private function inferTemplateTypesOnParametersAcceptor(ParametersAcceptor $parametersAcceptor): TemplateTypeMap
727: {
728: $parameterTypes = array_map(static fn ($parameter) => $parameter->getType(), $this->getParameters());
729: $parametersAcceptor = ParametersAcceptorSelector::selectFromTypes($parameterTypes, [$parametersAcceptor], false);
730: $args = $parametersAcceptor->getParameters();
731: $returnType = $parametersAcceptor->getReturnType();
732:
733: $typeMap = TemplateTypeMap::createEmpty();
734: foreach ($this->getParameters() as $i => $param) {
735: $paramType = $param->getType();
736: if (isset($args[$i])) {
737: $argType = $args[$i]->getType();
738: } elseif ($paramType instanceof TemplateType) {
739: $argType = TemplateTypeHelper::resolveToBounds($paramType);
740: } else {
741: $argType = new NeverType();
742: }
743:
744: $typeMap = $typeMap->union($paramType->inferTemplateTypes($argType)->convertToLowerBoundTypes());
745: }
746:
747: $typeMap = $typeMap->union(CallableAssertionsHelper::inferTemplateTypesOnAsserts($this, $parametersAcceptor));
748:
749: return $typeMap->union($this->getReturnType()->inferTemplateTypes($returnType));
750: }
751:
752: public function getReferencedTemplateTypes(TemplateTypeVariance $positionVariance): array
753: {
754: $references = $this->getReturnType()->getReferencedTemplateTypes(
755: $positionVariance->compose(TemplateTypeVariance::createCovariant()),
756: );
757:
758: foreach ($this->assertions->getAll() as $assertTag) {
759: foreach ($assertTag->getType()->getReferencedTemplateTypes($positionVariance->compose(TemplateTypeVariance::createCovariant())) as $reference) {
760: $references[] = $reference;
761: }
762: }
763:
764: $paramVariance = $positionVariance->compose(TemplateTypeVariance::createContravariant());
765:
766: foreach ($this->getParameters() as $param) {
767: foreach ($param->getType()->getReferencedTemplateTypes($paramVariance) as $reference) {
768: $references[] = $reference;
769: }
770: }
771:
772: return $references;
773: }
774:
775: public function traverse(callable $cb): Type
776: {
777: if ($this->isCommonCallable) {
778: return $this;
779: }
780:
781: return new self(
782: array_map(static function (ParameterReflection $param) use ($cb): NativeParameterReflection {
783: $defaultValue = $param->getDefaultValue();
784: return new NativeParameterReflection(
785: $param->getName(),
786: $param->isOptional(),
787: $cb($param->getType()),
788: $param->passedByReference(),
789: $param->isVariadic(),
790: $defaultValue !== null ? $cb($defaultValue) : null,
791: );
792: }, $this->getParameters()),
793: $cb($this->getReturnType()),
794: $this->isVariadic(),
795: $this->templateTypeMap,
796: $this->resolvedTemplateTypeMap,
797: $this->callSiteVarianceMap,
798: $this->templateTags,
799: $this->throwPoints,
800: $this->impurePoints,
801: $this->invalidateExpressions,
802: $this->usedVariables,
803: $this->acceptsNamedArguments,
804: $this->mustUseReturnValue,
805: $this->assertions->mapTypes($cb),
806: $this->isStatic,
807: $this->byRefUseTypes,
808: );
809: }
810:
811: public function traverseSimultaneously(Type $right, callable $cb): Type
812: {
813: if ($this->isCommonCallable) {
814: return $this;
815: }
816:
817: if (!$right instanceof self) {
818: return $this;
819: }
820:
821: $rightParameters = $right->getParameters();
822: if (count($this->getParameters()) !== count($rightParameters)) {
823: return $this;
824: }
825:
826: $parameters = [];
827: foreach ($this->getParameters() as $i => $leftParam) {
828: $rightParam = $rightParameters[$i];
829: $leftDefaultValue = $leftParam->getDefaultValue();
830: $rightDefaultValue = $rightParam->getDefaultValue();
831: $defaultValue = $leftDefaultValue;
832: if ($leftDefaultValue !== null && $rightDefaultValue !== null) {
833: $defaultValue = $cb($leftDefaultValue, $rightDefaultValue);
834: }
835: $parameters[] = new NativeParameterReflection(
836: $leftParam->getName(),
837: $leftParam->isOptional(),
838: $cb($leftParam->getType(), $rightParam->getType()),
839: $leftParam->passedByReference(),
840: $leftParam->isVariadic(),
841: $defaultValue,
842: );
843: }
844:
845: return new self(
846: $parameters,
847: $cb($this->getReturnType(), $right->getReturnType()),
848: $this->isVariadic(),
849: $this->templateTypeMap,
850: $this->resolvedTemplateTypeMap,
851: $this->callSiteVarianceMap,
852: $this->templateTags,
853: $this->throwPoints,
854: $this->impurePoints,
855: $this->invalidateExpressions,
856: $this->usedVariables,
857: $this->acceptsNamedArguments,
858: $this->mustUseReturnValue,
859: $this->assertions,
860: $this->isStatic,
861: $this->byRefUseTypes,
862: );
863: }
864:
865: public function isNull(): TrinaryLogic
866: {
867: return TrinaryLogic::createNo();
868: }
869:
870: public function isConstantValue(): TrinaryLogic
871: {
872: return TrinaryLogic::createNo();
873: }
874:
875: public function isConstantScalarValue(): TrinaryLogic
876: {
877: return TrinaryLogic::createNo();
878: }
879:
880: public function getConstantScalarTypes(): array
881: {
882: return [];
883: }
884:
885: public function getConstantScalarValues(): array
886: {
887: return [];
888: }
889:
890: public function isTrue(): TrinaryLogic
891: {
892: return TrinaryLogic::createNo();
893: }
894:
895: public function isFalse(): TrinaryLogic
896: {
897: return TrinaryLogic::createNo();
898: }
899:
900: public function isBoolean(): TrinaryLogic
901: {
902: return TrinaryLogic::createNo();
903: }
904:
905: public function isFloat(): TrinaryLogic
906: {
907: return TrinaryLogic::createNo();
908: }
909:
910: public function isInteger(): TrinaryLogic
911: {
912: return TrinaryLogic::createNo();
913: }
914:
915: public function isString(): TrinaryLogic
916: {
917: return TrinaryLogic::createNo();
918: }
919:
920: public function isNumericString(): TrinaryLogic
921: {
922: return TrinaryLogic::createNo();
923: }
924:
925: public function isDecimalIntegerString(): TrinaryLogic
926: {
927: return TrinaryLogic::createNo();
928: }
929:
930: public function isNonEmptyString(): TrinaryLogic
931: {
932: return TrinaryLogic::createNo();
933: }
934:
935: public function isNonFalsyString(): TrinaryLogic
936: {
937: return TrinaryLogic::createNo();
938: }
939:
940: public function isLiteralString(): TrinaryLogic
941: {
942: return TrinaryLogic::createNo();
943: }
944:
945: public function isLowercaseString(): TrinaryLogic
946: {
947: return TrinaryLogic::createNo();
948: }
949:
950: public function isClassString(): TrinaryLogic
951: {
952: return TrinaryLogic::createNo();
953: }
954:
955: public function isUppercaseString(): TrinaryLogic
956: {
957: return TrinaryLogic::createNo();
958: }
959:
960: public function getClassStringObjectType(): Type
961: {
962: return new ErrorType();
963: }
964:
965: public function getObjectTypeOrClassStringObjectType(): Type
966: {
967: return $this;
968: }
969:
970: public function isVoid(): TrinaryLogic
971: {
972: return TrinaryLogic::createNo();
973: }
974:
975: public function isScalar(): TrinaryLogic
976: {
977: return TrinaryLogic::createNo();
978: }
979:
980: public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType
981: {
982: return new BooleanType();
983: }
984:
985: public function exponentiate(Type $exponent): Type
986: {
987: return new ErrorType();
988: }
989:
990: public function getFiniteTypes(): array
991: {
992: return [];
993: }
994:
995: public function toPhpDocNode(): TypeNode
996: {
997: if ($this->isCommonCallable) {
998: $prefix = $this->isStatic->yes() ? 'static-' : '';
999: $name = $this->isPure()->yes() ? 'pure-Closure' : 'Closure';
1000: return new IdentifierTypeNode($prefix . $name);
1001: }
1002:
1003: $parameters = [];
1004: foreach ($this->parameters as $parameter) {
1005: $parameters[] = new CallableTypeParameterNode(
1006: $parameter->getType()->toPhpDocNode(),
1007: !$parameter->passedByReference()->no(),
1008: $parameter->isVariadic(),
1009: $parameter->getName() === '' ? '' : '$' . $parameter->getName(),
1010: $parameter->isOptional(),
1011: );
1012: }
1013:
1014: $templateTags = [];
1015: foreach ($this->templateTags as $templateName => $templateTag) {
1016: $templateTags[] = new TemplateTagValueNode(
1017: $templateName,
1018: $templateTag->getBound()->toPhpDocNode(),
1019: '',
1020: );
1021: }
1022:
1023: $returnTypeNode = CallableAssertionsHelper::toConditionalReturnTypeNode($this->assertions, $this->parameters, $this->returnType)
1024: ?? $this->returnType->toPhpDocNode();
1025:
1026: return new CallableTypeNode(
1027: new IdentifierTypeNode('Closure'),
1028: $parameters,
1029: $returnTypeNode,
1030: $templateTags,
1031: );
1032: }
1033:
1034: public function hasTemplateOrLateResolvableType(): bool
1035: {
1036: foreach ($this->parameters as $parameter) {
1037: if ($parameter->getType()->hasTemplateOrLateResolvableType()) {
1038: return true;
1039: }
1040:
1041: if (!$parameter instanceof ExtendedParameterReflection) {
1042: continue;
1043: }
1044:
1045: if ($parameter->getOutType() !== null && $parameter->getOutType()->hasTemplateOrLateResolvableType()) {
1046: return true;
1047: }
1048: if ($parameter->getClosureThisType() !== null && $parameter->getClosureThisType()->hasTemplateOrLateResolvableType()) {
1049: return true;
1050: }
1051: }
1052:
1053: foreach ($this->assertions->getAll() as $assertTag) {
1054: if ($assertTag->getType()->hasTemplateOrLateResolvableType()) {
1055: return true;
1056: }
1057: }
1058:
1059: return $this->getReturnType()->hasTemplateOrLateResolvableType();
1060: }
1061:
1062: }
1063: