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