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