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