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