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