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