|
10 | 10 | use PHPStan\Type\Generic\TemplateType; |
11 | 11 | use PHPStan\Type\Generic\TemplateTypeHelper; |
12 | 12 | use PHPStan\Type\Generic\TemplateTypeMap; |
| 13 | +use PHPStan\Type\Generic\TemplateTypeReference; |
13 | 14 | use PHPStan\Type\Generic\TemplateTypeVariance; |
14 | 15 | use PHPStan\Type\Generic\TemplateTypeVarianceMap; |
15 | 16 | use PHPStan\Type\NarrowedSubjectType; |
@@ -230,19 +231,19 @@ private function resolveResolvableTemplateTypes(Type $type, TemplateTypeVariance |
230 | 231 | { |
231 | 232 | $references = $type->getReferencedTemplateTypes($positionVariance); |
232 | 233 |
|
233 | | - $objectCb = function (Type $type, callable $traverse) use ($references): Type { |
234 | | - if ( |
235 | | - $type instanceof TemplateType |
236 | | - && !$type instanceof NarrowedSubjectType |
237 | | - && !$type->isArgument() |
238 | | - && $type->getScope()->getFunctionName() !== null |
239 | | - ) { |
| 234 | + $objectCb = fn (Type $type, callable $traverse): Type => $this->resolveTemplateTypeInGenericType($type, $traverse, $references, null); |
| 235 | + |
| 236 | + return TypeTraverser::map($type, function (Type $type, callable $traverse) use ($references, $objectCb): Type { |
| 237 | + if ($type instanceof GenericObjectType || $type instanceof GenericStaticType) { |
| 238 | + return TypeTraverser::map($type, $objectCb); |
| 239 | + } |
| 240 | + |
| 241 | + if ($type instanceof TemplateType && !$type instanceof NarrowedSubjectType && !$type->isArgument()) { |
240 | 242 | $newType = $this->resolvedTemplateTypeMap->getType($type->getName()); |
241 | 243 | if ($newType === null || $newType instanceof ErrorType) { |
242 | 244 | return $traverse($type); |
243 | 245 | } |
244 | 246 |
|
245 | | - $newType = TemplateTypeHelper::generalizeInferredTemplateType($type, $newType); |
246 | 247 | $variance = TemplateTypeVariance::createInvariant(); |
247 | 248 | foreach ($references as $reference) { |
248 | 249 | // this uses identity to distinguish between different occurrences of the same template type |
@@ -270,47 +271,74 @@ private function resolveResolvableTemplateTypes(Type $type, TemplateTypeVariance |
270 | 271 | } |
271 | 272 |
|
272 | 273 | return $traverse($type); |
273 | | - }; |
| 274 | + }); |
| 275 | + } |
274 | 276 |
|
275 | | - return TypeTraverser::map($type, function (Type $type, callable $traverse) use ($references, $objectCb): Type { |
276 | | - if ($type instanceof GenericObjectType || $type instanceof GenericStaticType) { |
277 | | - return TypeTraverser::map($type, $objectCb); |
278 | | - } |
| 277 | + /** |
| 278 | + * @param callable(Type): Type $traverse |
| 279 | + * @param list<TemplateTypeReference> $references |
| 280 | + * @param string|null $keepInferredName a template type whose inferred type is not generalized here |
| 281 | + */ |
| 282 | + private function resolveTemplateTypeInGenericType(Type $type, callable $traverse, array $references, ?string $keepInferredName): Type |
| 283 | + { |
| 284 | + if ( |
| 285 | + $type instanceof NarrowedSubjectType |
| 286 | + && $keepInferredName === null |
| 287 | + && !$type->isArgument() |
| 288 | + && $type->getScope()->getFunctionName() !== null |
| 289 | + ) { |
| 290 | + // generalize what the branch knows about the subject, not the subject: in the |
| 291 | + // else branch of `(T is \UnitEnum ? array-key : T)` with T of array-key|\UnitEnum, |
| 292 | + // T is a scalar and an inferred 'foo' stays 'foo'. Only the subject is taken as |
| 293 | + // inferred - a template type in the target is generalized as the condition sees it. |
| 294 | + $subjectName = $type->getName(); |
| 295 | + $resolved = TypeTraverser::map($type, fn (Type $type, callable $traverse): Type => $this->resolveTemplateTypeInGenericType($type, $traverse, $references, $subjectName)); |
| 296 | + |
| 297 | + return TemplateTypeHelper::generalizeInferredTemplateType($type, $resolved); |
| 298 | + } |
279 | 299 |
|
280 | | - if ($type instanceof TemplateType && !$type instanceof NarrowedSubjectType && !$type->isArgument()) { |
281 | | - $newType = $this->resolvedTemplateTypeMap->getType($type->getName()); |
282 | | - if ($newType === null || $newType instanceof ErrorType) { |
283 | | - return $traverse($type); |
284 | | - } |
| 300 | + if ( |
| 301 | + $type instanceof TemplateType |
| 302 | + && !$type instanceof NarrowedSubjectType |
| 303 | + && !$type->isArgument() |
| 304 | + && $type->getScope()->getFunctionName() !== null |
| 305 | + ) { |
| 306 | + $newType = $this->resolvedTemplateTypeMap->getType($type->getName()); |
| 307 | + if ($newType === null || $newType instanceof ErrorType) { |
| 308 | + return $traverse($type); |
| 309 | + } |
285 | 310 |
|
286 | | - $variance = TemplateTypeVariance::createInvariant(); |
287 | | - foreach ($references as $reference) { |
288 | | - // this uses identity to distinguish between different occurrences of the same template type |
289 | | - // see https://github.com/phpstan/phpstan-src/pull/2485#discussion_r1328555397 for details |
290 | | - if ($reference->getType() === $type) { |
291 | | - $variance = $reference->getPositionVariance(); |
292 | | - break; |
293 | | - } |
294 | | - } |
| 311 | + if ($type->getName() !== $keepInferredName) { |
| 312 | + $newType = TemplateTypeHelper::generalizeInferredTemplateType($type, $newType); |
| 313 | + } |
295 | 314 |
|
296 | | - $callSiteVariance = $this->callSiteVarianceMap->getVariance($type->getName()); |
297 | | - if ($callSiteVariance === null || $callSiteVariance->invariant()) { |
298 | | - return $newType; |
| 315 | + $variance = TemplateTypeVariance::createInvariant(); |
| 316 | + foreach ($references as $reference) { |
| 317 | + // this uses identity to distinguish between different occurrences of the same template type |
| 318 | + // see https://github.com/phpstan/phpstan-src/pull/2485#discussion_r1328555397 for details |
| 319 | + if ($reference->getType() === $type) { |
| 320 | + $variance = $reference->getPositionVariance(); |
| 321 | + break; |
299 | 322 | } |
| 323 | + } |
300 | 324 |
|
301 | | - if (!$callSiteVariance->covariant() && $variance->covariant()) { |
302 | | - return $traverse($type->getBound()); |
303 | | - } |
| 325 | + $callSiteVariance = $this->callSiteVarianceMap->getVariance($type->getName()); |
| 326 | + if ($callSiteVariance === null || $callSiteVariance->invariant()) { |
| 327 | + return $newType; |
| 328 | + } |
304 | 329 |
|
305 | | - if (!$callSiteVariance->contravariant() && $variance->contravariant()) { |
306 | | - return new NonAcceptingNeverType(); |
307 | | - } |
| 330 | + if (!$callSiteVariance->covariant() && $variance->covariant()) { |
| 331 | + return $traverse($type->getBound()); |
| 332 | + } |
308 | 333 |
|
309 | | - return $newType; |
| 334 | + if (!$callSiteVariance->contravariant() && $variance->contravariant()) { |
| 335 | + return new NonAcceptingNeverType(); |
310 | 336 | } |
311 | 337 |
|
312 | | - return $traverse($type); |
313 | | - }); |
| 338 | + return $newType; |
| 339 | + } |
| 340 | + |
| 341 | + return $traverse($type); |
314 | 342 | } |
315 | 343 |
|
316 | 344 | /** |
|
0 commit comments