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