|
5 | 5 | use PhpParser\Node\Expr; |
6 | 6 | use PHPStan\Analyser\OutOfClassScope; |
7 | 7 | use PHPStan\DependencyInjection\AutowiredService; |
| 8 | +use PHPStan\Reflection\MissingPropertyFromReflectionException; |
8 | 9 | use PHPStan\Reflection\ParametersAcceptor; |
9 | 10 | use PHPStan\Reflection\ResolvedFunctionVariant; |
10 | 11 | use PHPStan\Turbo\ShadowedByTurboExtension; |
|
14 | 15 | use PHPStan\Type\Generic\TemplateTypeMap; |
15 | 16 | use PHPStan\Type\Generic\TemplateTypeVariance; |
16 | 17 | use PHPStan\Type\Generic\UnresolvedTemplateArgumentType; |
| 18 | +use PHPStan\Type\IntersectionType; |
17 | 19 | use PHPStan\Type\MixedType; |
18 | 20 | use PHPStan\Type\NeverType; |
| 21 | +use PHPStan\Type\ObjectShapeType; |
19 | 22 | use PHPStan\Type\Type; |
20 | 23 | use PHPStan\Type\TypeCombinator; |
21 | 24 | use PHPStan\Type\TypeTraverser; |
@@ -451,6 +454,18 @@ private function observeSend(TemplateArgumentConstraints $constraints, Type $dec |
451 | 454 | // never holds no markers, and is its own iterable key and value type |
452 | 455 | return $constraints; |
453 | 456 | } |
| 457 | + if ($declared instanceof ObjectShapeType) { |
| 458 | + return $this->observeObjectShapeSend($constraints, $declared, $actual, $isCallArgument); |
| 459 | + } |
| 460 | + if ($declared instanceof IntersectionType) { |
| 461 | + // object{...}&Foo - the shape's properties are bounds next to Foo's arguments |
| 462 | + foreach ($declared->getTypes() as $member) { |
| 463 | + if (!$member instanceof ObjectShapeType) { |
| 464 | + continue; |
| 465 | + } |
| 466 | + $constraints = $this->observeObjectShapeSend($constraints, $member, $actual, $isCallArgument); |
| 467 | + } |
| 468 | + } |
454 | 469 |
|
455 | 470 | $actualReflections = $actual->getObjectClassReflections(); |
456 | 471 | if (count($actualReflections) === 1) { |
@@ -506,6 +521,38 @@ private function observeSend(TemplateArgumentConstraints $constraints, Type $dec |
506 | 521 | return $constraints; |
507 | 522 | } |
508 | 523 |
|
| 524 | + /** |
| 525 | + * Each property of the object flows into the declared property of the shape |
| 526 | + * (`(object) ['x' => new Foo(1)]` returned as `object{x: Foo<int>}`). |
| 527 | + */ |
| 528 | + private function observeObjectShapeSend(TemplateArgumentConstraints $constraints, ObjectShapeType $declared, Type $actual, bool $isCallArgument): TemplateArgumentConstraints |
| 529 | + { |
| 530 | + if (!$actual->isObject()->yes()) { |
| 531 | + return $constraints; |
| 532 | + } |
| 533 | + |
| 534 | + $scope = new OutOfClassScope(); |
| 535 | + foreach ($declared->getProperties() as $name => $declaredPropertyType) { |
| 536 | + $name = (string) $name; |
| 537 | + if (!$actual->hasInstanceProperty($name)->yes()) { |
| 538 | + continue; |
| 539 | + } |
| 540 | + |
| 541 | + try { |
| 542 | + $actualProperty = $actual->getInstanceProperty($name, $scope); |
| 543 | + } catch (MissingPropertyFromReflectionException) { |
| 544 | + continue; |
| 545 | + } |
| 546 | + if (!$actualProperty->isPublic() || $actualProperty->isStatic()) { |
| 547 | + continue; |
| 548 | + } |
| 549 | + |
| 550 | + $constraints = $this->observeSend($constraints, $declaredPropertyType, $actualProperty->getReadableType(), $isCallArgument); |
| 551 | + } |
| 552 | + |
| 553 | + return $constraints; |
| 554 | + } |
| 555 | + |
509 | 556 | /** |
510 | 557 | * A template argument of an object sent to the declared argument at the same |
511 | 558 | * position. A union of several sites' markers (Box<T|U> of zip(of(1), of(2))) |
|
0 commit comments