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