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