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