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