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