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