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