|
4 | 4 |
|
5 | 5 | namespace Rector\TypeDeclaration\TypeInferer; |
6 | 6 |
|
7 | | -use PhpParser\Node; |
8 | | -use PhpParser\Node\Expr; |
9 | 7 | use PhpParser\Node\Expr\Closure; |
10 | | -use PhpParser\Node\Expr\Yield_; |
11 | | -use PhpParser\Node\FunctionLike; |
12 | 8 | use PhpParser\Node\Stmt\ClassMethod; |
13 | 9 | use PhpParser\Node\Stmt\Function_; |
14 | | -use PhpParser\Node\Stmt\Return_; |
15 | | -use PHPStan\Reflection\ClassReflection; |
16 | | -use PHPStan\Reflection\ReflectionProvider; |
17 | | -use PHPStan\Type\BenevolentUnionType; |
18 | 10 | use PHPStan\Type\MixedType; |
19 | | -use PHPStan\Type\ThisType; |
20 | 11 | use PHPStan\Type\Type; |
21 | | -use PHPStan\Type\TypeWithClassName; |
22 | | -use PHPStan\Type\UnionType; |
23 | | -use Rector\Enum\ObjectReference; |
24 | | -use Rector\Exception\ShouldNotHappenException; |
25 | | -use Rector\NodeTypeResolver\NodeTypeResolver; |
26 | | -use Rector\Php\PhpVersionProvider; |
27 | | -use Rector\PhpParser\Node\BetterNodeFinder; |
28 | | -use Rector\Reflection\ReflectionResolver; |
29 | | -use Rector\StaticTypeMapper\ValueObject\Type\FullyQualifiedObjectType; |
30 | | -use Rector\TypeDeclaration\TypeAnalyzer\GenericClassStringTypeNormalizer; |
31 | 12 | use Rector\TypeDeclaration\TypeInferer\ReturnTypeInferer\ReturnedNodesReturnTypeInfererTypeInferer; |
32 | 13 | use Rector\TypeDeclaration\TypeNormalizer; |
33 | | -use Rector\ValueObject\PhpVersionFeature; |
34 | 14 |
|
35 | 15 | /** |
36 | 16 | * @internal |
|
39 | 19 | { |
40 | 20 | public function __construct( |
41 | 21 | private TypeNormalizer $typeNormalizer, |
42 | | - private ReturnedNodesReturnTypeInfererTypeInferer $returnedNodesReturnTypeInfererTypeInferer, |
43 | | - private GenericClassStringTypeNormalizer $genericClassStringTypeNormalizer, |
44 | | - private PhpVersionProvider $phpVersionProvider, |
45 | | - private BetterNodeFinder $betterNodeFinder, |
46 | | - private ReflectionResolver $reflectionResolver, |
47 | | - private ReflectionProvider $reflectionProvider, |
48 | | - private NodeTypeResolver $nodeTypeResolver |
| 22 | + private ReturnedNodesReturnTypeInfererTypeInferer $returnedNodesReturnTypeInfererTypeInferer |
49 | 23 | ) { |
50 | 24 | } |
51 | 25 |
|
52 | 26 | public function inferFunctionLike(ClassMethod|Function_|Closure $functionLike): Type |
53 | 27 | { |
54 | | - $isSupportedStaticReturnType = $this->phpVersionProvider->isAtLeastPhpVersion( |
55 | | - PhpVersionFeature::STATIC_RETURN_TYPE |
56 | | - ); |
57 | | - |
58 | 28 | $originalType = $this->returnedNodesReturnTypeInfererTypeInferer->inferFunctionLike($functionLike); |
59 | 29 | if ($originalType instanceof MixedType) { |
60 | 30 | return new MixedType(); |
61 | 31 | } |
62 | 32 |
|
63 | | - $type = $this->typeNormalizer->normalizeArrayTypeAndArrayNever($originalType); |
64 | | - |
65 | | - // in case of void, check return type of children methods |
66 | | - if ($type instanceof MixedType) { |
67 | | - return new MixedType(); |
68 | | - } |
69 | | - |
70 | | - $type = $this->verifyStaticType($type, $isSupportedStaticReturnType); |
71 | | - if (! $type instanceof Type) { |
72 | | - return new MixedType(); |
73 | | - } |
74 | | - |
75 | | - $type = $this->verifyThisType($type, $functionLike); |
76 | | - |
77 | | - // normalize ConstStringType to ClassStringType |
78 | | - $resolvedType = $this->genericClassStringTypeNormalizer->normalize($type); |
79 | | - return $this->resolveTypeWithVoidHandling($functionLike, $resolvedType); |
80 | | - } |
81 | | - |
82 | | - private function verifyStaticType(Type $type, bool $isSupportedStaticReturnType): ?Type |
83 | | - { |
84 | | - if ($this->isStaticType($type)) { |
85 | | - /** @var TypeWithClassName $type */ |
86 | | - return $this->resolveStaticType($isSupportedStaticReturnType, $type); |
87 | | - } |
88 | | - |
89 | | - if ($type instanceof UnionType) { |
90 | | - return $this->resolveUnionStaticTypes($type, $isSupportedStaticReturnType); |
91 | | - } |
92 | | - |
93 | | - return $type; |
94 | | - } |
95 | | - |
96 | | - private function verifyThisType(Type $type, FunctionLike $functionLike): Type |
97 | | - { |
98 | | - if (! $type instanceof ThisType) { |
99 | | - return $type; |
100 | | - } |
101 | | - |
102 | | - $classReflection = $this->reflectionResolver->resolveClassReflection($functionLike); |
103 | | - $objectType = $type->getStaticObjectType(); |
104 | | - $objectTypeClassName = $objectType->getClassName(); |
105 | | - |
106 | | - if (! $classReflection instanceof ClassReflection || ! $classReflection->isClass()) { |
107 | | - return $type; |
108 | | - } |
109 | | - |
110 | | - if ($classReflection->getName() === $objectTypeClassName) { |
111 | | - return $type; |
112 | | - } |
113 | | - |
114 | | - return new MixedType(); |
115 | | - } |
116 | | - |
117 | | - private function resolveTypeWithVoidHandling( |
118 | | - ClassMethod|Function_|Closure $functionLike, |
119 | | - Type $resolvedType |
120 | | - ): Type { |
121 | | - if ($resolvedType->isVoid()->yes()) { |
122 | | - $hasReturnValue = (bool) $this->betterNodeFinder->findFirstInFunctionLikeScoped( |
123 | | - $functionLike, |
124 | | - static function (Node $subNode): bool { |
125 | | - if (! $subNode instanceof Return_) { |
126 | | - // yield return is handled on speicific rule: AddReturnTypeDeclarationFromYieldsRector |
127 | | - return $subNode instanceof Yield_; |
128 | | - } |
129 | | - |
130 | | - return $subNode->expr instanceof Expr; |
131 | | - } |
132 | | - ); |
133 | | - |
134 | | - if ($hasReturnValue) { |
135 | | - return new MixedType(); |
136 | | - } |
137 | | - } |
138 | | - |
139 | | - if ($resolvedType instanceof UnionType) { |
140 | | - $benevolentUnionTypeIntegerType = $this->resolveBenevolentUnionTypeInteger($functionLike, $resolvedType); |
141 | | - if ($benevolentUnionTypeIntegerType->isInteger()->yes()) { |
142 | | - return $benevolentUnionTypeIntegerType; |
143 | | - } |
144 | | - } |
145 | | - |
146 | | - return $resolvedType; |
147 | | - } |
148 | | - |
149 | | - private function resolveBenevolentUnionTypeInteger( |
150 | | - ClassMethod|Function_|Closure $functionLike, |
151 | | - UnionType $unionType |
152 | | - ): Type { |
153 | | - $types = $unionType->getTypes(); |
154 | | - $countTypes = count($types); |
155 | | - |
156 | | - if ($countTypes !== 2) { |
157 | | - return $unionType; |
158 | | - } |
159 | | - |
160 | | - if (! ($types[0]->isInteger()->yes() && $types[1]->isString()->yes())) { |
161 | | - return $unionType; |
162 | | - } |
163 | | - |
164 | | - $returns = $this->betterNodeFinder->findReturnsScoped($functionLike); |
165 | | - $returnsWithExpr = array_filter( |
166 | | - $returns, |
167 | | - static fn (Return_ $return): bool => $return->expr instanceof Expr |
168 | | - ); |
169 | | - |
170 | | - if ($returns !== $returnsWithExpr) { |
171 | | - return $unionType; |
172 | | - } |
173 | | - |
174 | | - if ($returnsWithExpr === []) { |
175 | | - return $unionType; |
176 | | - } |
177 | | - |
178 | | - foreach ($returnsWithExpr as $returnWithExpr) { |
179 | | - /** @var Expr $expr */ |
180 | | - $expr = $returnWithExpr->expr; |
181 | | - $type = $this->nodeTypeResolver->getNativeType($expr); |
182 | | - |
183 | | - if (! $type instanceof BenevolentUnionType) { |
184 | | - return $unionType; |
185 | | - } |
186 | | - } |
187 | | - |
188 | | - return $types[0]; |
189 | | - } |
190 | | - |
191 | | - private function isStaticType(Type $type): bool |
192 | | - { |
193 | | - if (! $type instanceof TypeWithClassName) { |
194 | | - return false; |
195 | | - } |
196 | | - |
197 | | - return $type->getClassName() === ObjectReference::STATIC; |
198 | | - } |
199 | | - |
200 | | - private function resolveUnionStaticTypes(UnionType $unionType, bool $isSupportedStaticReturnType): UnionType|null |
201 | | - { |
202 | | - $resolvedTypes = []; |
203 | | - $hasStatic = false; |
204 | | - |
205 | | - foreach ($unionType->getTypes() as $unionedType) { |
206 | | - if ($this->isStaticType($unionedType)) { |
207 | | - /** @var FullyQualifiedObjectType $unionedType */ |
208 | | - $classReflection = $this->reflectionProvider->getClass($unionedType->getClassName()); |
209 | | - |
210 | | - $resolvedTypes[] = new ThisType($classReflection); |
211 | | - $hasStatic = true; |
212 | | - continue; |
213 | | - } |
214 | | - |
215 | | - $resolvedTypes[] = $unionedType; |
216 | | - } |
217 | | - |
218 | | - if (! $hasStatic) { |
219 | | - return $unionType; |
220 | | - } |
221 | | - |
222 | | - // has static, but it is not supported |
223 | | - if (! $isSupportedStaticReturnType) { |
224 | | - return null; |
225 | | - } |
226 | | - |
227 | | - return new UnionType($resolvedTypes); |
228 | | - } |
229 | | - |
230 | | - private function resolveStaticType( |
231 | | - bool $isSupportedStaticReturnType, |
232 | | - TypeWithClassName $typeWithClassName |
233 | | - ): ?ThisType { |
234 | | - if (! $isSupportedStaticReturnType) { |
235 | | - return null; |
236 | | - } |
237 | | - |
238 | | - $classReflection = $typeWithClassName->getClassReflection(); |
239 | | - if (! $classReflection instanceof ClassReflection) { |
240 | | - throw new ShouldNotHappenException(); |
241 | | - } |
242 | | - |
243 | | - return new ThisType($classReflection); |
| 33 | + return $this->typeNormalizer->normalizeArrayTypeAndArrayNever($originalType); |
244 | 34 | } |
245 | 35 | } |
0 commit comments