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