1: <?php declare(strict_types = 1);
2:
3: namespace PHPStan\Type;
4:
5: use PHPStan\Reflection\ClassReflection;
6: use PHPStan\Reflection\ReflectionProviderStaticAccessor;
7: use PHPStan\ShouldNotHappenException;
8: use PHPStan\Type\Constant\ConstantBooleanType;
9: use PHPStan\Type\Generic\TemplateTypeHelper;
10: use ReflectionIntersectionType;
11: use ReflectionNamedType;
12: use ReflectionType;
13: use ReflectionUnionType;
14: use function array_map;
15: use function count;
16: use function get_class;
17: use function is_string;
18: use function sprintf;
19: use function str_ends_with;
20: use function strtolower;
21:
22: class TypehintHelper
23: {
24:
25: private static function getTypeObjectFromTypehint(string $typeString, ClassReflection|string|null $selfClass): Type
26: {
27: switch (strtolower($typeString)) {
28: case 'int':
29: return new IntegerType();
30: case 'bool':
31: return new BooleanType();
32: case 'false':
33: return new ConstantBooleanType(false);
34: case 'true':
35: return new ConstantBooleanType(true);
36: case 'string':
37: return new StringType();
38: case 'float':
39: return new FloatType();
40: case 'array':
41: return new ArrayType(new MixedType(), new MixedType());
42: case 'iterable':
43: return new IterableType(new MixedType(), new MixedType());
44: case 'callable':
45: return new CallableType();
46: case 'void':
47: return new VoidType();
48: case 'object':
49: return new ObjectWithoutClassType();
50: case 'mixed':
51: return new MixedType(true);
52: case 'self':
53: if ($selfClass instanceof ClassReflection) {
54: $selfClass = $selfClass->getName();
55: }
56: return $selfClass !== null ? new ObjectType($selfClass) : new ErrorType();
57: case 'parent':
58: $reflectionProvider = ReflectionProviderStaticAccessor::getInstance();
59: if (is_string($selfClass)) {
60: if ($reflectionProvider->hasClass($selfClass)) {
61: $selfClass = $reflectionProvider->getClass($selfClass);
62: } else {
63: $selfClass = null;
64: }
65: }
66: if ($selfClass !== null) {
67: if ($selfClass->getParentClass() !== null) {
68: return new ObjectType($selfClass->getParentClass()->getName());
69: }
70: }
71: return new NonexistentParentClassType();
72: case 'static':
73: $reflectionProvider = ReflectionProviderStaticAccessor::getInstance();
74: if (is_string($selfClass)) {
75: if ($reflectionProvider->hasClass($selfClass)) {
76: $selfClass = $reflectionProvider->getClass($selfClass);
77: } else {
78: $selfClass = null;
79: }
80: }
81: if ($selfClass !== null) {
82: return new StaticType($selfClass);
83: }
84:
85: return new ErrorType();
86: case 'null':
87: return new NullType();
88: case 'never':
89: return new NonAcceptingNeverType();
90: default:
91: return new ObjectType($typeString);
92: }
93: }
94:
95: /** @api */
96: public static function decideTypeFromReflection(
97: ?ReflectionType $reflectionType,
98: ?Type $phpDocType = null,
99: ClassReflection|string|null $selfClass = null,
100: bool $isVariadic = false,
101: ): Type
102: {
103: if ($reflectionType === null) {
104: if ($isVariadic && $phpDocType instanceof ArrayType) {
105: $phpDocType = $phpDocType->getItemType();
106: }
107: return $phpDocType ?? new MixedType();
108: }
109:
110: if ($reflectionType instanceof ReflectionUnionType) {
111: $type = TypeCombinator::union(...array_map(static fn (ReflectionType $type): Type => self::decideTypeFromReflection($type, null, $selfClass, false), $reflectionType->getTypes()));
112:
113: return self::decideType($type, $phpDocType);
114: }
115:
116: if ($reflectionType instanceof ReflectionIntersectionType) {
117: $types = [];
118: foreach ($reflectionType->getTypes() as $innerReflectionType) {
119: $innerType = self::decideTypeFromReflection($innerReflectionType, null, $selfClass, false);
120: if (!$innerType->isObject()->yes()) {
121: return new NeverType();
122: }
123:
124: $types[] = $innerType;
125: }
126:
127: return self::decideType(TypeCombinator::intersect(...$types), $phpDocType);
128: }
129:
130: if (!$reflectionType instanceof ReflectionNamedType) {
131: throw new ShouldNotHappenException(sprintf('Unexpected type: %s', get_class($reflectionType)));
132: }
133:
134: $reflectionTypeString = $reflectionType->getName();
135: $loweredReflectionTypeString = strtolower($reflectionTypeString);
136: if (str_ends_with($loweredReflectionTypeString, '\\object')) {
137: $reflectionTypeString = 'object';
138: } elseif (str_ends_with($loweredReflectionTypeString, '\\mixed')) {
139: $reflectionTypeString = 'mixed';
140: } elseif (str_ends_with($loweredReflectionTypeString, '\\true')) {
141: $reflectionTypeString = 'true';
142: } elseif (str_ends_with($loweredReflectionTypeString, '\\false')) {
143: $reflectionTypeString = 'false';
144: } elseif (str_ends_with($loweredReflectionTypeString, '\\null')) {
145: $reflectionTypeString = 'null';
146: } elseif (str_ends_with($loweredReflectionTypeString, '\\never')) {
147: $reflectionTypeString = 'never';
148: }
149:
150: $type = self::getTypeObjectFromTypehint($reflectionTypeString, $selfClass);
151: if ($reflectionType->allowsNull()) {
152: $type = TypeCombinator::addNull($type);
153: } elseif ($phpDocType !== null) {
154: $phpDocType = TypeCombinator::removeNull($phpDocType);
155: }
156:
157: return self::decideType($type, $phpDocType);
158: }
159:
160: public static function decideType(
161: Type $type,
162: ?Type $phpDocType = null,
163: ): Type
164: {
165: if ($type instanceof BenevolentUnionType) {
166: return $type;
167: }
168:
169: if ($phpDocType !== null && !$phpDocType instanceof ErrorType) {
170: if ($phpDocType instanceof NeverType && $phpDocType->isExplicit()) {
171: return $phpDocType;
172: }
173: if (
174: $type instanceof MixedType
175: && !$type->isExplicitMixed()
176: && $phpDocType->isVoid()->yes()
177: ) {
178: return $phpDocType;
179: }
180:
181: if (TypeCombinator::removeNull($type) instanceof IterableType) {
182: if ($phpDocType instanceof UnionType) {
183: $innerTypes = [];
184: foreach ($phpDocType->getTypes() as $innerType) {
185: if ($innerType instanceof ArrayType) {
186: $innerTypes[] = new IterableType(
187: $innerType->getIterableKeyType(),
188: $innerType->getItemType(),
189: );
190: } else {
191: $innerTypes[] = $innerType;
192: }
193: }
194: $phpDocType = new UnionType($innerTypes);
195: } elseif ($phpDocType instanceof ArrayType) {
196: $phpDocType = new IterableType(
197: $phpDocType->getKeyType(),
198: $phpDocType->getItemType(),
199: );
200: }
201: }
202:
203: if (
204: (!$phpDocType instanceof NeverType || ($type instanceof MixedType && !$type->isExplicitMixed()))
205: && $type->isSuperTypeOf(TemplateTypeHelper::resolveToBounds($phpDocType))->yes()
206: ) {
207: $resultType = $phpDocType;
208: } else {
209: $resultType = $type;
210: }
211:
212: if ($type instanceof UnionType) {
213: $addToUnionTypes = [];
214: foreach ($type->getTypes() as $innerType) {
215: if (!$innerType->isSuperTypeOf($resultType)->no()) {
216: continue;
217: }
218:
219: $addToUnionTypes[] = $innerType;
220: }
221:
222: if (count($addToUnionTypes) > 0) {
223: $type = TypeCombinator::union($resultType, ...$addToUnionTypes);
224: } else {
225: $type = $resultType;
226: }
227: } elseif (TypeCombinator::containsNull($type)) {
228: $type = TypeCombinator::addNull($resultType);
229: } else {
230: $type = $resultType;
231: }
232: }
233:
234: return $type;
235: }
236:
237: }
238: