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\Callables\CallableParametersAcceptor;
17: use PHPStan\Reflection\Callables\SimpleImpurePoint;
18: use PHPStan\Reflection\Callables\SimpleThrowPoint;
19: use PHPStan\Reflection\ClassConstantReflection;
20: use PHPStan\Reflection\ClassMemberAccessAnswerer;
21: use PHPStan\Reflection\ClassReflection;
22: use PHPStan\Reflection\ExtendedMethodReflection;
23: use PHPStan\Reflection\ExtendedPropertyReflection;
24: use PHPStan\Reflection\Native\NativeParameterReflection;
25: use PHPStan\Reflection\ParameterReflection;
26: use PHPStan\Reflection\ParametersAcceptor;
27: use PHPStan\Reflection\ParametersAcceptorSelector;
28: use PHPStan\Reflection\PassedByReference;
29: use PHPStan\Reflection\Php\ClosureCallUnresolvedMethodPrototypeReflection;
30: use PHPStan\Reflection\Php\DummyParameter;
31: use PHPStan\Reflection\Type\UnresolvedMethodPrototypeReflection;
32: use PHPStan\Reflection\Type\UnresolvedPropertyPrototypeReflection;
33: use PHPStan\TrinaryLogic;
34: use PHPStan\Type\Constant\ConstantArrayType;
35: use PHPStan\Type\Constant\ConstantBooleanType;
36: use PHPStan\Type\Constant\ConstantIntegerType;
37: use PHPStan\Type\Generic\TemplateType;
38: use PHPStan\Type\Generic\TemplateTypeHelper;
39: use PHPStan\Type\Generic\TemplateTypeMap;
40: use PHPStan\Type\Generic\TemplateTypeVariance;
41: use PHPStan\Type\Generic\TemplateTypeVarianceMap;
42: use PHPStan\Type\Traits\NonArrayTypeTrait;
43: use PHPStan\Type\Traits\NonGeneralizableTypeTrait;
44: use PHPStan\Type\Traits\NonIterableTypeTrait;
45: use PHPStan\Type\Traits\NonOffsetAccessibleTypeTrait;
46: use PHPStan\Type\Traits\NonRemoveableTypeTrait;
47: use PHPStan\Type\Traits\UndecidedComparisonTypeTrait;
48: use function array_map;
49: use function array_merge;
50: use function count;
51:
52: /** @api */
53: class ClosureType implements TypeWithClassName, CallableParametersAcceptor
54: {
55:
56: use NonArrayTypeTrait;
57: use NonIterableTypeTrait;
58: use UndecidedComparisonTypeTrait;
59: use NonOffsetAccessibleTypeTrait;
60: use NonRemoveableTypeTrait;
61: use NonGeneralizableTypeTrait;
62:
63: /** @var list<ParameterReflection> */
64: private array $parameters;
65:
66: private Type $returnType;
67:
68: private bool $isCommonCallable;
69:
70: private ObjectType $objectType;
71:
72: private TemplateTypeMap $templateTypeMap;
73:
74: private TemplateTypeMap $resolvedTemplateTypeMap;
75:
76: private TemplateTypeVarianceMap $callSiteVarianceMap;
77:
78: /** @var SimpleImpurePoint[] */
79: private array $impurePoints;
80:
81: private TrinaryLogic $acceptsNamedArguments;
82:
83: /**
84: * @api
85: * @param list<ParameterReflection>|null $parameters
86: * @param array<non-empty-string, TemplateTag> $templateTags
87: * @param SimpleThrowPoint[] $throwPoints
88: * @param ?SimpleImpurePoint[] $impurePoints
89: * @param InvalidateExprNode[] $invalidateExpressions
90: * @param string[] $usedVariables
91: */
92: public function __construct(
93: ?array $parameters = null,
94: ?Type $returnType = null,
95: private bool $variadic = true,
96: ?TemplateTypeMap $templateTypeMap = null,
97: ?TemplateTypeMap $resolvedTemplateTypeMap = null,
98: ?TemplateTypeVarianceMap $callSiteVarianceMap = null,
99: private array $templateTags = [],
100: private array $throwPoints = [],
101: ?array $impurePoints = null,
102: private array $invalidateExpressions = [],
103: private array $usedVariables = [],
104: ?TrinaryLogic $acceptsNamedArguments = null,
105: )
106: {
107: if ($acceptsNamedArguments === null) {
108: $acceptsNamedArguments = TrinaryLogic::createYes();
109: }
110: $this->acceptsNamedArguments = $acceptsNamedArguments;
111:
112: $this->parameters = $parameters ?? [];
113: $this->returnType = $returnType ?? new MixedType();
114: $this->isCommonCallable = $parameters === null && $returnType === null;
115: $this->objectType = new ObjectType(Closure::class);
116: $this->templateTypeMap = $templateTypeMap ?? TemplateTypeMap::createEmpty();
117: $this->resolvedTemplateTypeMap = $resolvedTemplateTypeMap ?? TemplateTypeMap::createEmpty();
118: $this->callSiteVarianceMap = $callSiteVarianceMap ?? TemplateTypeVarianceMap::createEmpty();
119: $this->impurePoints = $impurePoints ?? [new SimpleImpurePoint('functionCall', 'call to an unknown Closure', false)];
120: }
121:
122: /**
123: * @return array<non-empty-string, TemplateTag>
124: */
125: public function getTemplateTags(): array
126: {
127: return $this->templateTags;
128: }
129:
130: public static function createPure(): self
131: {
132: return new self(null, null, true, null, null, null, [], [], []);
133: }
134:
135: public function isPure(): TrinaryLogic
136: {
137: $impurePoints = $this->getImpurePoints();
138: if (count($impurePoints) === 0) {
139: return TrinaryLogic::createYes();
140: }
141:
142: $certainCount = 0;
143: foreach ($impurePoints as $impurePoint) {
144: if (!$impurePoint->isCertain()) {
145: continue;
146: }
147:
148: $certainCount++;
149: }
150:
151: return $certainCount > 0 ? TrinaryLogic::createNo() : TrinaryLogic::createMaybe();
152: }
153:
154: public function getClassName(): string
155: {
156: return $this->objectType->getClassName();
157: }
158:
159: public function getClassReflection(): ?ClassReflection
160: {
161: return $this->objectType->getClassReflection();
162: }
163:
164: public function getAncestorWithClassName(string $className): ?TypeWithClassName
165: {
166: return $this->objectType->getAncestorWithClassName($className);
167: }
168:
169: public function getReferencedClasses(): array
170: {
171: $classes = $this->objectType->getReferencedClasses();
172: foreach ($this->parameters as $parameter) {
173: $classes = array_merge($classes, $parameter->getType()->getReferencedClasses());
174: }
175:
176: return array_merge($classes, $this->returnType->getReferencedClasses());
177: }
178:
179: public function getObjectClassNames(): array
180: {
181: return $this->objectType->getObjectClassNames();
182: }
183:
184: public function getObjectClassReflections(): array
185: {
186: return $this->objectType->getObjectClassReflections();
187: }
188:
189: public function accepts(Type $type, bool $strictTypes): AcceptsResult
190: {
191: if ($type instanceof CompoundType) {
192: return $type->isAcceptedBy($this, $strictTypes);
193: }
194:
195: if (!$type instanceof ClosureType) {
196: return $this->objectType->accepts($type, $strictTypes);
197: }
198:
199: return $this->isSuperTypeOfInternal($type, true)->toAcceptsResult();
200: }
201:
202: public function isSuperTypeOf(Type $type): IsSuperTypeOfResult
203: {
204: if ($type instanceof CompoundType) {
205: return $type->isSubTypeOf($this);
206: }
207:
208: return $this->isSuperTypeOfInternal($type, false);
209: }
210:
211: private function isSuperTypeOfInternal(Type $type, bool $treatMixedAsAny): IsSuperTypeOfResult
212: {
213: if ($type instanceof self) {
214: $parameterTypes = array_map(static fn ($parameter) => $parameter->getType(), $this->getParameters());
215: $variant = ParametersAcceptorSelector::selectFromTypes($parameterTypes, [$type], false);
216: if (!$variant instanceof CallableParametersAcceptor) {
217: return IsSuperTypeOfResult::createNo([]);
218: }
219: return CallableTypeHelper::isParametersAcceptorSuperTypeOf(
220: $this,
221: $variant,
222: $treatMixedAsAny,
223: );
224: }
225:
226: if ($type->getObjectClassNames() === [Closure::class]) {
227: return IsSuperTypeOfResult::createMaybe();
228: }
229:
230: return $this->objectType->isSuperTypeOf($type);
231: }
232:
233: public function equals(Type $type): bool
234: {
235: if (!$type instanceof self) {
236: return false;
237: }
238:
239: return $this->describe(VerbosityLevel::precise()) === $type->describe(VerbosityLevel::precise());
240: }
241:
242: public function describe(VerbosityLevel $level): string
243: {
244: return $level->handle(
245: static fn (): string => 'Closure',
246: function (): string {
247: if ($this->isCommonCallable) {
248: return $this->isPure()->yes() ? 'pure-Closure' : 'Closure';
249: }
250:
251: $printer = new Printer();
252: $selfWithoutParameterNames = new self(
253: array_map(static fn (ParameterReflection $p): ParameterReflection => new DummyParameter(
254: '',
255: $p->getType(),
256: $p->isOptional() && !$p->isVariadic(),
257: PassedByReference::createNo(),
258: $p->isVariadic(),
259: $p->getDefaultValue(),
260: ), $this->parameters),
261: $this->returnType,
262: $this->variadic,
263: $this->templateTypeMap,
264: $this->resolvedTemplateTypeMap,
265: $this->callSiteVarianceMap,
266: $this->templateTags,
267: $this->throwPoints,
268: $this->impurePoints,
269: $this->invalidateExpressions,
270: $this->usedVariables,
271: );
272:
273: return $printer->print($selfWithoutParameterNames->toPhpDocNode());
274: },
275: );
276: }
277:
278: public function isOffsetAccessLegal(): TrinaryLogic
279: {
280: return TrinaryLogic::createNo();
281: }
282:
283: public function isObject(): TrinaryLogic
284: {
285: return $this->objectType->isObject();
286: }
287:
288: public function isEnum(): TrinaryLogic
289: {
290: return $this->objectType->isEnum();
291: }
292:
293: public function getTemplateType(string $ancestorClassName, string $templateTypeName): Type
294: {
295: return $this->objectType->getTemplateType($ancestorClassName, $templateTypeName);
296: }
297:
298: public function canAccessProperties(): TrinaryLogic
299: {
300: return $this->objectType->canAccessProperties();
301: }
302:
303: public function hasProperty(string $propertyName): TrinaryLogic
304: {
305: return $this->objectType->hasProperty($propertyName);
306: }
307:
308: public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection
309: {
310: return $this->objectType->getProperty($propertyName, $scope);
311: }
312:
313: public function getUnresolvedPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection
314: {
315: return $this->objectType->getUnresolvedPropertyPrototype($propertyName, $scope);
316: }
317:
318: public function canCallMethods(): TrinaryLogic
319: {
320: return $this->objectType->canCallMethods();
321: }
322:
323: public function hasMethod(string $methodName): TrinaryLogic
324: {
325: return $this->objectType->hasMethod($methodName);
326: }
327:
328: public function getMethod(string $methodName, ClassMemberAccessAnswerer $scope): ExtendedMethodReflection
329: {
330: return $this->getUnresolvedMethodPrototype($methodName, $scope)->getTransformedMethod();
331: }
332:
333: public function getUnresolvedMethodPrototype(string $methodName, ClassMemberAccessAnswerer $scope): UnresolvedMethodPrototypeReflection
334: {
335: if ($methodName === 'call') {
336: return new ClosureCallUnresolvedMethodPrototypeReflection(
337: $this->objectType->getUnresolvedMethodPrototype($methodName, $scope),
338: $this,
339: );
340: }
341:
342: return $this->objectType->getUnresolvedMethodPrototype($methodName, $scope);
343: }
344:
345: public function canAccessConstants(): TrinaryLogic
346: {
347: return $this->objectType->canAccessConstants();
348: }
349:
350: public function hasConstant(string $constantName): TrinaryLogic
351: {
352: return $this->objectType->hasConstant($constantName);
353: }
354:
355: public function getConstant(string $constantName): ClassConstantReflection
356: {
357: return $this->objectType->getConstant($constantName);
358: }
359:
360: public function getConstantStrings(): array
361: {
362: return [];
363: }
364:
365: public function isIterable(): TrinaryLogic
366: {
367: return TrinaryLogic::createNo();
368: }
369:
370: public function isIterableAtLeastOnce(): TrinaryLogic
371: {
372: return TrinaryLogic::createNo();
373: }
374:
375: public function isCallable(): TrinaryLogic
376: {
377: return TrinaryLogic::createYes();
378: }
379:
380: public function getEnumCases(): array
381: {
382: return [];
383: }
384:
385: public function isCommonCallable(): bool
386: {
387: return $this->isCommonCallable;
388: }
389:
390: public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope): array
391: {
392: return [$this];
393: }
394:
395: public function getThrowPoints(): array
396: {
397: return $this->throwPoints;
398: }
399:
400: public function getImpurePoints(): array
401: {
402: return $this->impurePoints;
403: }
404:
405: public function getInvalidateExpressions(): array
406: {
407: return $this->invalidateExpressions;
408: }
409:
410: public function getUsedVariables(): array
411: {
412: return $this->usedVariables;
413: }
414:
415: public function acceptsNamedArguments(): TrinaryLogic
416: {
417: return $this->acceptsNamedArguments;
418: }
419:
420: public function isCloneable(): TrinaryLogic
421: {
422: return TrinaryLogic::createYes();
423: }
424:
425: public function toBoolean(): BooleanType
426: {
427: return new ConstantBooleanType(true);
428: }
429:
430: public function toNumber(): Type
431: {
432: return new ErrorType();
433: }
434:
435: public function toAbsoluteNumber(): Type
436: {
437: return new ErrorType();
438: }
439:
440: public function toInteger(): Type
441: {
442: return new ErrorType();
443: }
444:
445: public function toFloat(): Type
446: {
447: return new ErrorType();
448: }
449:
450: public function toString(): Type
451: {
452: return new ErrorType();
453: }
454:
455: public function toArray(): Type
456: {
457: return new ConstantArrayType(
458: [new ConstantIntegerType(0)],
459: [$this],
460: [1],
461: [],
462: TrinaryLogic::createYes(),
463: );
464: }
465:
466: public function toArrayKey(): Type
467: {
468: return new ErrorType();
469: }
470:
471: public function toCoercedArgumentType(bool $strictTypes): Type
472: {
473: return TypeCombinator::union($this, new CallableType());
474: }
475:
476: public function getTemplateTypeMap(): TemplateTypeMap
477: {
478: return $this->templateTypeMap;
479: }
480:
481: public function getResolvedTemplateTypeMap(): TemplateTypeMap
482: {
483: return $this->resolvedTemplateTypeMap;
484: }
485:
486: public function getCallSiteVarianceMap(): TemplateTypeVarianceMap
487: {
488: return $this->callSiteVarianceMap;
489: }
490:
491: /**
492: * @return list<ParameterReflection>
493: */
494: public function getParameters(): array
495: {
496: return $this->parameters;
497: }
498:
499: public function isVariadic(): bool
500: {
501: return $this->variadic;
502: }
503:
504: public function getReturnType(): Type
505: {
506: return $this->returnType;
507: }
508:
509: public function inferTemplateTypes(Type $receivedType): TemplateTypeMap
510: {
511: if ($receivedType instanceof UnionType || $receivedType instanceof IntersectionType) {
512: return $receivedType->inferTemplateTypesOn($this);
513: }
514:
515: if ($receivedType->isCallable()->no() || ! $receivedType instanceof self) {
516: return TemplateTypeMap::createEmpty();
517: }
518:
519: $parametersAcceptors = $receivedType->getCallableParametersAcceptors(new OutOfClassScope());
520:
521: $typeMap = TemplateTypeMap::createEmpty();
522:
523: foreach ($parametersAcceptors as $parametersAcceptor) {
524: $typeMap = $typeMap->union($this->inferTemplateTypesOnParametersAcceptor($parametersAcceptor));
525: }
526:
527: return $typeMap;
528: }
529:
530: private function inferTemplateTypesOnParametersAcceptor(ParametersAcceptor $parametersAcceptor): TemplateTypeMap
531: {
532: $parameterTypes = array_map(static fn ($parameter) => $parameter->getType(), $this->getParameters());
533: $parametersAcceptor = ParametersAcceptorSelector::selectFromTypes($parameterTypes, [$parametersAcceptor], false);
534: $args = $parametersAcceptor->getParameters();
535: $returnType = $parametersAcceptor->getReturnType();
536:
537: $typeMap = TemplateTypeMap::createEmpty();
538: foreach ($this->getParameters() as $i => $param) {
539: $paramType = $param->getType();
540: if (isset($args[$i])) {
541: $argType = $args[$i]->getType();
542: } elseif ($paramType instanceof TemplateType) {
543: $argType = TemplateTypeHelper::resolveToBounds($paramType);
544: } else {
545: $argType = new NeverType();
546: }
547:
548: $typeMap = $typeMap->union($paramType->inferTemplateTypes($argType)->convertToLowerBoundTypes());
549: }
550:
551: return $typeMap->union($this->getReturnType()->inferTemplateTypes($returnType));
552: }
553:
554: public function getReferencedTemplateTypes(TemplateTypeVariance $positionVariance): array
555: {
556: $references = $this->getReturnType()->getReferencedTemplateTypes(
557: $positionVariance->compose(TemplateTypeVariance::createCovariant()),
558: );
559:
560: $paramVariance = $positionVariance->compose(TemplateTypeVariance::createContravariant());
561:
562: foreach ($this->getParameters() as $param) {
563: foreach ($param->getType()->getReferencedTemplateTypes($paramVariance) as $reference) {
564: $references[] = $reference;
565: }
566: }
567:
568: return $references;
569: }
570:
571: public function traverse(callable $cb): Type
572: {
573: if ($this->isCommonCallable) {
574: return $this;
575: }
576:
577: return new self(
578: array_map(static function (ParameterReflection $param) use ($cb): NativeParameterReflection {
579: $defaultValue = $param->getDefaultValue();
580: return new NativeParameterReflection(
581: $param->getName(),
582: $param->isOptional(),
583: $cb($param->getType()),
584: $param->passedByReference(),
585: $param->isVariadic(),
586: $defaultValue !== null ? $cb($defaultValue) : null,
587: );
588: }, $this->getParameters()),
589: $cb($this->getReturnType()),
590: $this->isVariadic(),
591: $this->templateTypeMap,
592: $this->resolvedTemplateTypeMap,
593: $this->callSiteVarianceMap,
594: $this->templateTags,
595: $this->throwPoints,
596: $this->impurePoints,
597: $this->invalidateExpressions,
598: $this->usedVariables,
599: $this->acceptsNamedArguments,
600: );
601: }
602:
603: public function traverseSimultaneously(Type $right, callable $cb): Type
604: {
605: if ($this->isCommonCallable) {
606: return $this;
607: }
608:
609: if (!$right instanceof self) {
610: return $this;
611: }
612:
613: $rightParameters = $right->getParameters();
614: if (count($this->getParameters()) !== count($rightParameters)) {
615: return $this;
616: }
617:
618: $parameters = [];
619: foreach ($this->getParameters() as $i => $leftParam) {
620: $rightParam = $rightParameters[$i];
621: $leftDefaultValue = $leftParam->getDefaultValue();
622: $rightDefaultValue = $rightParam->getDefaultValue();
623: $defaultValue = $leftDefaultValue;
624: if ($leftDefaultValue !== null && $rightDefaultValue !== null) {
625: $defaultValue = $cb($leftDefaultValue, $rightDefaultValue);
626: }
627: $parameters[] = new NativeParameterReflection(
628: $leftParam->getName(),
629: $leftParam->isOptional(),
630: $cb($leftParam->getType(), $rightParam->getType()),
631: $leftParam->passedByReference(),
632: $leftParam->isVariadic(),
633: $defaultValue,
634: );
635: }
636:
637: return new self(
638: $parameters,
639: $cb($this->getReturnType(), $right->getReturnType()),
640: $this->isVariadic(),
641: $this->templateTypeMap,
642: $this->resolvedTemplateTypeMap,
643: $this->callSiteVarianceMap,
644: $this->templateTags,
645: $this->throwPoints,
646: $this->impurePoints,
647: $this->invalidateExpressions,
648: $this->usedVariables,
649: $this->acceptsNamedArguments,
650: );
651: }
652:
653: public function isNull(): TrinaryLogic
654: {
655: return TrinaryLogic::createNo();
656: }
657:
658: public function isConstantValue(): TrinaryLogic
659: {
660: return TrinaryLogic::createNo();
661: }
662:
663: public function isConstantScalarValue(): TrinaryLogic
664: {
665: return TrinaryLogic::createNo();
666: }
667:
668: public function getConstantScalarTypes(): array
669: {
670: return [];
671: }
672:
673: public function getConstantScalarValues(): array
674: {
675: return [];
676: }
677:
678: public function isTrue(): TrinaryLogic
679: {
680: return TrinaryLogic::createNo();
681: }
682:
683: public function isFalse(): TrinaryLogic
684: {
685: return TrinaryLogic::createNo();
686: }
687:
688: public function isBoolean(): TrinaryLogic
689: {
690: return TrinaryLogic::createNo();
691: }
692:
693: public function isFloat(): TrinaryLogic
694: {
695: return TrinaryLogic::createNo();
696: }
697:
698: public function isInteger(): TrinaryLogic
699: {
700: return TrinaryLogic::createNo();
701: }
702:
703: public function isString(): TrinaryLogic
704: {
705: return TrinaryLogic::createNo();
706: }
707:
708: public function isNumericString(): TrinaryLogic
709: {
710: return TrinaryLogic::createNo();
711: }
712:
713: public function isNonEmptyString(): TrinaryLogic
714: {
715: return TrinaryLogic::createNo();
716: }
717:
718: public function isNonFalsyString(): TrinaryLogic
719: {
720: return TrinaryLogic::createNo();
721: }
722:
723: public function isLiteralString(): TrinaryLogic
724: {
725: return TrinaryLogic::createNo();
726: }
727:
728: public function isLowercaseString(): TrinaryLogic
729: {
730: return TrinaryLogic::createNo();
731: }
732:
733: public function isClassString(): TrinaryLogic
734: {
735: return TrinaryLogic::createNo();
736: }
737:
738: public function isUppercaseString(): TrinaryLogic
739: {
740: return TrinaryLogic::createNo();
741: }
742:
743: public function getClassStringObjectType(): Type
744: {
745: return new ErrorType();
746: }
747:
748: public function getObjectTypeOrClassStringObjectType(): Type
749: {
750: return $this;
751: }
752:
753: public function isVoid(): TrinaryLogic
754: {
755: return TrinaryLogic::createNo();
756: }
757:
758: public function isScalar(): TrinaryLogic
759: {
760: return TrinaryLogic::createNo();
761: }
762:
763: public function looseCompare(Type $type, PhpVersion $phpVersion): BooleanType
764: {
765: return new BooleanType();
766: }
767:
768: public function exponentiate(Type $exponent): Type
769: {
770: return new ErrorType();
771: }
772:
773: public function getFiniteTypes(): array
774: {
775: return [];
776: }
777:
778: public function toPhpDocNode(): TypeNode
779: {
780: if ($this->isCommonCallable) {
781: return new IdentifierTypeNode($this->isPure()->yes() ? 'pure-Closure' : 'Closure');
782: }
783:
784: $parameters = [];
785: foreach ($this->parameters as $parameter) {
786: $parameters[] = new CallableTypeParameterNode(
787: $parameter->getType()->toPhpDocNode(),
788: !$parameter->passedByReference()->no(),
789: $parameter->isVariadic(),
790: $parameter->getName() === '' ? '' : '$' . $parameter->getName(),
791: $parameter->isOptional(),
792: );
793: }
794:
795: $templateTags = [];
796: foreach ($this->templateTags as $templateName => $templateTag) {
797: $templateTags[] = new TemplateTagValueNode(
798: $templateName,
799: $templateTag->getBound()->toPhpDocNode(),
800: '',
801: );
802: }
803:
804: return new CallableTypeNode(
805: new IdentifierTypeNode('Closure'),
806: $parameters,
807: $this->returnType->toPhpDocNode(),
808: $templateTags,
809: );
810: }
811:
812: }
813: