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\PhpDocParser\Ast\ConstExpr\ConstExprArrayNode;
14: use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprFalseNode;
15: use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprFloatNode;
16: use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprIntegerNode;
17: use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprNullNode;
18: use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprStringNode;
19: use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprTrueNode;
20: use PHPStan\PhpDocParser\Ast\ConstExpr\ConstFetchNode;
21: use PHPStan\PhpDocParser\Ast\Type\ArrayShapeNode;
22: use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode;
23: use PHPStan\PhpDocParser\Ast\Type\CallableTypeNode;
24: use PHPStan\PhpDocParser\Ast\Type\CallableTypeParameterNode;
25: use PHPStan\PhpDocParser\Ast\Type\ConditionalTypeForParameterNode;
26: use PHPStan\PhpDocParser\Ast\Type\ConditionalTypeNode;
27: use PHPStan\PhpDocParser\Ast\Type\ConstTypeNode;
28: use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode;
29: use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
30: use PHPStan\PhpDocParser\Ast\Type\IntersectionTypeNode;
31: use PHPStan\PhpDocParser\Ast\Type\NullableTypeNode;
32: use PHPStan\PhpDocParser\Ast\Type\OffsetAccessTypeNode;
33: use PHPStan\PhpDocParser\Ast\Type\ThisTypeNode;
34: use PHPStan\PhpDocParser\Ast\Type\TypeNode;
35: use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode;
36: use PHPStan\Reflection\InitializerExprContext;
37: use PHPStan\Reflection\InitializerExprTypeResolver;
38: use PHPStan\Reflection\Native\NativeParameterReflection;
39: use PHPStan\Reflection\PassedByReference;
40: use PHPStan\Reflection\ReflectionProvider;
41: use PHPStan\ShouldNotHappenException;
42: use PHPStan\Type\Accessory\AccessoryArrayListType;
43: use PHPStan\Type\Accessory\AccessoryLiteralStringType;
44: use PHPStan\Type\Accessory\AccessoryNonEmptyStringType;
45: use PHPStan\Type\Accessory\AccessoryNonFalsyStringType;
46: use PHPStan\Type\Accessory\AccessoryNumericStringType;
47: use PHPStan\Type\Accessory\NonEmptyArrayType;
48: use PHPStan\Type\ArrayType;
49: use PHPStan\Type\BenevolentUnionType;
50: use PHPStan\Type\BooleanType;
51: use PHPStan\Type\CallableType;
52: use PHPStan\Type\ClassStringType;
53: use PHPStan\Type\ClosureType;
54: use PHPStan\Type\ConditionalType;
55: use PHPStan\Type\ConditionalTypeForParameter;
56: use PHPStan\Type\Constant\ConstantArrayTypeBuilder;
57: use PHPStan\Type\Constant\ConstantBooleanType;
58: use PHPStan\Type\Constant\ConstantFloatType;
59: use PHPStan\Type\Constant\ConstantIntegerType;
60: use PHPStan\Type\Constant\ConstantStringType;
61: use PHPStan\Type\Enum\EnumCaseObjectType;
62: use PHPStan\Type\ErrorType;
63: use PHPStan\Type\FloatType;
64: use PHPStan\Type\Generic\GenericClassStringType;
65: use PHPStan\Type\Generic\GenericObjectType;
66: use PHPStan\Type\IntegerRangeType;
67: use PHPStan\Type\IntegerType;
68: use PHPStan\Type\IntersectionType;
69: use PHPStan\Type\IterableType;
70: use PHPStan\Type\KeyOfType;
71: use PHPStan\Type\MixedType;
72: use PHPStan\Type\NeverType;
73: use PHPStan\Type\NonexistentParentClassType;
74: use PHPStan\Type\NullType;
75: use PHPStan\Type\ObjectType;
76: use PHPStan\Type\ObjectWithoutClassType;
77: use PHPStan\Type\OffsetAccessType;
78: use PHPStan\Type\ResourceType;
79: use PHPStan\Type\StaticType;
80: use PHPStan\Type\StaticTypeFactory;
81: use PHPStan\Type\StringType;
82: use PHPStan\Type\ThisType;
83: use PHPStan\Type\Type;
84: use PHPStan\Type\TypeAliasResolver;
85: use PHPStan\Type\TypeAliasResolverProvider;
86: use PHPStan\Type\TypeCombinator;
87: use PHPStan\Type\TypeUtils;
88: use PHPStan\Type\TypeWithClassName;
89: use PHPStan\Type\UnionType;
90: use PHPStan\Type\ValueOfType;
91: use PHPStan\Type\VoidType;
92: use Traversable;
93: use function array_key_exists;
94: use function array_map;
95: use function count;
96: use function explode;
97: use function get_class;
98: use function in_array;
99: use function max;
100: use function min;
101: use function preg_match;
102: use function preg_quote;
103: use function str_replace;
104: use function strpos;
105: use function strtolower;
106: use function substr;
107:
108: class TypeNodeResolver
109: {
110:
111: public function __construct(
112: private TypeNodeResolverExtensionRegistryProvider $extensionRegistryProvider,
113: private ReflectionProvider\ReflectionProviderProvider $reflectionProviderProvider,
114: private TypeAliasResolverProvider $typeAliasResolverProvider,
115: private ConstantResolver $constantResolver,
116: private InitializerExprTypeResolver $initializerExprTypeResolver,
117: )
118: {
119: }
120:
121: /** @api */
122: public function resolve(TypeNode $typeNode, NameScope $nameScope): Type
123: {
124: foreach ($this->extensionRegistryProvider->getRegistry()->getExtensions() as $extension) {
125: $type = $extension->resolve($typeNode, $nameScope);
126: if ($type !== null) {
127: return $type;
128: }
129: }
130:
131: if ($typeNode instanceof IdentifierTypeNode) {
132: return $this->resolveIdentifierTypeNode($typeNode, $nameScope);
133:
134: } elseif ($typeNode instanceof ThisTypeNode) {
135: return $this->resolveThisTypeNode($typeNode, $nameScope);
136:
137: } elseif ($typeNode instanceof NullableTypeNode) {
138: return $this->resolveNullableTypeNode($typeNode, $nameScope);
139:
140: } elseif ($typeNode instanceof UnionTypeNode) {
141: return $this->resolveUnionTypeNode($typeNode, $nameScope);
142:
143: } elseif ($typeNode instanceof IntersectionTypeNode) {
144: return $this->resolveIntersectionTypeNode($typeNode, $nameScope);
145:
146: } elseif ($typeNode instanceof ConditionalTypeNode) {
147: return $this->resolveConditionalTypeNode($typeNode, $nameScope);
148:
149: } elseif ($typeNode instanceof ConditionalTypeForParameterNode) {
150: return $this->resolveConditionalTypeForParameterNode($typeNode, $nameScope);
151:
152: } elseif ($typeNode instanceof ArrayTypeNode) {
153: return $this->resolveArrayTypeNode($typeNode, $nameScope);
154:
155: } elseif ($typeNode instanceof GenericTypeNode) {
156: return $this->resolveGenericTypeNode($typeNode, $nameScope);
157:
158: } elseif ($typeNode instanceof CallableTypeNode) {
159: return $this->resolveCallableTypeNode($typeNode, $nameScope);
160:
161: } elseif ($typeNode instanceof ArrayShapeNode) {
162: return $this->resolveArrayShapeNode($typeNode, $nameScope);
163: } elseif ($typeNode instanceof ConstTypeNode) {
164: return $this->resolveConstTypeNode($typeNode, $nameScope);
165: } elseif ($typeNode instanceof OffsetAccessTypeNode) {
166: return $this->resolveOffsetAccessNode($typeNode, $nameScope);
167: }
168:
169: return new ErrorType();
170: }
171:
172: private function resolveIdentifierTypeNode(IdentifierTypeNode $typeNode, NameScope $nameScope): Type
173: {
174: switch (strtolower($typeNode->name)) {
175: case 'int':
176: case 'integer':
177: return new IntegerType();
178:
179: case 'positive-int':
180: return IntegerRangeType::fromInterval(1, null);
181:
182: case 'negative-int':
183: return IntegerRangeType::fromInterval(null, -1);
184:
185: case 'non-positive-int':
186: return IntegerRangeType::fromInterval(null, 0);
187:
188: case 'non-negative-int':
189: return IntegerRangeType::fromInterval(0, null);
190:
191: case 'string':
192: case 'lowercase-string':
193: return new StringType();
194:
195: case 'literal-string':
196: return new IntersectionType([new StringType(), new AccessoryLiteralStringType()]);
197:
198: case 'class-string':
199: case 'interface-string':
200: case 'trait-string':
201: return new ClassStringType();
202:
203: case 'callable-string':
204: return new IntersectionType([new StringType(), new CallableType()]);
205:
206: case 'array-key':
207: return new BenevolentUnionType([new IntegerType(), new StringType()]);
208:
209: case 'scalar':
210: $type = $this->tryResolvePseudoTypeClassType($typeNode, $nameScope);
211:
212: if ($type !== null) {
213: return $type;
214: }
215:
216: return new UnionType([new IntegerType(), new FloatType(), new StringType(), new BooleanType()]);
217:
218: case 'number':
219: $type = $this->tryResolvePseudoTypeClassType($typeNode, $nameScope);
220:
221: if ($type !== null) {
222: return $type;
223: }
224:
225: return new UnionType([new IntegerType(), new FloatType()]);
226:
227: case 'numeric':
228: $type = $this->tryResolvePseudoTypeClassType($typeNode, $nameScope);
229:
230: if ($type !== null) {
231: return $type;
232: }
233:
234: return new UnionType([
235: new IntegerType(),
236: new FloatType(),
237: new IntersectionType([
238: new StringType(),
239: new AccessoryNumericStringType(),
240: ]),
241: ]);
242:
243: case 'numeric-string':
244: return new IntersectionType([
245: new StringType(),
246: new AccessoryNumericStringType(),
247: ]);
248:
249: case 'non-empty-string':
250: case 'non-empty-lowercase-string':
251: return new IntersectionType([
252: new StringType(),
253: new AccessoryNonEmptyStringType(),
254: ]);
255:
256: case 'truthy-string':
257: case 'non-falsy-string':
258: return new IntersectionType([
259: new StringType(),
260: new AccessoryNonFalsyStringType(),
261: ]);
262:
263: case 'bool':
264: return new BooleanType();
265:
266: case 'boolean':
267: $type = $this->tryResolvePseudoTypeClassType($typeNode, $nameScope);
268:
269: if ($type !== null) {
270: return $type;
271: }
272:
273: return new BooleanType();
274:
275: case 'true':
276: return new ConstantBooleanType(true);
277:
278: case 'false':
279: return new ConstantBooleanType(false);
280:
281: case 'null':
282: return new NullType();
283:
284: case 'float':
285: return new FloatType();
286:
287: case 'double':
288: $type = $this->tryResolvePseudoTypeClassType($typeNode, $nameScope);
289:
290: if ($type !== null) {
291: return $type;
292: }
293:
294: return new FloatType();
295:
296: case 'array':
297: case 'associative-array':
298: return new ArrayType(new MixedType(), new MixedType());
299:
300: case 'non-empty-array':
301: return TypeCombinator::intersect(
302: new ArrayType(new MixedType(), new MixedType()),
303: new NonEmptyArrayType(),
304: );
305:
306: case 'iterable':
307: return new IterableType(new MixedType(), new MixedType());
308:
309: case 'callable':
310: return new CallableType();
311:
312: case 'resource':
313: $type = $this->tryResolvePseudoTypeClassType($typeNode, $nameScope);
314:
315: if ($type !== null) {
316: return $type;
317: }
318:
319: return new ResourceType();
320:
321: case 'mixed':
322: return new MixedType(true);
323:
324: case 'void':
325: return new VoidType();
326:
327: case 'object':
328: return new ObjectWithoutClassType();
329:
330: case 'callable-object':
331: return new IntersectionType([new ObjectWithoutClassType(), new CallableType()]);
332:
333: case 'never':
334: case 'noreturn':
335: $type = $this->tryResolvePseudoTypeClassType($typeNode, $nameScope);
336:
337: if ($type !== null) {
338: return $type;
339: }
340:
341: return new NeverType(true);
342:
343: case 'never-return':
344: case 'never-returns':
345: case 'no-return':
346: return new NeverType(true);
347:
348: case 'list':
349: return AccessoryArrayListType::intersectWith(new ArrayType(new IntegerType(), new MixedType()));
350: case 'non-empty-list':
351: return AccessoryArrayListType::intersectWith(TypeCombinator::intersect(
352: new ArrayType(new IntegerType(), new MixedType()),
353: new NonEmptyArrayType(),
354: ));
355:
356: case 'empty':
357: $type = $this->tryResolvePseudoTypeClassType($typeNode, $nameScope);
358: if ($type !== null) {
359: return $type;
360: }
361:
362: return StaticTypeFactory::falsey();
363: }
364:
365: if ($nameScope->getClassName() !== null) {
366: switch (strtolower($typeNode->name)) {
367: case 'self':
368: return new ObjectType($nameScope->getClassName());
369:
370: case 'static':
371: if ($this->getReflectionProvider()->hasClass($nameScope->getClassName())) {
372: $classReflection = $this->getReflectionProvider()->getClass($nameScope->getClassName());
373:
374: return new StaticType($classReflection);
375: }
376:
377: return new ErrorType();
378: case 'parent':
379: if ($this->getReflectionProvider()->hasClass($nameScope->getClassName())) {
380: $classReflection = $this->getReflectionProvider()->getClass($nameScope->getClassName());
381: if ($classReflection->getParentClass() !== null) {
382: return new ObjectType($classReflection->getParentClass()->getName());
383: }
384: }
385:
386: return new NonexistentParentClassType();
387: }
388: }
389:
390: if (!$nameScope->shouldBypassTypeAliases()) {
391: $typeAlias = $this->getTypeAliasResolver()->resolveTypeAlias($typeNode->name, $nameScope);
392: if ($typeAlias !== null) {
393: return $typeAlias;
394: }
395: }
396:
397: $templateType = $nameScope->resolveTemplateTypeName($typeNode->name);
398: if ($templateType !== null) {
399: return $templateType;
400: }
401:
402: $stringName = $nameScope->resolveStringName($typeNode->name);
403: if (strpos($stringName, '-') !== false && strpos($stringName, 'OCI-') !== 0) {
404: return new ErrorType();
405: }
406:
407: if ($this->mightBeConstant($typeNode->name) && !$this->getReflectionProvider()->hasClass($stringName)) {
408: $constType = $this->tryResolveConstant($typeNode->name, $nameScope);
409: if ($constType !== null) {
410: return $constType;
411: }
412: }
413:
414: return new ObjectType($stringName);
415: }
416:
417: private function mightBeConstant(string $name): bool
418: {
419: return preg_match('((?:^|\\\\)[A-Z_][A-Z0-9_]*$)', $name) > 0;
420: }
421:
422: private function tryResolveConstant(string $name, NameScope $nameScope): ?Type
423: {
424: foreach ($nameScope->resolveConstantNames($name) as $constName) {
425: $nameNode = new Name\FullyQualified(explode('\\', $constName));
426: $constType = $this->constantResolver->resolveConstant($nameNode, null);
427: if ($constType !== null) {
428: return $constType;
429: }
430: }
431:
432: return null;
433: }
434:
435: private function tryResolvePseudoTypeClassType(IdentifierTypeNode $typeNode, NameScope $nameScope): ?Type
436: {
437: if ($nameScope->hasUseAlias($typeNode->name)) {
438: return new ObjectType($nameScope->resolveStringName($typeNode->name));
439: }
440:
441: if ($nameScope->getNamespace() === null) {
442: return null;
443: }
444:
445: $className = $nameScope->resolveStringName($typeNode->name);
446:
447: if ($this->getReflectionProvider()->hasClass($className)) {
448: return new ObjectType($className);
449: }
450:
451: return null;
452: }
453:
454: private function resolveThisTypeNode(ThisTypeNode $typeNode, NameScope $nameScope): Type
455: {
456: $className = $nameScope->getClassName();
457: if ($className !== null) {
458: if ($this->getReflectionProvider()->hasClass($className)) {
459: return new ThisType($this->getReflectionProvider()->getClass($className));
460: }
461: }
462:
463: return new ErrorType();
464: }
465:
466: private function resolveNullableTypeNode(NullableTypeNode $typeNode, NameScope $nameScope): Type
467: {
468: return TypeCombinator::union($this->resolve($typeNode->type, $nameScope), new NullType());
469: }
470:
471: private function resolveUnionTypeNode(UnionTypeNode $typeNode, NameScope $nameScope): Type
472: {
473: $iterableTypeNodes = [];
474: $otherTypeNodes = [];
475:
476: foreach ($typeNode->types as $innerTypeNode) {
477: if ($innerTypeNode instanceof ArrayTypeNode) {
478: $iterableTypeNodes[] = $innerTypeNode->type;
479: } else {
480: $otherTypeNodes[] = $innerTypeNode;
481: }
482: }
483:
484: $otherTypeTypes = $this->resolveMultiple($otherTypeNodes, $nameScope);
485: if (count($iterableTypeNodes) > 0) {
486: $arrayTypeTypes = $this->resolveMultiple($iterableTypeNodes, $nameScope);
487: $arrayTypeType = TypeCombinator::union(...$arrayTypeTypes);
488: $addArray = true;
489:
490: foreach ($otherTypeTypes as &$type) {
491: if (!$type->isIterable()->yes() || !$type->getIterableValueType()->isSuperTypeOf($arrayTypeType)->yes()) {
492: continue;
493: }
494:
495: if ($type instanceof ObjectType) {
496: $type = new IntersectionType([$type, new IterableType(new MixedType(), $arrayTypeType)]);
497: } elseif ($type instanceof ArrayType) {
498: $type = new ArrayType(new MixedType(), $arrayTypeType);
499: } elseif ($type instanceof IterableType) {
500: $type = new IterableType(new MixedType(), $arrayTypeType);
501: } else {
502: continue;
503: }
504:
505: $addArray = false;
506: }
507:
508: if ($addArray) {
509: $otherTypeTypes[] = new ArrayType(new MixedType(), $arrayTypeType);
510: }
511: }
512:
513: return TypeCombinator::union(...$otherTypeTypes);
514: }
515:
516: private function resolveIntersectionTypeNode(IntersectionTypeNode $typeNode, NameScope $nameScope): Type
517: {
518: $types = $this->resolveMultiple($typeNode->types, $nameScope);
519: return TypeCombinator::intersect(...$types);
520: }
521:
522: private function resolveConditionalTypeNode(ConditionalTypeNode $typeNode, NameScope $nameScope): Type
523: {
524: return new ConditionalType(
525: $this->resolve($typeNode->subjectType, $nameScope),
526: $this->resolve($typeNode->targetType, $nameScope),
527: $this->resolve($typeNode->if, $nameScope),
528: $this->resolve($typeNode->else, $nameScope),
529: $typeNode->negated,
530: );
531: }
532:
533: private function resolveConditionalTypeForParameterNode(ConditionalTypeForParameterNode $typeNode, NameScope $nameScope): Type
534: {
535: return new ConditionalTypeForParameter(
536: $typeNode->parameterName,
537: $this->resolve($typeNode->targetType, $nameScope),
538: $this->resolve($typeNode->if, $nameScope),
539: $this->resolve($typeNode->else, $nameScope),
540: $typeNode->negated,
541: );
542: }
543:
544: private function resolveArrayTypeNode(ArrayTypeNode $typeNode, NameScope $nameScope): Type
545: {
546: $itemType = $this->resolve($typeNode->type, $nameScope);
547: return new ArrayType(new BenevolentUnionType([new IntegerType(), new StringType()]), $itemType);
548: }
549:
550: private function resolveGenericTypeNode(GenericTypeNode $typeNode, NameScope $nameScope): Type
551: {
552: $mainTypeName = strtolower($typeNode->type->name);
553: $genericTypes = $this->resolveMultiple($typeNode->genericTypes, $nameScope);
554:
555: if ($mainTypeName === 'array' || $mainTypeName === 'non-empty-array') {
556: if (count($genericTypes) === 1) { // array<ValueType>
557: $arrayType = new ArrayType(new BenevolentUnionType([new IntegerType(), new StringType()]), $genericTypes[0]);
558: } elseif (count($genericTypes) === 2) { // array<KeyType, ValueType>
559: $keyType = TypeCombinator::intersect($genericTypes[0], new UnionType([
560: new IntegerType(),
561: new StringType(),
562: ]));
563: $arrayType = new ArrayType($keyType->toArrayKey(), $genericTypes[1]);
564: } else {
565: return new ErrorType();
566: }
567:
568: if ($mainTypeName === 'non-empty-array') {
569: return TypeCombinator::intersect($arrayType, new NonEmptyArrayType());
570: }
571:
572: return $arrayType;
573: } elseif ($mainTypeName === 'list' || $mainTypeName === 'non-empty-list') {
574: if (count($genericTypes) === 1) { // list<ValueType>
575: $listType = AccessoryArrayListType::intersectWith(new ArrayType(new IntegerType(), $genericTypes[0]));
576: if ($mainTypeName === 'non-empty-list') {
577: return TypeCombinator::intersect($listType, new NonEmptyArrayType());
578: }
579:
580: return $listType;
581: }
582:
583: return new ErrorType();
584: } elseif ($mainTypeName === 'iterable') {
585: if (count($genericTypes) === 1) { // iterable<ValueType>
586: return new IterableType(new MixedType(true), $genericTypes[0]);
587:
588: }
589:
590: if (count($genericTypes) === 2) { // iterable<KeyType, ValueType>
591: return new IterableType($genericTypes[0], $genericTypes[1]);
592: }
593: } elseif (in_array($mainTypeName, ['class-string', 'interface-string'], true)) {
594: if (count($genericTypes) === 1) {
595: $genericType = $genericTypes[0];
596: if ((new ObjectWithoutClassType())->isSuperTypeOf($genericType)->yes() || $genericType instanceof MixedType) {
597: return new GenericClassStringType($genericType);
598: }
599: }
600:
601: return new ErrorType();
602: } elseif ($mainTypeName === 'int') {
603: if (count($genericTypes) === 2) { // int<min, max>, int<1, 3>
604:
605: if ($genericTypes[0] instanceof ConstantIntegerType) {
606: $min = $genericTypes[0]->getValue();
607: } elseif ($typeNode->genericTypes[0] instanceof IdentifierTypeNode && $typeNode->genericTypes[0]->name === 'min') {
608: $min = null;
609: } else {
610: return new ErrorType();
611: }
612:
613: if ($genericTypes[1] instanceof ConstantIntegerType) {
614: $max = $genericTypes[1]->getValue();
615: } elseif ($typeNode->genericTypes[1] instanceof IdentifierTypeNode && $typeNode->genericTypes[1]->name === 'max') {
616: $max = null;
617: } else {
618: return new ErrorType();
619: }
620:
621: return IntegerRangeType::fromInterval($min, $max);
622: }
623: } elseif ($mainTypeName === 'key-of') {
624: if (count($genericTypes) === 1) { // key-of<ValueType>
625: $type = new KeyOfType($genericTypes[0]);
626: return $type->isResolvable() ? $type->resolve() : $type;
627: }
628:
629: return new ErrorType();
630: } elseif ($mainTypeName === 'value-of') {
631: if (count($genericTypes) === 1) { // value-of<ValueType>
632: if ($genericTypes[0] instanceof TypeWithClassName) {
633: if ($this->getReflectionProvider()->hasClass($genericTypes[0]->getClassName())) {
634: $classReflection = $this->getReflectionProvider()->getClass($genericTypes[0]->getClassName());
635:
636: if ($classReflection->isBackedEnum()) {
637: $cases = [];
638: foreach ($classReflection->getEnumCases() as $enumCaseReflection) {
639: $backingType = $enumCaseReflection->getBackingValueType();
640: if ($backingType === null) {
641: continue;
642: }
643:
644: $cases[] = $backingType;
645: }
646:
647: return TypeCombinator::union(...$cases);
648: }
649: }
650: }
651:
652: $type = new ValueOfType($genericTypes[0]);
653: return $type->isResolvable() ? $type->resolve() : $type;
654: }
655:
656: return new ErrorType();
657: } elseif ($mainTypeName === 'int-mask-of') {
658: if (count($genericTypes) === 1) { // int-mask-of<Class::CONST*>
659: $maskType = $this->expandIntMaskToType($genericTypes[0]);
660: if ($maskType !== null) {
661: return $maskType;
662: }
663: }
664:
665: return new ErrorType();
666: } elseif ($mainTypeName === 'int-mask') {
667: if (count($genericTypes) > 0) { // int-mask<1, 2, 4>
668: $maskType = $this->expandIntMaskToType(TypeCombinator::union(...$genericTypes));
669: if ($maskType !== null) {
670: return $maskType;
671: }
672: }
673:
674: return new ErrorType();
675: } elseif ($mainTypeName === '__benevolent') {
676: if (count($genericTypes) === 1) {
677: return TypeUtils::toBenevolentUnion($genericTypes[0]);
678: }
679: return new ErrorType();
680: }
681:
682: $mainType = $this->resolveIdentifierTypeNode($typeNode->type, $nameScope);
683:
684: if ($mainType instanceof TypeWithClassName) {
685: if (!$this->getReflectionProvider()->hasClass($mainType->getClassName())) {
686: return new GenericObjectType($mainType->getClassName(), $genericTypes);
687: }
688:
689: $classReflection = $this->getReflectionProvider()->getClass($mainType->getClassName());
690: if ($classReflection->isGeneric()) {
691: if (in_array($mainType->getClassName(), [
692: Traversable::class,
693: IteratorAggregate::class,
694: Iterator::class,
695: ], true)) {
696: if (count($genericTypes) === 1) {
697: return new GenericObjectType($mainType->getClassName(), [
698: new MixedType(true),
699: $genericTypes[0],
700: ]);
701: }
702:
703: if (count($genericTypes) === 2) {
704: return new GenericObjectType($mainType->getClassName(), [
705: $genericTypes[0],
706: $genericTypes[1],
707: ]);
708: }
709: }
710: if ($mainType->getClassName() === Generator::class) {
711: if (count($genericTypes) === 1) {
712: $mixed = new MixedType(true);
713: return new GenericObjectType($mainType->getClassName(), [
714: $mixed,
715: $genericTypes[0],
716: $mixed,
717: $mixed,
718: ]);
719: }
720:
721: if (count($genericTypes) === 2) {
722: $mixed = new MixedType(true);
723: return new GenericObjectType($mainType->getClassName(), [
724: $genericTypes[0],
725: $genericTypes[1],
726: $mixed,
727: $mixed,
728: ]);
729: }
730: }
731:
732: if (!$mainType->isIterable()->yes()) {
733: return new GenericObjectType($mainType->getClassName(), $genericTypes);
734: }
735:
736: if (
737: count($genericTypes) !== 1
738: || $classReflection->getTemplateTypeMap()->count() === 1
739: ) {
740: return new GenericObjectType($mainType->getClassName(), $genericTypes);
741: }
742: }
743: }
744:
745: if ($mainType->isIterable()->yes()) {
746: if (count($genericTypes) === 1) { // Foo<ValueType>
747: return TypeCombinator::intersect(
748: $mainType,
749: new IterableType(new MixedType(true), $genericTypes[0]),
750: );
751: }
752:
753: if (count($genericTypes) === 2) { // Foo<KeyType, ValueType>
754: return TypeCombinator::intersect(
755: $mainType,
756: new IterableType($genericTypes[0], $genericTypes[1]),
757: );
758: }
759: }
760:
761: if ($mainType instanceof TypeWithClassName) {
762: return new GenericObjectType($mainType->getClassName(), $genericTypes);
763: }
764:
765: return new ErrorType();
766: }
767:
768: private function resolveCallableTypeNode(CallableTypeNode $typeNode, NameScope $nameScope): Type
769: {
770: $mainType = $this->resolve($typeNode->identifier, $nameScope);
771: $isVariadic = false;
772: $parameters = array_map(
773: function (CallableTypeParameterNode $parameterNode) use ($nameScope, &$isVariadic): NativeParameterReflection {
774: $isVariadic = $isVariadic || $parameterNode->isVariadic;
775: $parameterName = $parameterNode->parameterName;
776: if (strpos($parameterName, '$') === 0) {
777: $parameterName = substr($parameterName, 1);
778: }
779: return new NativeParameterReflection(
780: $parameterName,
781: $parameterNode->isOptional || $parameterNode->isVariadic,
782: $this->resolve($parameterNode->type, $nameScope),
783: $parameterNode->isReference ? PassedByReference::createCreatesNewVariable() : PassedByReference::createNo(),
784: $parameterNode->isVariadic,
785: null,
786: );
787: },
788: $typeNode->parameters,
789: );
790: $returnType = $this->resolve($typeNode->returnType, $nameScope);
791:
792: if ($mainType instanceof CallableType) {
793: return new CallableType($parameters, $returnType, $isVariadic);
794:
795: } elseif (
796: $mainType instanceof ObjectType
797: && $mainType->getClassName() === Closure::class
798: ) {
799: return new ClosureType($parameters, $returnType, $isVariadic);
800: }
801:
802: return new ErrorType();
803: }
804:
805: private function resolveArrayShapeNode(ArrayShapeNode $typeNode, NameScope $nameScope): Type
806: {
807: $builder = ConstantArrayTypeBuilder::createEmpty();
808:
809: foreach ($typeNode->items as $itemNode) {
810: $offsetType = null;
811: if ($itemNode->keyName instanceof ConstExprIntegerNode) {
812: $offsetType = new ConstantIntegerType((int) $itemNode->keyName->value);
813: } elseif ($itemNode->keyName instanceof IdentifierTypeNode) {
814: $offsetType = new ConstantStringType($itemNode->keyName->name);
815: } elseif ($itemNode->keyName instanceof ConstExprStringNode) {
816: $offsetType = new ConstantStringType($itemNode->keyName->value);
817: } elseif ($itemNode->keyName !== null) {
818: throw new ShouldNotHappenException('Unsupported key node type: ' . get_class($itemNode->keyName));
819: }
820: $builder->setOffsetValueType($offsetType, $this->resolve($itemNode->valueType, $nameScope), $itemNode->optional);
821: }
822:
823: return $builder->getArray();
824: }
825:
826: private function resolveConstTypeNode(ConstTypeNode $typeNode, NameScope $nameScope): Type
827: {
828: $constExpr = $typeNode->constExpr;
829: if ($constExpr instanceof ConstExprArrayNode) {
830: throw new ShouldNotHappenException(); // we prefer array shapes
831: }
832:
833: if (
834: $constExpr instanceof ConstExprFalseNode
835: || $constExpr instanceof ConstExprTrueNode
836: || $constExpr instanceof ConstExprNullNode
837: ) {
838: throw new ShouldNotHappenException(); // we prefer IdentifierTypeNode
839: }
840:
841: if ($constExpr instanceof ConstFetchNode) {
842: if ($constExpr->className === '') {
843: throw new ShouldNotHappenException(); // global constant should get parsed as class name in IdentifierTypeNode
844: }
845:
846: if ($nameScope->getClassName() !== null) {
847: switch (strtolower($constExpr->className)) {
848: case 'static':
849: case 'self':
850: $className = $nameScope->getClassName();
851: break;
852:
853: case 'parent':
854: if ($this->getReflectionProvider()->hasClass($nameScope->getClassName())) {
855: $classReflection = $this->getReflectionProvider()->getClass($nameScope->getClassName());
856: if ($classReflection->getParentClass() === null) {
857: return new ErrorType();
858:
859: }
860:
861: $className = $classReflection->getParentClass()->getName();
862: }
863: }
864: }
865:
866: if (!isset($className)) {
867: $className = $nameScope->resolveStringName($constExpr->className);
868: }
869:
870: if (!$this->getReflectionProvider()->hasClass($className)) {
871: return new ErrorType();
872: }
873:
874: $classReflection = $this->getReflectionProvider()->getClass($className);
875:
876: $constantName = $constExpr->name;
877: if (Strings::contains($constantName, '*')) {
878: // convert * into .*? and escape everything else so the constants can be matched against the pattern
879: $pattern = '{^' . str_replace('\\*', '.*?', preg_quote($constantName)) . '$}D';
880: $constantTypes = [];
881: foreach ($classReflection->getNativeReflection()->getReflectionConstants() as $reflectionConstant) {
882: $classConstantName = $reflectionConstant->getName();
883: if (Strings::match($classConstantName, $pattern) === null) {
884: continue;
885: }
886:
887: if ($classReflection->isEnum() && $classReflection->hasEnumCase($classConstantName)) {
888: $constantTypes[] = new EnumCaseObjectType($classReflection->getName(), $classConstantName);
889: continue;
890: }
891:
892: $declaringClassName = $reflectionConstant->getDeclaringClass()->getName();
893: if (!$this->getReflectionProvider()->hasClass($declaringClassName)) {
894: continue;
895: }
896:
897: $constantTypes[] = $this->initializerExprTypeResolver->getType(
898: $reflectionConstant->getValueExpression(),
899: InitializerExprContext::fromClassReflection(
900: $this->getReflectionProvider()->getClass($declaringClassName),
901: ),
902: );
903: }
904:
905: if (count($constantTypes) === 0) {
906: return new ErrorType();
907: }
908:
909: return TypeCombinator::union(...$constantTypes);
910: }
911:
912: if (!$classReflection->hasConstant($constantName)) {
913: return new ErrorType();
914: }
915:
916: if ($classReflection->isEnum() && $classReflection->hasEnumCase($constantName)) {
917: return new EnumCaseObjectType($classReflection->getName(), $constantName);
918: }
919:
920: $reflectionConstant = $classReflection->getConstant($constantName);
921:
922: return $this->initializerExprTypeResolver->getType($reflectionConstant->getValueExpr(), InitializerExprContext::fromClassReflection($reflectionConstant->getDeclaringClass()));
923: }
924:
925: if ($constExpr instanceof ConstExprFloatNode) {
926: return new ConstantFloatType((float) $constExpr->value);
927: }
928:
929: if ($constExpr instanceof ConstExprIntegerNode) {
930: return new ConstantIntegerType((int) $constExpr->value);
931: }
932:
933: if ($constExpr instanceof ConstExprStringNode) {
934: return new ConstantStringType($constExpr->value);
935: }
936:
937: return new ErrorType();
938: }
939:
940: private function resolveOffsetAccessNode(OffsetAccessTypeNode $typeNode, NameScope $nameScope): Type
941: {
942: $type = $this->resolve($typeNode->type, $nameScope);
943: $offset = $this->resolve($typeNode->offset, $nameScope);
944:
945: if ($type->isOffsetAccessible()->no() || $type->hasOffsetValueType($offset)->no()) {
946: return new ErrorType();
947: }
948:
949: return new OffsetAccessType($type, $offset);
950: }
951:
952: private function expandIntMaskToType(Type $type): ?Type
953: {
954: $ints = array_map(static fn (ConstantIntegerType $type) => $type->getValue(), TypeUtils::getConstantIntegers($type));
955: if (count($ints) === 0) {
956: return null;
957: }
958:
959: $values = [];
960:
961: foreach ($ints as $int) {
962: if ($int !== 0 && !array_key_exists($int, $values)) {
963: foreach ($values as $value) {
964: $computedValue = $value | $int;
965: $values[$computedValue] = $computedValue;
966: }
967: }
968:
969: $values[$int] = $int;
970: }
971:
972: $values[0] = 0;
973:
974: $min = min($values);
975: $max = max($values);
976:
977: if ($max - $min === count($values) - 1) {
978: return IntegerRangeType::fromInterval($min, $max);
979: }
980:
981: return TypeCombinator::union(...array_map(static fn ($value) => new ConstantIntegerType($value), $values));
982: }
983:
984: /**
985: * @api
986: * @param TypeNode[] $typeNodes
987: * @return Type[]
988: */
989: public function resolveMultiple(array $typeNodes, NameScope $nameScope): array
990: {
991: $types = [];
992: foreach ($typeNodes as $typeNode) {
993: $types[] = $this->resolve($typeNode, $nameScope);
994: }
995:
996: return $types;
997: }
998:
999: private function getReflectionProvider(): ReflectionProvider
1000: {
1001: return $this->reflectionProviderProvider->getReflectionProvider();
1002: }
1003:
1004: private function getTypeAliasResolver(): TypeAliasResolver
1005: {
1006: return $this->typeAliasResolverProvider->getTypeAliasResolver();
1007: }
1008:
1009: }
1010: