Skip to content

Commit f199980

Browse files
committed
Fix object shapes with interfaces and final classes
1 parent ffe5931 commit f199980

File tree

4 files changed

+119
-4
lines changed

4 files changed

+119
-4
lines changed

src/Type/ObjectShapeType.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -178,11 +178,12 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult
178178
$otherProperty = $type->getProperty($propertyName, $scope);
179179
} catch (MissingPropertyFromReflectionException) {
180180
return new AcceptsResult(
181-
TrinaryLogic::createNo(),
181+
$result->result,
182182
[
183183
sprintf(
184-
'%s does not have property $%s.',
184+
'%s %s not have property $%s.',
185185
$type->describe(VerbosityLevel::typeOnly()),
186+
$result->no() ? 'does' : 'might',
186187
$propertyName,
187188
),
188189
],
@@ -278,7 +279,7 @@ public function isSuperTypeOf(Type $type): TrinaryLogic
278279
try {
279280
$otherProperty = $type->getProperty($propertyName, $scope);
280281
} catch (MissingPropertyFromReflectionException) {
281-
return TrinaryLogic::createNo();
282+
return $result;
282283
}
283284

284285
if (!$otherProperty->isPublic()) {

tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2919,7 +2919,7 @@ public function testObjectShapes(): void
29192919
[
29202920
'Parameter #1 $o of method ObjectShapesAcceptance\Foo::doBar() expects object{foo: int, bar: string}, Exception given.',
29212921
14,
2922-
'Exception does not have property $foo.',
2922+
'Exception might not have property $foo.',
29232923
],
29242924
[
29252925
'Parameter #1 $o of method ObjectShapesAcceptance\Foo::doBar() expects object{foo: int, bar: string}, object{foo: string, bar: int} given.',
@@ -2989,6 +2989,16 @@ public function testObjectShapes(): void
29892989
157,
29902990
'Property ($foo) type int does not accept type string.',
29912991
],
2992+
[
2993+
'Parameter #1 $o of method ObjectShapesAcceptance\TestAcceptance::doFoo() expects object{foo: int}, Traversable given.',
2994+
209,
2995+
'Traversable might not have property $foo.',
2996+
],
2997+
[
2998+
'Parameter #1 $o of method ObjectShapesAcceptance\TestAcceptance::doFoo() expects object{foo: int}, ObjectShapesAcceptance\FinalClass given.',
2999+
210,
3000+
PHP_VERSION_ID < 80200 ? 'ObjectShapesAcceptance\FinalClass might not have property $foo.' : 'ObjectShapesAcceptance\FinalClass does not have property $foo.',
3001+
],
29923002
]);
29933003
}
29943004

tests/PHPStan/Rules/Methods/data/object-shapes.php

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,3 +174,41 @@ public function doBaz(object $o): void
174174
}
175175

176176
}
177+
178+
final class FinalClass
179+
{
180+
181+
}
182+
183+
class ClassWithFooIntProperty
184+
{
185+
186+
/** @var int */
187+
public $foo;
188+
189+
}
190+
191+
class TestAcceptance
192+
{
193+
194+
/**
195+
* @param object{foo: int} $o
196+
* @return void
197+
*/
198+
public function doFoo(object $o): void
199+
{
200+
201+
}
202+
203+
public function doBar(
204+
\Traversable $traversable,
205+
FinalClass $finalClass,
206+
ClassWithFooIntProperty $classWithFooIntProperty
207+
)
208+
{
209+
$this->doFoo($traversable);
210+
$this->doFoo($finalClass);
211+
$this->doFoo($classWithFooIntProperty);
212+
}
213+
214+
}

tests/PHPStan/Type/TypeCombinatorTest.php

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use Exception;
1414
use InvalidArgumentException;
1515
use Iterator;
16+
use ObjectShapesAcceptance\ClassWithFooIntProperty;
1617
use PHPStan\Fixture\FinalClass;
1718
use PHPStan\Testing\PHPStanTestCase;
1819
use PHPStan\Type\Accessory\AccessoryLiteralStringType;
@@ -2396,6 +2397,39 @@ public function dataUnion(): iterable
23962397
UnionType::class,
23972398
'object{bar: string}|object{foo: int}',
23982399
];
2400+
2401+
yield [
2402+
[
2403+
new ObjectShapeType(['foo' => new IntegerType()], []),
2404+
new ObjectType(Traversable::class),
2405+
],
2406+
UnionType::class,
2407+
'object{foo: int}|Traversable',
2408+
];
2409+
yield [
2410+
[
2411+
new ObjectShapeType(['foo' => new IntegerType()], []),
2412+
new ObjectType(\ObjectShape\Foo::class),
2413+
],
2414+
UnionType::class,
2415+
'ObjectShape\Foo|object{foo: int}',
2416+
];
2417+
yield [
2418+
[
2419+
new ObjectShapeType(['foo' => new IntegerType()], []),
2420+
new ObjectType(ClassWithFooIntProperty::class),
2421+
],
2422+
ObjectShapeType::class,
2423+
'object{foo: int}',
2424+
];
2425+
yield [
2426+
[
2427+
new ObjectShapeType(['foo' => new IntegerType()], []),
2428+
new ObjectType(\ObjectShapesAcceptance\FinalClass::class),
2429+
],
2430+
UnionType::class,
2431+
'ObjectShapesAcceptance\FinalClass|object{foo: int}',
2432+
];
23992433
}
24002434

24012435
/**
@@ -3936,6 +3970,38 @@ public function dataIntersect(): iterable
39363970
NeverType::class,
39373971
'*NEVER*=implicit',
39383972
];
3973+
yield [
3974+
[
3975+
new ObjectShapeType(['foo' => new IntegerType()], []),
3976+
new ObjectType(Traversable::class),
3977+
],
3978+
IntersectionType::class,
3979+
'object{foo: int}&Traversable',
3980+
];
3981+
yield [
3982+
[
3983+
new ObjectShapeType(['foo' => new IntegerType()], []),
3984+
new ObjectType(\ObjectShapesAcceptance\Foo::class),
3985+
],
3986+
IntersectionType::class,
3987+
'ObjectShapesAcceptance\Foo&object{foo: int}',
3988+
];
3989+
yield [
3990+
[
3991+
new ObjectShapeType(['foo' => new IntegerType()], []),
3992+
new ObjectType(ClassWithFooIntProperty::class),
3993+
],
3994+
ObjectType::class,
3995+
'ObjectShapesAcceptance\ClassWithFooIntProperty',
3996+
];
3997+
yield [
3998+
[
3999+
new ObjectShapeType(['foo' => new IntegerType()], []),
4000+
new ObjectType(\ObjectShapesAcceptance\FinalClass::class),
4001+
],
4002+
PHP_VERSION_ID < 80200 ? IntersectionType::class : NeverType::class,
4003+
PHP_VERSION_ID < 80200 ? 'ObjectShapesAcceptance\FinalClass&object{foo: int}' : '*NEVER*=implicit',
4004+
];
39394005
}
39404006

39414007
/**

0 commit comments

Comments
 (0)