1: <?php declare(strict_types = 1);
2:
3: namespace PHPStan\PhpDoc;
4:
5: use Closure;
6: use Generator;
7: use Iterator;
8: use IteratorAggregate;
9: use Nette\Utils\Strings;
10: use PhpParser\Node\Name;
11: use PHPStan\Analyser\ConstantResolver;
12: use PHPStan\Analyser\NameScope;
13: use PHPStan\PhpDoc\Tag\TemplateTag;
14: use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprArrayNode;
15: use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprFalseNode;
16: use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprFloatNode;
17: use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprIntegerNode;
18: use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprNullNode;
19: use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprStringNode;
20: use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprTrueNode;
21: use PHPStan\PhpDocParser\Ast\ConstExpr\ConstFetchNode;
22: use PHPStan\PhpDocParser\Ast\Type\ArrayShapeNode;
23: use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode;
24: use PHPStan\PhpDocParser\Ast\Type\CallableTypeNode;
25: use PHPStan\PhpDocParser\Ast\Type\CallableTypeParameterNode;
26: use PHPStan\PhpDocParser\Ast\Type\ConditionalTypeForParameterNode;
27: use PHPStan\PhpDocParser\Ast\Type\ConditionalTypeNode;
28: use PHPStan\PhpDocParser\Ast\Type\ConstTypeNode;
29: use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode;
30: use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
31: use PHPStan\PhpDocParser\Ast\Type\IntersectionTypeNode;
32: use PHPStan\PhpDocParser\Ast\Type\InvalidTypeNode;
33: use PHPStan\PhpDocParser\Ast\Type\NullableTypeNode;
34: use PHPStan\PhpDocParser\Ast\Type\ObjectShapeNode;
35: use PHPStan\PhpDocParser\Ast\Type\OffsetAccessTypeNode;
36: use PHPStan\PhpDocParser\Ast\Type\ThisTypeNode;
37: use PHPStan\PhpDocParser\Ast\Type\TypeNode;
38: use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode;
39: use PHPStan\Reflection\Callables\SimpleImpurePoint;
40: use PHPStan\Reflection\InitializerExprContext;
41: use PHPStan\Reflection\InitializerExprTypeResolver;
42: use PHPStan\Reflection\Native\NativeParameterReflection;
43: use PHPStan\Reflection\PassedByReference;
44: use PHPStan\Reflection\ReflectionProvider;
45: use PHPStan\ShouldNotHappenException;
46: use PHPStan\TrinaryLogic;
47: use PHPStan\Type\Accessory\AccessoryArrayListType;
48: use PHPStan\Type\Accessory\AccessoryLiteralStringType;
49: use PHPStan\Type\Accessory\AccessoryLowercaseStringType;
50: use PHPStan\Type\Accessory\AccessoryNonEmptyStringType;
51: use PHPStan\Type\Accessory\AccessoryNonFalsyStringType;
52: use PHPStan\Type\Accessory\AccessoryNumericStringType;
53: use PHPStan\Type\Accessory\AccessoryUppercaseStringType;
54: use PHPStan\Type\Accessory\NonEmptyArrayType;
55: use PHPStan\Type\ArrayType;
56: use PHPStan\Type\BenevolentUnionType;
57: use PHPStan\Type\BooleanType;
58: use PHPStan\Type\CallableType;
59: use PHPStan\Type\ClassStringType;
60: use PHPStan\Type\ClosureType;
61: use PHPStan\Type\ConditionalType;
62: use PHPStan\Type\ConditionalTypeForParameter;
63: use PHPStan\Type\Constant\ConstantArrayType;
64: use PHPStan\Type\Constant\ConstantArrayTypeBuilder;
65: use PHPStan\Type\Constant\ConstantBooleanType;
66: use PHPStan\Type\Constant\ConstantFloatType;
67: use PHPStan\Type\Constant\ConstantIntegerType;
68: use PHPStan\Type\Constant\ConstantStringType;
69: use PHPStan\Type\Enum\EnumCaseObjectType;
70: use PHPStan\Type\ErrorType;
71: use PHPStan\Type\FloatType;
72: use PHPStan\Type\Generic\GenericClassStringType;
73: use PHPStan\Type\Generic\GenericObjectType;
74: use PHPStan\Type\Generic\GenericStaticType;
75: use PHPStan\Type\Generic\TemplateType;
76: use PHPStan\Type\Generic\TemplateTypeFactory;
77: use PHPStan\Type\Generic\TemplateTypeMap;
78: use PHPStan\Type\Generic\TemplateTypeScope;
79: use PHPStan\Type\Generic\TemplateTypeVariance;
80: use PHPStan\Type\Helper\GetTemplateTypeType;
81: use PHPStan\Type\IntegerRangeType;
82: use PHPStan\Type\IntegerType;
83: use PHPStan\Type\IntersectionType;
84: use PHPStan\Type\IterableType;
85: use PHPStan\Type\KeyOfType;
86: use PHPStan\Type\MixedType;
87: use PHPStan\Type\NewObjectType;
88: use PHPStan\Type\NonAcceptingNeverType;
89: use PHPStan\Type\NonexistentParentClassType;
90: use PHPStan\Type\NullType;
91: use PHPStan\Type\ObjectShapeType;
92: use PHPStan\Type\ObjectType;
93: use PHPStan\Type\ObjectWithoutClassType;
94: use PHPStan\Type\OffsetAccessType;
95: use PHPStan\Type\ResourceType;
96: use PHPStan\Type\StaticType;
97: use PHPStan\Type\StaticTypeFactory;
98: use PHPStan\Type\StringAlwaysAcceptingObjectWithToStringType;
99: use PHPStan\Type\StringType;
100: use PHPStan\Type\ThisType;
101: use PHPStan\Type\Type;
102: use PHPStan\Type\TypeAliasResolver;
103: use PHPStan\Type\TypeAliasResolverProvider;
104: use PHPStan\Type\TypeCombinator;
105: use PHPStan\Type\TypeUtils;
106: use PHPStan\Type\UnionType;
107: use PHPStan\Type\ValueOfType;
108: use PHPStan\Type\VoidType;
109: use Traversable;
110: use function array_key_exists;
111: use function array_map;
112: use function array_values;
113: use function count;
114: use function explode;
115: use function get_class;
116: use function in_array;
117: use function max;
118: use function min;
119: use function preg_match;
120: use function preg_quote;
121: use function str_contains;
122: use function str_replace;
123: use function str_starts_with;
124: use function strtolower;
125: use function substr;
126:
127: final class TypeNodeResolver
128: {
129:
130: /** @var array<string, true> */
131: private array $genericTypeResolvingStack = [];
132:
133: public function __construct(
134: private TypeNodeResolverExtensionRegistryProvider $extensionRegistryProvider,
135: private ReflectionProvider\ReflectionProviderProvider $reflectionProviderProvider,
136: private TypeAliasResolverProvider $typeAliasResolverProvider,
137: private ConstantResolver $constantResolver,
138: private InitializerExprTypeResolver $initializerExprTypeResolver,
139: )
140: {
141: }
142:
143: /** @api */
144: public function resolve(TypeNode $typeNode, NameScope $nameScope): Type
145: {
146: foreach ($this->extensionRegistryProvider->getRegistry()->getExtensions() as $extension) {
147: $type = $extension->resolve($typeNode, $nameScope);
148: if ($type !== null) {
149: return $type;
150: }
151: }
152:
153: if ($typeNode instanceof IdentifierTypeNode) {
154: return $this->resolveIdentifierTypeNode($typeNode, $nameScope);
155:
156: } elseif ($typeNode instanceof ThisTypeNode) {
157: return $this->resolveThisTypeNode($typeNode, $nameScope);
158:
159: } elseif ($typeNode instanceof NullableTypeNode) {
160: return $this->resolveNullableTypeNode($typeNode, $nameScope);
161:
162: } elseif ($typeNode instanceof UnionTypeNode) {
163: return $this->resolveUnionTypeNode($typeNode, $nameScope);
164:
165: } elseif ($typeNode instanceof IntersectionTypeNode) {
166: return $this->resolveIntersectionTypeNode($typeNode, $nameScope);
167:
168: } elseif ($typeNode instanceof ConditionalTypeNode) {
169: return $this->resolveConditionalTypeNode($typeNode, $nameScope);
170:
171: } elseif ($typeNode instanceof ConditionalTypeForParameterNode) {
172: return $this->resolveConditionalTypeForParameterNode($typeNode, $nameScope);
173:
174: } elseif ($typeNode instanceof ArrayTypeNode) {
175: return $this->resolveArrayTypeNode($typeNode, $nameScope);
176:
177: } elseif ($typeNode instanceof GenericTypeNode) {
178: return $this->resolveGenericTypeNode($typeNode, $nameScope);
179:
180: } elseif ($typeNode instanceof CallableTypeNode) {
181: return $this->resolveCallableTypeNode($typeNode, $nameScope);
182:
183: } elseif ($typeNode instanceof ArrayShapeNode) {
184: return $this->resolveArrayShapeNode($typeNode, $nameScope);
185: } elseif ($typeNode instanceof ObjectShapeNode) {
186: return $this->resolveObjectShapeNode($typeNode, $nameScope);
187: } elseif ($typeNode instanceof ConstTypeNode) {
188: return $this->resolveConstTypeNode($typeNode, $nameScope);
189: } elseif ($typeNode instanceof OffsetAccessTypeNode) {
190: return $this->resolveOffsetAccessNode($typeNode, $nameScope);
191: } elseif ($typeNode instanceof InvalidTypeNode) {
192: return new MixedType(true);
193: }
194:
195: return new ErrorType();
196: }
197:
198: private function resolveIdentifierTypeNode(IdentifierTypeNode $typeNode, NameScope $nameScope): Type
199: {
200: switch (strtolower($typeNode->name)) {
201: case 'int':
202: return new IntegerType();
203:
204: case 'integer':
205: $type = $this->tryResolvePseudoTypeClassType($typeNode, $nameScope);
206:
207: if ($type !== null) {
208: return $type;
209: }
210:
211: return new IntegerType();
212:
213: case 'positive-int':
214: return IntegerRangeType::fromInterval(1, null);
215:
216: case 'negative-int':
217: return IntegerRangeType::fromInterval(null, -1);
218:
219: case 'non-positive-int':
220: return IntegerRangeType::fromInterval(null, 0);
221:
222: case 'non-negative-int':
223: return IntegerRangeType::fromInterval(0, null);
224:
225: case 'non-zero-int':
226: return new UnionType([
227: IntegerRangeType::fromInterval(null, -1),
228: IntegerRangeType::fromInterval(1, null),
229: ]);
230:
231: case 'string':
232: return new StringType();
233:
234: case 'lowercase-string':
235: return new IntersectionType([new StringType(), new AccessoryLowercaseStringType()]);
236:
237: case 'uppercase-string':
238: return new IntersectionType([new StringType(), new AccessoryUppercaseStringType()]);
239:
240: case 'literal-string':
241: return new IntersectionType([new StringType(), new AccessoryLiteralStringType()]);
242:
243: case 'class-string':
244: case 'interface-string':
245: case 'trait-string':
246: return new ClassStringType();
247:
248: case 'enum-string':
249: return new GenericClassStringType(new ObjectType('UnitEnum'));
250:
251: case 'callable-string':
252: return new IntersectionType([new StringType(), new CallableType()]);
253:
254: case 'array-key':
255: return new BenevolentUnionType([new IntegerType(), new StringType()]);
256:
257: case 'scalar':
258: $type = $this->tryResolvePseudoTypeClassType($typeNode, $nameScope);
259:
260: if ($type !== null) {
261: return $type;
262: }
263:
264: return new UnionType([new IntegerType(), new FloatType(), new StringType(), new BooleanType()]);
265:
266: case 'empty-scalar':
267: return TypeCombinator::intersect(
268: new UnionType([new IntegerType(), new FloatType(), new StringType(), new BooleanType()]),
269: StaticTypeFactory::falsey(),
270: );
271:
272: case 'non-empty-scalar':
273: return TypeCombinator::remove(
274: new UnionType([new IntegerType(), new FloatType(), new StringType(), new BooleanType()]),
275: StaticTypeFactory::falsey(),
276: );
277:
278: case 'number':
279: $type = $this->tryResolvePseudoTypeClassType($typeNode, $nameScope);
280:
281: if ($type !== null) {
282: return $type;
283: }
284:
285: return new UnionType([new IntegerType(), new FloatType()]);
286:
287: case 'numeric':
288: $type = $this->tryResolvePseudoTypeClassType($typeNode, $nameScope);
289:
290: if ($type !== null) {
291: return $type;
292: }
293:
294: return new UnionType([
295: new IntegerType(),
296: new FloatType(),
297: new IntersectionType([
298: new StringType(),
299: new AccessoryNumericStringType(),
300: ]),
301: ]);
302:
303: case 'numeric-string':
304: return new IntersectionType([
305: new StringType(),
306: new AccessoryNumericStringType(),
307: ]);
308:
309: case 'non-empty-string':
310: return new IntersectionType([
311: new StringType(),
312: new AccessoryNonEmptyStringType(),
313: ]);
314:
315: case 'non-empty-lowercase-string':
316: return new IntersectionType([
317: new StringType(),
318: new AccessoryNonEmptyStringType(),
319: new AccessoryLowercaseStringType(),
320: ]);
321:
322: case 'non-empty-uppercase-string':
323: return new IntersectionType([
324: new StringType(),
325: new AccessoryNonEmptyStringType(),
326: new AccessoryUppercaseStringType(),
327: ]);
328:
329: case 'truthy-string':
330: case 'non-falsy-string':
331: return new IntersectionType([
332: new StringType(),
333: new AccessoryNonFalsyStringType(),
334: ]);
335:
336: case 'non-empty-literal-string':
337: return new IntersectionType([
338: new StringType(),
339: new AccessoryNonEmptyStringType(),
340: new AccessoryLiteralStringType(),
341: ]);
342:
343: case 'bool':
344: return new BooleanType();
345:
346: case 'boolean':
347: $type = $this->tryResolvePseudoTypeClassType($typeNode, $nameScope);
348:
349: if ($type !== null) {
350: return $type;
351: }
352:
353: return new BooleanType();
354:
355: case 'true':
356: return new ConstantBooleanType(true);
357:
358: case 'false':
359: return new ConstantBooleanType(false);
360:
361: case 'null':
362: return new NullType();
363:
364: case 'float':
365: return new FloatType();
366:
367: case 'double':
368: $type = $this->tryResolvePseudoTypeClassType($typeNode, $nameScope);
369:
370: if ($type !== null) {
371: return $type;
372: }
373:
374: return new FloatType();
375:
376: case 'array':
377: case 'associative-array':
378: return new ArrayType(new MixedType(), new MixedType());
379:
380: case 'non-empty-array':
381: return TypeCombinator::intersect(
382: new ArrayType(new MixedType(), new MixedType()),
383: new NonEmptyArrayType(),
384: );
385:
386: case 'iterable':
387: return new IterableType(new MixedType(), new MixedType());
388:
389: case 'callable':
390: return new CallableType();
391:
392: case 'pure-callable':
393: return new CallableType(null, null, true, null, null, [], TrinaryLogic::createYes());
394:
395: case 'pure-closure':
396: return ClosureType::createPure();
397:
398: case 'resource':
399: $type = $this->tryResolvePseudoTypeClassType($typeNode, $nameScope);
400:
401: if ($type !== null) {
402: return $type;
403: }
404:
405: return new ResourceType();
406:
407: case 'open-resource':
408: case 'closed-resource':
409: return new ResourceType();
410:
411: case 'mixed':
412: return new MixedType(true);
413:
414: case 'non-empty-mixed':
415: return new MixedType(true, StaticTypeFactory::falsey());
416:
417: case 'void':
418: return new VoidType();
419:
420: case 'object':
421: return new ObjectWithoutClassType();
422:
423: case 'callable-object':
424: return new IntersectionType([new ObjectWithoutClassType(), new CallableType()]);
425:
426: case 'callable-array':
427: return new IntersectionType([new ArrayType(new MixedType(), new MixedType()), new CallableType()]);
428:
429: case 'never':
430: case 'noreturn':
431: $type = $this->tryResolvePseudoTypeClassType($typeNode, $nameScope);
432:
433: if ($type !== null) {
434: return $type;
435: }
436:
437: return new NonAcceptingNeverType();
438:
439: case 'never-return':
440: case 'never-returns':
441: case 'no-return':
442: return new NonAcceptingNeverType();
443:
444: case 'list':
445: return TypeCombinator::intersect(new ArrayType(IntegerRangeType::createAllGreaterThanOrEqualTo(0), new MixedType()), new AccessoryArrayListType());
446: case 'non-empty-list':
447: return TypeCombinator::intersect(
448: new ArrayType(IntegerRangeType::createAllGreaterThanOrEqualTo(0), new MixedType()),
449: new NonEmptyArrayType(),
450: new AccessoryArrayListType(),
451: );
452:
453: case 'empty':
454: $type = $this->tryResolvePseudoTypeClassType($typeNode, $nameScope);
455: if ($type !== null) {
456: return $type;
457: }
458:
459: return StaticTypeFactory::falsey();
460: case '__stringandstringable':
461: return new StringAlwaysAcceptingObjectWithToStringType();
462: }
463:
464: if ($nameScope->getClassName() !== null) {
465: switch (strtolower($typeNode->name)) {
466: case 'self':
467: return new ObjectType($nameScope->getClassName());
468:
469: case 'static':
470: if ($this->getReflectionProvider()->hasClass($nameScope->getClassName())) {
471: $classReflection = $this->getReflectionProvider()->getClass($nameScope->getClassName());
472:
473: return new StaticType($classReflection);
474: }
475:
476: return new ErrorType();
477: case 'parent':
478: if ($this->getReflectionProvider()->hasClass($nameScope->getClassName())) {
479: $classReflection = $this->getReflectionProvider()->getClass($nameScope->getClassName());
480: if ($classReflection->getParentClass() !== null) {
481: return new ObjectType($classReflection->getParentClass()->getName());
482: }
483: }
484:
485: return new NonexistentParentClassType();
486: }
487: }
488:
489: if (!$nameScope->shouldBypassTypeAliases()) {
490: $typeAlias = $this->getTypeAliasResolver()->resolveTypeAlias($typeNode->name, $nameScope);
491: if ($typeAlias !== null) {
492: return $typeAlias;
493: }
494: }
495:
496: $templateType = $nameScope->resolveTemplateTypeName($typeNode->name);
497: if ($templateType !== null) {
498: return $templateType;
499: }
500:
501: $stringName = $nameScope->resolveStringName($typeNode->name);
502: if (str_contains($stringName, '-') && !str_starts_with($stringName, 'OCI-')) {
503: return new ErrorType();
504: }
505:
506: if ($this->mightBeConstant($typeNode->name) && !$this->getReflectionProvider()->hasClass($stringName)) {
507: $constType = $this->tryResolveConstant($typeNode->name, $nameScope);
508: if ($constType !== null) {
509: return $constType;
510: }
511: }
512:
513: return new ObjectType($stringName);
514: }
515:
516: private function mightBeConstant(string $name): bool
517: {
518: return preg_match('((?:^|\\\\)[A-Z_][A-Z0-9_]*$)', $name) > 0;
519: }
520:
521: private function tryResolveConstant(string $name, NameScope $nameScope): ?Type
522: {
523: foreach ($nameScope->resolveConstantNames($name) as $constName) {
524: $nameNode = new Name\FullyQualified(explode('\\', $constName));
525: $constType = $this->constantResolver->resolveConstant($nameNode, null);
526: if ($constType !== null) {
527: return $constType;
528: }
529: }
530:
531: return null;
532: }
533:
534: private function tryResolvePseudoTypeClassType(IdentifierTypeNode $typeNode, NameScope $nameScope): ?Type
535: {
536: if ($nameScope->hasUseAlias($typeNode->name)) {
537: return new ObjectType($nameScope->resolveStringName($typeNode->name));
538: }
539:
540: if ($nameScope->getNamespace() === null) {
541: return null;
542: }
543:
544: $className = $nameScope->resolveStringName($typeNode->name);
545:
546: if ($this->getReflectionProvider()->hasClass($className)) {
547: return new ObjectType($className);
548: }
549:
550: return null;
551: }
552:
553: private function resolveThisTypeNode(ThisTypeNode $typeNode, NameScope $nameScope): Type
554: {
555: $className = $nameScope->getClassName();
556: if ($className !== null) {
557: if ($this->getReflectionProvider()->hasClass($className)) {
558: return new ThisType($this->getReflectionProvider()->getClass($className));
559: }
560: }
561:
562: return new ErrorType();
563: }
564:
565: private function resolveNullableTypeNode(NullableTypeNode $typeNode, NameScope $nameScope): Type
566: {
567: return TypeCombinator::union($this->resolve($typeNode->type, $nameScope), new NullType());
568: }
569:
570: private function resolveUnionTypeNode(UnionTypeNode $typeNode, NameScope $nameScope): Type
571: {
572: $iterableTypeNodes = [];
573: $otherTypeNodes = [];
574:
575: foreach ($typeNode->types as $innerTypeNode) {
576: if ($innerTypeNode instanceof ArrayTypeNode) {
577: $iterableTypeNodes[] = $innerTypeNode->type;
578: } else {
579: $otherTypeNodes[] = $innerTypeNode;
580: }
581: }
582:
583: $otherTypeTypes = $this->resolveMultiple($otherTypeNodes, $nameScope);
584: if (count($iterableTypeNodes) > 0) {
585: $arrayTypeTypes = $this->resolveMultiple($iterableTypeNodes, $nameScope);
586: $arrayTypeType = TypeCombinator::union(...$arrayTypeTypes);
587: $addArray = true;
588:
589: foreach ($otherTypeTypes as &$type) {
590: if (!$type->isIterable()->yes() || !$type->getIterableValueType()->isSuperTypeOf($arrayTypeType)->yes()) {
591: continue;
592: }
593:
594: if ($type instanceof ObjectType && !$type instanceof GenericObjectType) {
595: $type = new IntersectionType([$type, new IterableType(new MixedType(), $arrayTypeType)]);
596: } elseif ($type instanceof ArrayType) {
597: $type = new ArrayType(new MixedType(), $arrayTypeType);
598: } elseif ($type instanceof ConstantArrayType) {
599: $type = new ArrayType(new MixedType(), $arrayTypeType);
600: } elseif ($type instanceof IterableType) {
601: $type = new IterableType(new MixedType(), $arrayTypeType);
602: } else {
603: continue;
604: }
605:
606: $addArray = false;
607: }
608:
609: if ($addArray) {
610: $otherTypeTypes[] = new ArrayType(new MixedType(), $arrayTypeType);
611: }
612: }
613:
614: return TypeCombinator::union(...$otherTypeTypes);
615: }
616:
617: private function resolveIntersectionTypeNode(IntersectionTypeNode $typeNode, NameScope $nameScope): Type
618: {
619: $types = $this->resolveMultiple($typeNode->types, $nameScope);
620: return TypeCombinator::intersect(...$types);
621: }
622:
623: private function resolveConditionalTypeNode(ConditionalTypeNode $typeNode, NameScope $nameScope): Type
624: {
625: return new ConditionalType(
626: $this->resolve($typeNode->subjectType, $nameScope),
627: $this->resolve($typeNode->targetType, $nameScope),
628: $this->resolve($typeNode->if, $nameScope),
629: $this->resolve($typeNode->else, $nameScope),
630: $typeNode->negated,
631: );
632: }
633:
634: private function resolveConditionalTypeForParameterNode(ConditionalTypeForParameterNode $typeNode, NameScope $nameScope): Type
635: {
636: return new ConditionalTypeForParameter(
637: $typeNode->parameterName,
638: $this->resolve($typeNode->targetType, $nameScope),
639: $this->resolve($typeNode->if, $nameScope),
640: $this->resolve($typeNode->else, $nameScope),
641: $typeNode->negated,
642: );
643: }
644:
645: private function resolveArrayTypeNode(ArrayTypeNode $typeNode, NameScope $nameScope): Type
646: {
647: $itemType = $this->resolve($typeNode->type, $nameScope);
648: return new ArrayType(new BenevolentUnionType([new IntegerType(), new StringType()]), $itemType);
649: }
650:
651: private function resolveGenericTypeNode(GenericTypeNode $typeNode, NameScope $nameScope): Type
652: {
653: $mainTypeName = strtolower($typeNode->type->name);
654: $genericTypes = $this->resolveMultiple($typeNode->genericTypes, $nameScope);
655: $variances = array_map(
656: static function (string $variance): TemplateTypeVariance {
657: switch ($variance) {
658: case GenericTypeNode::VARIANCE_INVARIANT:
659: return TemplateTypeVariance::createInvariant();
660: case GenericTypeNode::VARIANCE_COVARIANT:
661: return TemplateTypeVariance::createCovariant();
662: case GenericTypeNode::VARIANCE_CONTRAVARIANT:
663: return TemplateTypeVariance::createContravariant();
664: case GenericTypeNode::VARIANCE_BIVARIANT:
665: return TemplateTypeVariance::createBivariant();
666: }
667: },
668: $typeNode->variances,
669: );
670:
671: if (in_array($mainTypeName, ['array', 'non-empty-array'], true)) {
672: if (count($genericTypes) === 1) { // array<ValueType>
673: $arrayType = new ArrayType(new BenevolentUnionType([new IntegerType(), new StringType()]), $genericTypes[0]);
674: } elseif (count($genericTypes) === 2) { // array<KeyType, ValueType>
675: $keyType = TypeCombinator::intersect($genericTypes[0]->toArrayKey(), new UnionType([
676: new IntegerType(),
677: new StringType(),
678: ]))->toArrayKey();
679: $finiteTypes = $keyType->getFiniteTypes();
680: if (
681: count($finiteTypes) === 1
682: && ($finiteTypes[0] instanceof ConstantStringType || $finiteTypes[0] instanceof ConstantIntegerType)
683: ) {
684: $arrayBuilder = ConstantArrayTypeBuilder::createEmpty();
685: $arrayBuilder->setOffsetValueType($finiteTypes[0], $genericTypes[1], true);
686: $arrayType = $arrayBuilder->getArray();
687: } else {
688: $arrayType = new ArrayType($keyType, $genericTypes[1]);
689: }
690: } else {
691: return new ErrorType();
692: }
693:
694: if ($mainTypeName === 'non-empty-array') {
695: return TypeCombinator::intersect($arrayType, new NonEmptyArrayType());
696: }
697:
698: return $arrayType;
699: } elseif (in_array($mainTypeName, ['list', 'non-empty-list'], true)) {
700: if (count($genericTypes) === 1) { // list<ValueType>
701: $listType = TypeCombinator::intersect(new ArrayType(IntegerRangeType::createAllGreaterThanOrEqualTo(0), $genericTypes[0]), new AccessoryArrayListType());
702: if ($mainTypeName === 'non-empty-list') {
703: return TypeCombinator::intersect($listType, new NonEmptyArrayType());
704: }
705:
706: return $listType;
707: }
708:
709: return new ErrorType();
710: } elseif ($mainTypeName === 'iterable') {
711: if (count($genericTypes) === 1) { // iterable<ValueType>
712: return new IterableType(new MixedType(true), $genericTypes[0]);
713:
714: }
715:
716: if (count($genericTypes) === 2) { // iterable<KeyType, ValueType>
717: return new IterableType($genericTypes[0], $genericTypes[1]);
718: }
719: } elseif (in_array($mainTypeName, ['class-string', 'interface-string'], true)) {
720: if (count($genericTypes) === 1) {
721: $genericType = $genericTypes[0];
722: if ($genericType->isObject()->yes() || $genericType instanceof MixedType) {
723: return new GenericClassStringType($genericType);
724: }
725: }
726:
727: return new ErrorType();
728: } elseif ($mainTypeName === 'enum-string') {
729: if (count($genericTypes) === 1) {
730: $genericType = $genericTypes[0];
731: return new GenericClassStringType(TypeCombinator::intersect($genericType, new ObjectType('UnitEnum')));
732: }
733:
734: return new ErrorType();
735: } elseif ($mainTypeName === 'int') {
736: if (count($genericTypes) === 2) { // int<min, max>, int<1, 3>
737:
738: if ($genericTypes[0] instanceof ConstantIntegerType) {
739: $min = $genericTypes[0]->getValue();
740: } elseif ($typeNode->genericTypes[0] instanceof IdentifierTypeNode && $typeNode->genericTypes[0]->name === 'min') {
741: $min = null;
742: } else {
743: return new ErrorType();
744: }
745:
746: if ($genericTypes[1] instanceof ConstantIntegerType) {
747: $max = $genericTypes[1]->getValue();
748: } elseif ($typeNode->genericTypes[1] instanceof IdentifierTypeNode && $typeNode->genericTypes[1]->name === 'max') {
749: $max = null;
750: } else {
751: return new ErrorType();
752: }
753:
754: return IntegerRangeType::fromInterval($min, $max);
755: }
756: } elseif ($mainTypeName === 'key-of') {
757: if (count($genericTypes) === 1) { // key-of<ValueType>
758: $type = new KeyOfType($genericTypes[0]);
759: return $type->isResolvable() ? $type->resolve() : $type;
760: }
761:
762: return new ErrorType();
763: } elseif ($mainTypeName === 'value-of') {
764: if (count($genericTypes) === 1) { // value-of<ValueType>
765: $type = new ValueOfType($genericTypes[0]);
766:
767: return $type->isResolvable() ? $type->resolve() : $type;
768: }
769:
770: return new ErrorType();
771: } elseif ($mainTypeName === 'int-mask-of') {
772: if (count($genericTypes) === 1) { // int-mask-of<Class::CONST*>
773: $maskType = $this->expandIntMaskToType($genericTypes[0]);
774: if ($maskType !== null) {
775: return $maskType;
776: }
777: }
778:
779: return new ErrorType();
780: } elseif ($mainTypeName === 'int-mask') {
781: if (count($genericTypes) > 0) { // int-mask<1, 2, 4>
782: $maskType = $this->expandIntMaskToType(TypeCombinator::union(...$genericTypes));
783: if ($maskType !== null) {
784: return $maskType;
785: }
786: }
787:
788: return new ErrorType();
789: } elseif ($mainTypeName === '__benevolent') {
790: if (count($genericTypes) === 1) {
791: return TypeUtils::toBenevolentUnion($genericTypes[0]);
792: }
793: return new ErrorType();
794: } elseif ($mainTypeName === 'template-type') {
795: if (count($genericTypes) === 3) {
796: $result = [];
797: /** @var class-string $ancestorClassName */
798: foreach ($genericTypes[1]->getObjectClassNames() as $ancestorClassName) {
799: foreach ($genericTypes[2]->getConstantStrings() as $templateTypeName) {
800: $result[] = new GetTemplateTypeType($genericTypes[0], $ancestorClassName, $templateTypeName->getValue());
801: }
802: }
803:
804: return TypeCombinator::union(...$result);
805: }
806:
807: return new ErrorType();
808: } elseif ($mainTypeName === 'new') {
809: if (count($genericTypes) === 1) {
810: $type = new NewObjectType($genericTypes[0]);
811: return $type->isResolvable() ? $type->resolve() : $type;
812: }
813:
814: return new ErrorType();
815: } elseif ($mainTypeName === 'static') {
816: if ($nameScope->getClassName() !== null && $this->getReflectionProvider()->hasClass($nameScope->getClassName())) {
817: $classReflection = $this->getReflectionProvider()->getClass($nameScope->getClassName());
818:
819: return new GenericStaticType($classReflection, $genericTypes, null, $variances);
820: }
821:
822: return new ErrorType();
823: }
824:
825: $mainType = $this->resolveIdentifierTypeNode($typeNode->type, $nameScope);
826: $mainTypeObjectClassNames = $mainType->getObjectClassNames();
827: if (count($mainTypeObjectClassNames) > 1) {
828: if ($mainType instanceof TemplateType) {
829: return new ErrorType();
830: }
831: throw new ShouldNotHappenException();
832: }
833: $mainTypeClassName = $mainTypeObjectClassNames[0] ?? null;
834:
835: if ($mainTypeClassName !== null) {
836: if (!$this->getReflectionProvider()->hasClass($mainTypeClassName)) {
837: return new GenericObjectType($mainTypeClassName, $genericTypes, null, null, $variances);
838: }
839:
840: $classReflection = $this->getReflectionProvider()->getClass($mainTypeClassName);
841: if ($classReflection->isGeneric()) {
842: $templateTypes = array_values($classReflection->getTemplateTypeMap()->getTypes());
843: for ($i = count($genericTypes), $templateTypesCount = count($templateTypes); $i < $templateTypesCount; $i++) {
844: $templateType = $templateTypes[$i];
845: if (!$templateType instanceof TemplateType || $templateType->getDefault() === null) {
846: continue;
847: }
848: $genericTypes[] = $templateType->getDefault();
849: }
850:
851: if (in_array($mainTypeClassName, [
852: Traversable::class,
853: IteratorAggregate::class,
854: Iterator::class,
855: ], true)) {
856: if (count($genericTypes) === 1) {
857: return new GenericObjectType($mainTypeClassName, [
858: new MixedType(true),
859: $genericTypes[0],
860: ], null, null, [
861: TemplateTypeVariance::createInvariant(),
862: $variances[0],
863: ]);
864: }
865:
866: if (count($genericTypes) === 2) {
867: return new GenericObjectType($mainTypeClassName, [
868: $genericTypes[0],
869: $genericTypes[1],
870: ], null, null, [
871: $variances[0],
872: $variances[1],
873: ]);
874: }
875: }
876: if ($mainTypeClassName === Generator::class) {
877: if (count($genericTypes) === 1) {
878: $mixed = new MixedType(true);
879: return new GenericObjectType($mainTypeClassName, [
880: $mixed,
881: $genericTypes[0],
882: $mixed,
883: $mixed,
884: ], null, null, [
885: TemplateTypeVariance::createInvariant(),
886: $variances[0],
887: TemplateTypeVariance::createInvariant(),
888: TemplateTypeVariance::createInvariant(),
889: ]);
890: }
891:
892: if (count($genericTypes) === 2) {
893: $mixed = new MixedType(true);
894: return new GenericObjectType($mainTypeClassName, [
895: $genericTypes[0],
896: $genericTypes[1],
897: $mixed,
898: $mixed,
899: ], null, null, [
900: $variances[0],
901: $variances[1],
902: TemplateTypeVariance::createInvariant(),
903: TemplateTypeVariance::createInvariant(),
904: ]);
905: }
906: }
907:
908: if (!$mainType->isIterable()->yes()) {
909: return new GenericObjectType($mainTypeClassName, $genericTypes, null, null, $variances);
910: }
911:
912: if (
913: count($genericTypes) !== 1
914: || $classReflection->getTemplateTypeMap()->count() === 1
915: ) {
916: return new GenericObjectType($mainTypeClassName, $genericTypes, null, null, $variances);
917: }
918: }
919: }
920:
921: if ($mainType->isIterable()->yes()) {
922: if ($mainTypeClassName !== null) {
923: if (isset($this->genericTypeResolvingStack[$mainTypeClassName])) {
924: return new ErrorType();
925: }
926:
927: $this->genericTypeResolvingStack[$mainTypeClassName] = true;
928: }
929:
930: try {
931: if (count($genericTypes) === 1) { // Foo<ValueType>
932: return TypeCombinator::intersect(
933: $mainType,
934: new IterableType(new MixedType(true), $genericTypes[0]),
935: );
936: }
937:
938: if (count($genericTypes) === 2) { // Foo<KeyType, ValueType>
939: return TypeCombinator::intersect(
940: $mainType,
941: new IterableType($genericTypes[0], $genericTypes[1]),
942: );
943: }
944: } finally {
945: if ($mainTypeClassName !== null) {
946: unset($this->genericTypeResolvingStack[$mainTypeClassName]);
947: }
948: }
949: }
950:
951: if ($mainTypeClassName !== null) {
952: return new GenericObjectType($mainTypeClassName, $genericTypes, null, null, $variances);
953: }
954:
955: return new ErrorType();
956: }
957:
958: private function resolveCallableTypeNode(CallableTypeNode $typeNode, NameScope $nameScope): Type
959: {
960: $templateTags = [];
961:
962: if (count($typeNode->templateTypes) > 0) {
963: foreach ($typeNode->templateTypes as $templateType) {
964: $templateTags[$templateType->name] = new TemplateTag(
965: $templateType->name,
966: $templateType->bound !== null
967: ? $this->resolve($templateType->bound, $nameScope)
968: : new MixedType(),
969: $templateType->default !== null
970: ? $this->resolve($templateType->default, $nameScope)
971: : null,
972: TemplateTypeVariance::createInvariant(),
973: );
974: }
975: $templateTypeScope = TemplateTypeScope::createWithAnonymousFunction();
976:
977: $templateTypeMap = new TemplateTypeMap(array_map(
978: static fn (TemplateTag $tag): Type => TemplateTypeFactory::fromTemplateTag($templateTypeScope, $tag),
979: $templateTags,
980: ));
981:
982: $nameScope = $nameScope->withTemplateTypeMap($templateTypeMap);
983: } else {
984: $templateTypeMap = TemplateTypeMap::createEmpty();
985: }
986:
987: $mainType = $this->resolve($typeNode->identifier, $nameScope);
988:
989: $isVariadic = false;
990: $parameters = array_values(array_map(
991: function (CallableTypeParameterNode $parameterNode) use ($nameScope, &$isVariadic): NativeParameterReflection {
992: $isVariadic = $isVariadic || $parameterNode->isVariadic;
993: $parameterName = $parameterNode->parameterName;
994: if (str_starts_with($parameterName, '$')) {
995: $parameterName = substr($parameterName, 1);
996: }
997:
998: return new NativeParameterReflection(
999: $parameterName,
1000: $parameterNode->isOptional || $parameterNode->isVariadic,
1001: $this->resolve($parameterNode->type, $nameScope),
1002: $parameterNode->isReference ? PassedByReference::createCreatesNewVariable() : PassedByReference::createNo(),
1003: $parameterNode->isVariadic,
1004: null,
1005: );
1006: },
1007: $typeNode->parameters,
1008: ));
1009:
1010: $returnType = $this->resolve($typeNode->returnType, $nameScope);
1011:
1012: if ($mainType instanceof CallableType) {
1013: $pure = $mainType->isPure();
1014: if ($pure->yes() && $returnType->isVoid()->yes()) {
1015: return new ErrorType();
1016: }
1017:
1018: return new CallableType($parameters, $returnType, $isVariadic, $templateTypeMap, null, $templateTags, $pure);
1019:
1020: } elseif (
1021: $mainType instanceof ObjectType
1022: && $mainType->getClassName() === Closure::class
1023: ) {
1024: return new ClosureType($parameters, $returnType, $isVariadic, $templateTypeMap, null, null, $templateTags, [], [
1025: new SimpleImpurePoint(
1026: 'functionCall',
1027: 'call to a Closure',
1028: false,
1029: ),
1030: ]);
1031: } elseif ($mainType instanceof ClosureType) {
1032: $closure = new ClosureType($parameters, $returnType, $isVariadic, $templateTypeMap, null, null, $templateTags, [], $mainType->getImpurePoints(), $mainType->getInvalidateExpressions(), $mainType->getUsedVariables(), $mainType->acceptsNamedArguments());
1033: if ($closure->isPure()->yes() && $returnType->isVoid()->yes()) {
1034: return new ErrorType();
1035: }
1036:
1037: return $closure;
1038: }
1039:
1040: return new ErrorType();
1041: }
1042:
1043: private function resolveArrayShapeNode(ArrayShapeNode $typeNode, NameScope $nameScope): Type
1044: {
1045: $builder = ConstantArrayTypeBuilder::createEmpty();
1046: if (count($typeNode->items) > ConstantArrayTypeBuilder::ARRAY_COUNT_LIMIT) {
1047: $builder->degradeToGeneralArray(true);
1048: }
1049:
1050: foreach ($typeNode->items as $itemNode) {
1051: $offsetType = null;
1052: if ($itemNode->keyName instanceof ConstExprIntegerNode) {
1053: $offsetType = new ConstantIntegerType((int) $itemNode->keyName->value);
1054: } elseif ($itemNode->keyName instanceof IdentifierTypeNode) {
1055: $offsetType = new ConstantStringType($itemNode->keyName->name);
1056: } elseif ($itemNode->keyName instanceof ConstExprStringNode) {
1057: $offsetType = new ConstantStringType($itemNode->keyName->value);
1058: } elseif ($itemNode->keyName !== null) {
1059: throw new ShouldNotHappenException('Unsupported key node type: ' . get_class($itemNode->keyName));
1060: }
1061: $builder->setOffsetValueType($offsetType, $this->resolve($itemNode->valueType, $nameScope), $itemNode->optional);
1062: }
1063:
1064: $arrayType = $builder->getArray();
1065: if (in_array($typeNode->kind, [
1066: ArrayShapeNode::KIND_LIST,
1067: ArrayShapeNode::KIND_NON_EMPTY_LIST,
1068: ], true)) {
1069: $arrayType = TypeCombinator::intersect($arrayType, new AccessoryArrayListType());
1070: }
1071:
1072: if (in_array($typeNode->kind, [
1073: ArrayShapeNode::KIND_NON_EMPTY_ARRAY,
1074: ArrayShapeNode::KIND_NON_EMPTY_LIST,
1075: ], true)) {
1076: $arrayType = TypeCombinator::intersect($arrayType, new NonEmptyArrayType());
1077: }
1078:
1079: return $arrayType;
1080: }
1081:
1082: private function resolveObjectShapeNode(ObjectShapeNode $typeNode, NameScope $nameScope): Type
1083: {
1084: $properties = [];
1085: $optionalProperties = [];
1086: foreach ($typeNode->items as $itemNode) {
1087: if ($itemNode->keyName instanceof IdentifierTypeNode) {
1088: $propertyName = $itemNode->keyName->name;
1089: } elseif ($itemNode->keyName instanceof ConstExprStringNode) {
1090: $propertyName = $itemNode->keyName->value;
1091: }
1092:
1093: if ($itemNode->optional) {
1094: $optionalProperties[] = $propertyName;
1095: }
1096:
1097: $properties[$propertyName] = $this->resolve($itemNode->valueType, $nameScope);
1098: }
1099:
1100: return new ObjectShapeType($properties, $optionalProperties);
1101: }
1102:
1103: private function resolveConstTypeNode(ConstTypeNode $typeNode, NameScope $nameScope): Type
1104: {
1105: $constExpr = $typeNode->constExpr;
1106: if ($constExpr instanceof ConstExprArrayNode) {
1107: throw new ShouldNotHappenException(); // we prefer array shapes
1108: }
1109:
1110: if (
1111: $constExpr instanceof ConstExprFalseNode
1112: || $constExpr instanceof ConstExprTrueNode
1113: || $constExpr instanceof ConstExprNullNode
1114: ) {
1115: throw new ShouldNotHappenException(); // we prefer IdentifierTypeNode
1116: }
1117:
1118: if ($constExpr instanceof ConstFetchNode) {
1119: if ($constExpr->className === '') {
1120: throw new ShouldNotHappenException(); // global constant should get parsed as class name in IdentifierTypeNode
1121: }
1122:
1123: if ($nameScope->getClassName() !== null) {
1124: switch (strtolower($constExpr->className)) {
1125: case 'static':
1126: case 'self':
1127: $className = $nameScope->getClassName();
1128: break;
1129:
1130: case 'parent':
1131: if ($this->getReflectionProvider()->hasClass($nameScope->getClassName())) {
1132: $classReflection = $this->getReflectionProvider()->getClass($nameScope->getClassName());
1133: if ($classReflection->getParentClass() === null) {
1134: return new ErrorType();
1135:
1136: }
1137:
1138: $className = $classReflection->getParentClass()->getName();
1139: }
1140: break;
1141: }
1142: }
1143:
1144: if (!isset($className)) {
1145: $className = $nameScope->resolveStringName($constExpr->className);
1146: }
1147:
1148: if (!$this->getReflectionProvider()->hasClass($className)) {
1149: return new ErrorType();
1150: }
1151:
1152: $classReflection = $this->getReflectionProvider()->getClass($className);
1153:
1154: $constantName = $constExpr->name;
1155: if (Strings::contains($constantName, '*')) {
1156: // convert * into .*? and escape everything else so the constants can be matched against the pattern
1157: $pattern = '{^' . str_replace('\\*', '.*?', preg_quote($constantName)) . '$}D';
1158: $constantTypes = [];
1159: foreach ($classReflection->getNativeReflection()->getReflectionConstants() as $reflectionConstant) {
1160: $classConstantName = $reflectionConstant->getName();
1161: if (Strings::match($classConstantName, $pattern) === null) {
1162: continue;
1163: }
1164:
1165: if ($classReflection->isEnum() && $classReflection->hasEnumCase($classConstantName)) {
1166: $constantTypes[] = new EnumCaseObjectType($classReflection->getName(), $classConstantName);
1167: continue;
1168: }
1169:
1170: $declaringClassName = $reflectionConstant->getDeclaringClass()->getName();
1171: if (!$this->getReflectionProvider()->hasClass($declaringClassName)) {
1172: continue;
1173: }
1174:
1175: $constantTypes[] = $this->initializerExprTypeResolver->getType(
1176: $reflectionConstant->getValueExpression(),
1177: InitializerExprContext::fromClassReflection(
1178: $this->getReflectionProvider()->getClass($declaringClassName),
1179: ),
1180: );
1181: }
1182:
1183: if (count($constantTypes) === 0) {
1184: return new ErrorType();
1185: }
1186:
1187: return TypeCombinator::union(...$constantTypes);
1188: }
1189:
1190: if (!$classReflection->hasConstant($constantName)) {
1191: return new ErrorType();
1192: }
1193:
1194: if ($classReflection->isEnum() && $classReflection->hasEnumCase($constantName)) {
1195: return new EnumCaseObjectType($classReflection->getName(), $constantName);
1196: }
1197:
1198: $reflectionConstant = $classReflection->getNativeReflection()->getReflectionConstant($constantName);
1199: if ($reflectionConstant === false) {
1200: return new ErrorType();
1201: }
1202: $declaringClass = $reflectionConstant->getDeclaringClass();
1203:
1204: return $this->initializerExprTypeResolver->getType($reflectionConstant->getValueExpression(), InitializerExprContext::fromClass($declaringClass->getName(), $declaringClass->getFileName() ?: null));
1205: }
1206:
1207: if ($constExpr instanceof ConstExprFloatNode) {
1208: return new ConstantFloatType((float) $constExpr->value);
1209: }
1210:
1211: if ($constExpr instanceof ConstExprIntegerNode) {
1212: return new ConstantIntegerType((int) $constExpr->value);
1213: }
1214:
1215: if ($constExpr instanceof ConstExprStringNode) {
1216: return new ConstantStringType($constExpr->value);
1217: }
1218:
1219: return new ErrorType();
1220: }
1221:
1222: private function resolveOffsetAccessNode(OffsetAccessTypeNode $typeNode, NameScope $nameScope): Type
1223: {
1224: $type = $this->resolve($typeNode->type, $nameScope);
1225: $offset = $this->resolve($typeNode->offset, $nameScope);
1226:
1227: if ($type->isOffsetAccessible()->no() || $type->hasOffsetValueType($offset)->no()) {
1228: return new ErrorType();
1229: }
1230:
1231: return new OffsetAccessType($type, $offset);
1232: }
1233:
1234: private function expandIntMaskToType(Type $type): ?Type
1235: {
1236: $ints = array_map(static fn (ConstantIntegerType $type) => $type->getValue(), TypeUtils::getConstantIntegers($type));
1237: if (count($ints) === 0) {
1238: return null;
1239: }
1240:
1241: $values = [];
1242:
1243: foreach ($ints as $int) {
1244: if ($int !== 0 && !array_key_exists($int, $values)) {
1245: foreach ($values as $value) {
1246: $computedValue = $value | $int;
1247: $values[$computedValue] = $computedValue;
1248: }
1249: }
1250:
1251: $values[$int] = $int;
1252: }
1253:
1254: $values[0] = 0;
1255:
1256: $min = min($values);
1257: $max = max($values);
1258:
1259: if ($max - $min === count($values) - 1) {
1260: return IntegerRangeType::fromInterval($min, $max);
1261: }
1262:
1263: return TypeCombinator::union(...array_map(static fn ($value) => new ConstantIntegerType($value), $values));
1264: }
1265:
1266: /**
1267: * @api
1268: * @param TypeNode[] $typeNodes
1269: * @return list<Type>
1270: */
1271: public function resolveMultiple(array $typeNodes, NameScope $nameScope): array
1272: {
1273: $types = [];
1274: foreach ($typeNodes as $typeNode) {
1275: $types[] = $this->resolve($typeNode, $nameScope);
1276: }
1277:
1278: return $types;
1279: }
1280:
1281: private function getReflectionProvider(): ReflectionProvider
1282: {
1283: return $this->reflectionProviderProvider->getReflectionProvider();
1284: }
1285:
1286: private function getTypeAliasResolver(): TypeAliasResolver
1287: {
1288: return $this->typeAliasResolverProvider->getTypeAliasResolver();
1289: }
1290:
1291: }
1292: