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: $originalKey = $genericTypes[0];
709: if ($this->reportUnsafeArrayStringKeyCasting === ReportUnsafeArrayStringKeyCastingToggle::PREVENT) {
710: $originalKey = TypeTraverser::map($originalKey, static function (Type $type, callable $traverse) {
711: if ($type instanceof UnionType || $type instanceof IntersectionType) {
712: return $traverse($type);
713: }
714:
715: if ($type instanceof StringType) {
716: return TypeCombinator::intersect($type, new AccessoryDecimalIntegerStringType(inverse: true));
717: }
718:
719: return $type;
720: });
721: }
722: $keyType = TypeCombinator::intersect($originalKey->toArrayKey(), new UnionType([
723: new IntegerType(),
724: new StringType(),
725: ]))->toArrayKey();
726: $finiteTypes = $keyType->getFiniteTypes();
727: if (
728: count($finiteTypes) === 1
729: && ($finiteTypes[0] instanceof ConstantStringType || $finiteTypes[0] instanceof ConstantIntegerType)
730: ) {
731: $arrayBuilder = ConstantArrayTypeBuilder::createEmpty();
732: $arrayBuilder->setOffsetValueType($finiteTypes[0], $genericTypes[1], true);
733: $arrayType = $arrayBuilder->getArray();
734: } else {
735: $arrayType = new ArrayType($keyType, $genericTypes[1]);
736: }
737: } else {
738: return new ErrorType();
739: }
740:
741: if ($mainTypeName === 'non-empty-array') {
742: return TypeCombinator::intersect($arrayType, new NonEmptyArrayType());
743: }
744:
745: return $arrayType;
746: } elseif (in_array($mainTypeName, ['list', 'non-empty-list'], true)) {
747: if (count($genericTypes) === 1) { // list<ValueType>
748: $listType = new IntersectionType([new ArrayType(IntegerRangeType::createAllGreaterThanOrEqualTo(0), $genericTypes[0]), new AccessoryArrayListType()]);
749: if ($mainTypeName === 'non-empty-list') {
750: return TypeCombinator::intersect($listType, new NonEmptyArrayType());
751: }
752:
753: return $listType;
754: }
755:
756: return new ErrorType();
757: } elseif ($mainTypeName === 'iterable') {
758: if (count($genericTypes) === 1) { // iterable<ValueType>
759: return new IterableType(new MixedType(true), $genericTypes[0]);
760:
761: }
762:
763: if (count($genericTypes) === 2) { // iterable<KeyType, ValueType>
764: return new IterableType($genericTypes[0], $genericTypes[1]);
765: }
766: } elseif (in_array($mainTypeName, ['class-string', 'interface-string'], true)) {
767: if (count($genericTypes) === 1) {
768: $genericType = $genericTypes[0];
769: if ($genericType->isObject()->yes() || $genericType instanceof MixedType) {
770: return new GenericClassStringType($genericType);
771: }
772: }
773:
774: return new ErrorType();
775: } elseif ($mainTypeName === 'enum-string') {
776: if (count($genericTypes) === 1) {
777: $genericType = $genericTypes[0];
778: return new GenericClassStringType(TypeCombinator::intersect($genericType, new ObjectType('UnitEnum')));
779: }
780:
781: return new ErrorType();
782: } elseif ($mainTypeName === 'int') {
783: if (count($genericTypes) === 2) { // int<min, max>, int<1, 3>
784:
785: if ($genericTypes[0] instanceof ConstantIntegerType) {
786: $min = $genericTypes[0]->getValue();
787: } elseif ($typeNode->genericTypes[0] instanceof IdentifierTypeNode && $typeNode->genericTypes[0]->name === 'min') {
788: $min = null;
789: } else {
790: return new ErrorType();
791: }
792:
793: if ($genericTypes[1] instanceof ConstantIntegerType) {
794: $max = $genericTypes[1]->getValue();
795: } elseif ($typeNode->genericTypes[1] instanceof IdentifierTypeNode && $typeNode->genericTypes[1]->name === 'max') {
796: $max = null;
797: } else {
798: return new ErrorType();
799: }
800:
801: return IntegerRangeType::fromInterval($min, $max);
802: }
803: } elseif ($mainTypeName === 'key-of') {
804: if (count($genericTypes) === 1) { // key-of<ValueType>
805: $type = new KeyOfType($genericTypes[0]);
806: return $type->isResolvable() ? $type->resolve() : $type;
807: }
808:
809: return new ErrorType();
810: } elseif ($mainTypeName === 'value-of') {
811: if (count($genericTypes) === 1) { // value-of<ValueType>
812: $type = new ValueOfType($genericTypes[0]);
813:
814: return $type->isResolvable() ? $type->resolve() : $type;
815: }
816:
817: return new ErrorType();
818: } elseif ($mainTypeName === 'int-mask-of') {
819: if (count($genericTypes) === 1) { // int-mask-of<Class::CONST*>
820: $maskType = $this->expandIntMaskToType($genericTypes[0]);
821: if ($maskType !== null) {
822: return $maskType;
823: }
824: }
825:
826: return new ErrorType();
827: } elseif ($mainTypeName === 'int-mask') {
828: if (count($genericTypes) > 0) { // int-mask<1, 2, 4>
829: $maskType = $this->expandIntMaskToType(TypeCombinator::union(...$genericTypes));
830: if ($maskType !== null) {
831: return $maskType;
832: }
833: }
834:
835: return new ErrorType();
836: } elseif ($mainTypeName === '__benevolent') {
837: if (count($genericTypes) === 1) {
838: return TypeUtils::toBenevolentUnion($genericTypes[0]);
839: }
840: return new ErrorType();
841: } elseif ($mainTypeName === 'template-type') {
842: if (count($genericTypes) === 3) {
843: $result = [];
844: /** @var class-string $ancestorClassName */
845: foreach ($genericTypes[1]->getObjectClassNames() as $ancestorClassName) {
846: foreach ($genericTypes[2]->getConstantStrings() as $templateTypeName) {
847: $result[] = new GetTemplateTypeType($genericTypes[0], $ancestorClassName, $templateTypeName->getValue());
848: }
849: }
850:
851: return TypeCombinator::union(...$result);
852: }
853:
854: return new ErrorType();
855: } elseif ($mainTypeName === 'new') {
856: if (count($genericTypes) === 1) {
857: $type = new NewObjectType($genericTypes[0]);
858: return $type->isResolvable() ? $type->resolve() : $type;
859: }
860:
861: return new ErrorType();
862: } elseif ($mainTypeName === 'static') {
863: if ($nameScope->getClassName() !== null && $this->getReflectionProvider()->hasClass($nameScope->getClassName())) {
864: $classReflection = $this->getReflectionProvider()->getClass($nameScope->getClassName());
865:
866: return new GenericStaticType($classReflection, $genericTypes, null, $variances);
867: }
868:
869: return new ErrorType();
870: }
871:
872: $mainType = $this->resolveIdentifierTypeNode($typeNode->type, $nameScope);
873: $mainTypeObjectClassNames = $mainType->getObjectClassNames();
874: if (count($mainTypeObjectClassNames) > 1) {
875: if ($mainType instanceof TemplateType) {
876: return new ErrorType();
877: }
878: throw new ShouldNotHappenException();
879: }
880: $mainTypeClassName = $mainTypeObjectClassNames[0] ?? null;
881:
882: if ($mainTypeClassName !== null) {
883: if (!$this->getReflectionProvider()->hasClass($mainTypeClassName)) {
884: return new GenericObjectType($mainTypeClassName, $genericTypes, variances: $variances);
885: }
886:
887: $classReflection = $this->getReflectionProvider()->getClass($mainTypeClassName);
888: if ($classReflection->isGeneric()) {
889: $templateTypes = array_values($classReflection->getTemplateTypeMap()->getTypes());
890: for ($i = count($genericTypes), $templateTypesCount = count($templateTypes); $i < $templateTypesCount; $i++) {
891: $templateType = $templateTypes[$i];
892: if (!$templateType instanceof TemplateType || $templateType->getDefault() === null) {
893: continue;
894: }
895: $genericTypes[] = $templateType->getDefault();
896: }
897:
898: if (in_array($mainTypeClassName, [
899: Traversable::class,
900: IteratorAggregate::class,
901: Iterator::class,
902: ], true)) {
903: if (count($genericTypes) === 1) {
904: return new GenericObjectType($mainTypeClassName, [
905: new MixedType(true),
906: $genericTypes[0],
907: ], variances: [
908: TemplateTypeVariance::createInvariant(),
909: $variances[0],
910: ]);
911: }
912:
913: if (count($genericTypes) === 2) {
914: return new GenericObjectType($mainTypeClassName, [
915: $genericTypes[0],
916: $genericTypes[1],
917: ], variances: [
918: $variances[0],
919: $variances[1],
920: ]);
921: }
922: }
923: if ($mainTypeClassName === Generator::class) {
924: if (count($genericTypes) === 1) {
925: $mixed = new MixedType(true);
926: return new GenericObjectType($mainTypeClassName, [
927: $mixed,
928: $genericTypes[0],
929: $mixed,
930: $mixed,
931: ], variances: [
932: TemplateTypeVariance::createInvariant(),
933: $variances[0],
934: TemplateTypeVariance::createInvariant(),
935: TemplateTypeVariance::createInvariant(),
936: ]);
937: }
938:
939: if (count($genericTypes) === 2) {
940: $mixed = new MixedType(true);
941: return new GenericObjectType($mainTypeClassName, [
942: $genericTypes[0],
943: $genericTypes[1],
944: $mixed,
945: $mixed,
946: ], variances: [
947: $variances[0],
948: $variances[1],
949: TemplateTypeVariance::createInvariant(),
950: TemplateTypeVariance::createInvariant(),
951: ]);
952: }
953: }
954:
955: if (!$mainType->isIterable()->yes()) {
956: return new GenericObjectType($mainTypeClassName, $genericTypes, variances: $variances);
957: }
958:
959: if (
960: count($genericTypes) !== 1
961: || $classReflection->getTemplateTypeMap()->count() === 1
962: ) {
963: return new GenericObjectType($mainTypeClassName, $genericTypes, variances: $variances);
964: }
965: }
966: }
967:
968: if ($mainType->isIterable()->yes()) {
969: if ($mainTypeClassName !== null) {
970: if (isset($this->genericTypeResolvingStack[$mainTypeClassName])) {
971: return new ErrorType();
972: }
973:
974: $this->genericTypeResolvingStack[$mainTypeClassName] = true;
975: }
976:
977: try {
978: if (count($genericTypes) === 1) { // Foo<ValueType>
979: return TypeCombinator::intersect(
980: $mainType,
981: new IterableType(new MixedType(true), $genericTypes[0]),
982: );
983: }
984:
985: if (count($genericTypes) === 2) { // Foo<KeyType, ValueType>
986: return TypeCombinator::intersect(
987: $mainType,
988: new IterableType($genericTypes[0], $genericTypes[1]),
989: );
990: }
991: } finally {
992: if ($mainTypeClassName !== null) {
993: unset($this->genericTypeResolvingStack[$mainTypeClassName]);
994: }
995: }
996: }
997:
998: if ($mainTypeClassName !== null) {
999: return new GenericObjectType($mainTypeClassName, $genericTypes, variances: $variances);
1000: }
1001:
1002: return new ErrorType();
1003: }
1004:
1005: private function resolveCallableTypeNode(CallableTypeNode $typeNode, NameScope $nameScope): Type
1006: {
1007: $templateTags = [];
1008:
1009: if (count($typeNode->templateTypes) > 0) {
1010: foreach ($typeNode->templateTypes as $templateType) {
1011: $templateTags[$templateType->name] = new TemplateTag(
1012: $templateType->name,
1013: $templateType->bound !== null
1014: ? $this->resolve($templateType->bound, $nameScope)
1015: : new MixedType(),
1016: $templateType->default !== null
1017: ? $this->resolve($templateType->default, $nameScope)
1018: : null,
1019: TemplateTypeVariance::createInvariant(),
1020: );
1021: }
1022: $templateTypeScope = TemplateTypeScope::createWithAnonymousFunction();
1023:
1024: $templateTypeMap = new TemplateTypeMap(array_map(
1025: static fn (TemplateTag $tag): Type => TemplateTypeFactory::fromTemplateTag($templateTypeScope, $tag),
1026: $templateTags,
1027: ));
1028:
1029: $nameScope = $nameScope->withTemplateTypeMap($templateTypeMap, $templateTags);
1030: } else {
1031: $templateTypeMap = TemplateTypeMap::createEmpty();
1032: }
1033:
1034: $mainType = $this->resolve($typeNode->identifier, $nameScope);
1035:
1036: $isVariadic = false;
1037: $parameters = array_values(array_map(
1038: function (CallableTypeParameterNode $parameterNode) use ($nameScope, &$isVariadic): NativeParameterReflection {
1039: $isVariadic = $isVariadic || $parameterNode->isVariadic;
1040: $parameterName = $parameterNode->parameterName;
1041: if (str_starts_with($parameterName, '$')) {
1042: $parameterName = substr($parameterName, 1);
1043: }
1044:
1045: return new NativeParameterReflection(
1046: $parameterName,
1047: $parameterNode->isOptional || $parameterNode->isVariadic,
1048: $this->resolve($parameterNode->type, $nameScope),
1049: $parameterNode->isReference ? PassedByReference::createCreatesNewVariable() : PassedByReference::createNo(),
1050: $parameterNode->isVariadic,
1051: defaultValue: null,
1052: );
1053: },
1054: $typeNode->parameters,
1055: ));
1056:
1057: $returnType = $this->resolve($typeNode->returnType, $nameScope);
1058:
1059: if ($mainType instanceof CallableType) {
1060: $pure = $mainType->isPure();
1061: if ($pure->yes() && $returnType->isVoid()->yes()) {
1062: return new ErrorType();
1063: }
1064:
1065: return new CallableType($parameters, $returnType, $isVariadic, $templateTypeMap, templateTags: $templateTags, isPure: $pure);
1066:
1067: } elseif (
1068: $mainType instanceof ObjectType
1069: && $mainType->getClassName() === Closure::class
1070: ) {
1071: return new ClosureType($parameters, $returnType, $isVariadic, $templateTypeMap, templateTags: $templateTags, impurePoints: [
1072: new SimpleImpurePoint(
1073: 'functionCall',
1074: 'call to a Closure',
1075: false,
1076: ),
1077: ]);
1078: } elseif ($mainType instanceof ClosureType) {
1079: $closure = new ClosureType($parameters, $returnType, $isVariadic, $templateTypeMap, templateTags: $templateTags, impurePoints: $mainType->getImpurePoints(), invalidateExpressions: $mainType->getInvalidateExpressions(), usedVariables: $mainType->getUsedVariables(), acceptsNamedArguments: $mainType->acceptsNamedArguments(), mustUseReturnValue: $mainType->mustUseReturnValue());
1080: if ($closure->isPure()->yes() && $returnType->isVoid()->yes()) {
1081: return new ErrorType();
1082: }
1083:
1084: return $closure;
1085: }
1086:
1087: return new ErrorType();
1088: }
1089:
1090: private function resolveArrayShapeNode(ArrayShapeNode $typeNode, NameScope $nameScope): Type
1091: {
1092: $builder = ConstantArrayTypeBuilder::createEmpty();
1093: $builder->disableArrayDegradation();
1094:
1095: foreach ($typeNode->items as $itemNode) {
1096: if ($itemNode->valueType instanceof CallableTypeNode) {
1097: $builder->disableClosureDegradation();
1098: }
1099:
1100: $offsetType = $this->resolveArrayShapeOffsetType($itemNode, $nameScope);
1101: $builder->setOffsetValueType($offsetType, $this->resolve($itemNode->valueType, $nameScope), $itemNode->optional);
1102: }
1103:
1104: $arrayType = $builder->getArray();
1105:
1106: $accessories = [];
1107: if (in_array($typeNode->kind, [
1108: ArrayShapeNode::KIND_LIST,
1109: ArrayShapeNode::KIND_NON_EMPTY_LIST,
1110: ], true)) {
1111: $accessories[] = new AccessoryArrayListType();
1112: }
1113:
1114: if (in_array($typeNode->kind, [
1115: ArrayShapeNode::KIND_NON_EMPTY_ARRAY,
1116: ArrayShapeNode::KIND_NON_EMPTY_LIST,
1117: ], true)) {
1118: $accessories[] = new NonEmptyArrayType();
1119: }
1120:
1121: if (count($accessories) > 0) {
1122: return TypeCombinator::intersect($arrayType, ...$accessories);
1123: }
1124:
1125: return $arrayType;
1126: }
1127:
1128: private function resolveArrayShapeOffsetType(ArrayShapeItemNode $itemNode, NameScope $nameScope): ?Type
1129: {
1130: if ($itemNode->keyName instanceof ConstExprIntegerNode) {
1131: return new ConstantIntegerType((int) $itemNode->keyName->value);
1132: } elseif ($itemNode->keyName instanceof IdentifierTypeNode) {
1133: return new ConstantStringType($itemNode->keyName->name);
1134: } elseif ($itemNode->keyName instanceof ConstExprStringNode) {
1135: return new ConstantStringType($itemNode->keyName->value);
1136: } elseif ($itemNode->keyName instanceof ConstFetchNode) {
1137: $constExpr = $itemNode->keyName;
1138: if ($constExpr->className === '') {
1139: throw new ShouldNotHappenException(); // global constant should get parsed as class name in IdentifierTypeNode
1140: }
1141:
1142: $isStatic = false;
1143: if ($nameScope->getClassName() !== null) {
1144: switch (strtolower($constExpr->className)) {
1145: case 'static':
1146: $className = $nameScope->getClassName();
1147: $isStatic = true;
1148: break;
1149:
1150: case 'self':
1151: $className = $nameScope->getClassName();
1152: break;
1153:
1154: case 'parent':
1155: if ($this->getReflectionProvider()->hasClass($nameScope->getClassName())) {
1156: $classReflection = $this->getReflectionProvider()->getClass($nameScope->getClassName());
1157: if ($classReflection->getParentClass() === null) {
1158: return new ErrorType();
1159:
1160: }
1161:
1162: $className = $classReflection->getParentClass()->getName();
1163: }
1164: break;
1165: }
1166: }
1167:
1168: if (!isset($className)) {
1169: $className = $nameScope->resolveStringName($constExpr->className);
1170: }
1171:
1172: if (!$this->getReflectionProvider()->hasClass($className)) {
1173: return new ErrorType();
1174: }
1175: $classReflection = $this->getReflectionProvider()->getClass($className);
1176:
1177: if ($isStatic && $classReflection->isFinal()) {
1178: $isStatic = false;
1179: }
1180:
1181: $constantName = $constExpr->name;
1182: if (!$classReflection->hasConstant($constantName)) {
1183: return new ErrorType();
1184: }
1185:
1186: if ($isStatic) {
1187: return new ClassConstantAccessType(new StaticType($classReflection), $constantName);
1188: }
1189:
1190: $reflectionConstant = $classReflection->getNativeReflection()->getReflectionConstant($constantName);
1191: if ($reflectionConstant === false) {
1192: return new ErrorType();
1193: }
1194: $declaringClass = $reflectionConstant->getDeclaringClass();
1195:
1196: return $this->initializerExprTypeResolver->getType($reflectionConstant->getValueExpression(), InitializerExprContext::fromClass($declaringClass->getName(), $declaringClass->getFileName() ?: null));
1197: } elseif ($itemNode->keyName !== null) {
1198: throw new ShouldNotHappenException('Unsupported key node type: ' . get_class($itemNode->keyName));
1199: }
1200:
1201: return null;
1202: }
1203:
1204: private function resolveObjectShapeNode(ObjectShapeNode $typeNode, NameScope $nameScope): Type
1205: {
1206: $properties = [];
1207: $optionalProperties = [];
1208: foreach ($typeNode->items as $itemNode) {
1209: if ($itemNode->keyName instanceof IdentifierTypeNode) {
1210: $propertyName = $itemNode->keyName->name;
1211: } elseif ($itemNode->keyName instanceof ConstExprStringNode) {
1212: $propertyName = $itemNode->keyName->value;
1213: }
1214:
1215: if ($itemNode->optional) {
1216: $optionalProperties[] = $propertyName;
1217: }
1218:
1219: $properties[$propertyName] = $this->resolve($itemNode->valueType, $nameScope);
1220: }
1221:
1222: return new ObjectShapeType($properties, $optionalProperties);
1223: }
1224:
1225: private function resolveConstTypeNode(ConstTypeNode $typeNode, NameScope $nameScope): Type
1226: {
1227: $constExpr = $typeNode->constExpr;
1228: if ($constExpr instanceof ConstExprArrayNode) {
1229: throw new ShouldNotHappenException(); // we prefer array shapes
1230: }
1231:
1232: if (
1233: $constExpr instanceof ConstExprFalseNode
1234: || $constExpr instanceof ConstExprTrueNode
1235: || $constExpr instanceof ConstExprNullNode
1236: ) {
1237: throw new ShouldNotHappenException(); // we prefer IdentifierTypeNode
1238: }
1239:
1240: if ($constExpr instanceof ConstFetchNode) {
1241: if ($constExpr->className === '') {
1242: throw new ShouldNotHappenException(); // global constant should get parsed as class name in IdentifierTypeNode
1243: }
1244:
1245: $isStatic = false;
1246: if ($nameScope->getClassName() !== null) {
1247: switch (strtolower($constExpr->className)) {
1248: case 'static':
1249: $className = $nameScope->getClassName();
1250: $isStatic = true;
1251: break;
1252:
1253: case 'self':
1254: $className = $nameScope->getClassName();
1255: break;
1256:
1257: case 'parent':
1258: if ($this->getReflectionProvider()->hasClass($nameScope->getClassName())) {
1259: $classReflection = $this->getReflectionProvider()->getClass($nameScope->getClassName());
1260: if ($classReflection->getParentClass() === null) {
1261: return new ErrorType();
1262:
1263: }
1264:
1265: $className = $classReflection->getParentClass()->getName();
1266: }
1267: break;
1268: }
1269: }
1270:
1271: if (!isset($className)) {
1272: $className = $nameScope->resolveStringName($constExpr->className);
1273: }
1274:
1275: if (!$this->getReflectionProvider()->hasClass($className)) {
1276: return new ErrorType();
1277: }
1278:
1279: $classReflection = $this->getReflectionProvider()->getClass($className);
1280:
1281: if ($isStatic && $classReflection->isFinal()) {
1282: $isStatic = false;
1283: }
1284:
1285: $constantName = $constExpr->name;
1286: if (Strings::contains($constantName, '*')) {
1287: // convert * into .*? and escape everything else so the constants can be matched against the pattern
1288: $pattern = '{^' . str_replace('\\*', '.*?', preg_quote($constantName)) . '$}D';
1289: $constantTypes = [];
1290: foreach ($classReflection->getNativeReflection()->getReflectionConstants() as $reflectionConstant) {
1291: $classConstantName = $reflectionConstant->getName();
1292: if (Strings::match($classConstantName, $pattern) === null) {
1293: continue;
1294: }
1295:
1296: if ($classReflection->isEnum() && $classReflection->hasEnumCase($classConstantName)) {
1297: $constantTypes[] = new EnumCaseObjectType($classReflection->getName(), $classConstantName);
1298: continue;
1299: }
1300:
1301: $declaringClassName = $reflectionConstant->getDeclaringClass()->getName();
1302: if (!$this->getReflectionProvider()->hasClass($declaringClassName)) {
1303: continue;
1304: }
1305:
1306: $constantTypes[] = $this->initializerExprTypeResolver->getType(
1307: $reflectionConstant->getValueExpression(),
1308: InitializerExprContext::fromClassReflection(
1309: $this->getReflectionProvider()->getClass($declaringClassName),
1310: ),
1311: );
1312: }
1313:
1314: if (count($constantTypes) === 0) {
1315: return new ErrorType();
1316: }
1317:
1318: return TypeCombinator::union(...$constantTypes);
1319: }
1320:
1321: if (!$classReflection->hasConstant($constantName)) {
1322: return new ErrorType();
1323: }
1324:
1325: if ($classReflection->isEnum() && $classReflection->hasEnumCase($constantName)) {
1326: return new EnumCaseObjectType($classReflection->getName(), $constantName);
1327: }
1328:
1329: if ($isStatic) {
1330: return new ClassConstantAccessType(new StaticType($classReflection), $constantName);
1331: }
1332:
1333: $reflectionConstant = $classReflection->getNativeReflection()->getReflectionConstant($constantName);
1334: if ($reflectionConstant === false) {
1335: return new ErrorType();
1336: }
1337: $declaringClass = $reflectionConstant->getDeclaringClass();
1338:
1339: return $this->initializerExprTypeResolver->getType($reflectionConstant->getValueExpression(), InitializerExprContext::fromClass($declaringClass->getName(), $declaringClass->getFileName() ?: null));
1340: }
1341:
1342: if ($constExpr instanceof ConstExprFloatNode) {
1343: return new ConstantFloatType((float) $constExpr->value);
1344: }
1345:
1346: if ($constExpr instanceof ConstExprIntegerNode) {
1347: return new ConstantIntegerType((int) $constExpr->value);
1348: }
1349:
1350: if ($constExpr instanceof ConstExprStringNode) {
1351: return new ConstantStringType($constExpr->value);
1352: }
1353:
1354: return new ErrorType();
1355: }
1356:
1357: private function resolveOffsetAccessNode(OffsetAccessTypeNode $typeNode, NameScope $nameScope): Type
1358: {
1359: $type = $this->resolve($typeNode->type, $nameScope);
1360: $offset = $this->resolve($typeNode->offset, $nameScope);
1361:
1362: if ($type->isOffsetAccessible()->no() || $type->hasOffsetValueType($offset)->no()) {
1363: return new ErrorType();
1364: }
1365:
1366: return new OffsetAccessType($type, $offset);
1367: }
1368:
1369: private function expandIntMaskToType(Type $type): ?Type
1370: {
1371: $ints = array_map(static fn (ConstantIntegerType $type) => $type->getValue(), TypeUtils::getConstantIntegers($type));
1372: if (count($ints) === 0) {
1373: return null;
1374: }
1375:
1376: $values = [];
1377:
1378: foreach ($ints as $int) {
1379: if ($int !== 0 && !array_key_exists($int, $values)) {
1380: foreach ($values as $value) {
1381: $computedValue = $value | $int;
1382: $values[$computedValue] = $computedValue;
1383: }
1384: }
1385:
1386: $values[$int] = $int;
1387: }
1388:
1389: $values[0] = 0;
1390:
1391: $min = min($values);
1392: $max = max($values);
1393:
1394: if ($max - $min === count($values) - 1) {
1395: return IntegerRangeType::fromInterval($min, $max);
1396: }
1397:
1398: if (count($values) > InitializerExprTypeResolver::CALCULATE_SCALARS_LIMIT) {
1399: return IntegerRangeType::fromInterval($min, $max);
1400: }
1401:
1402: return TypeCombinator::union(...array_map(static fn ($value) => new ConstantIntegerType($value), $values));
1403: }
1404:
1405: /**
1406: * @api
1407: * @param TypeNode[] $typeNodes
1408: * @return list<Type>
1409: */
1410: public function resolveMultiple(array $typeNodes, NameScope $nameScope): array
1411: {
1412: $types = [];
1413: foreach ($typeNodes as $typeNode) {
1414: $types[] = $this->resolve($typeNode, $nameScope);
1415: }
1416:
1417: return $types;
1418: }
1419:
1420: private function getReflectionProvider(): ReflectionProvider
1421: {
1422: return $this->reflectionProviderProvider->getReflectionProvider();
1423: }
1424:
1425: private function getTypeAliasResolver(): TypeAliasResolver
1426: {
1427: return $this->typeAliasResolverProvider->getTypeAliasResolver();
1428: }
1429:
1430: }
1431: