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