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: /** @api */
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: