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