Skip to content

Commit

Permalink
Fix object shapes with interfaces and final classes
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Apr 20, 2023
1 parent ffe5931 commit f199980
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 4 deletions.
7 changes: 4 additions & 3 deletions src/Type/ObjectShapeType.php
Original file line number Diff line number Diff line change
Expand Up @@ -178,11 +178,12 @@ public function acceptsWithReason(Type $type, bool $strictTypes): AcceptsResult
$otherProperty = $type->getProperty($propertyName, $scope);
} catch (MissingPropertyFromReflectionException) {
return new AcceptsResult(
TrinaryLogic::createNo(),
$result->result,
[
sprintf(
'%s does not have property $%s.',
'%s %s not have property $%s.',
$type->describe(VerbosityLevel::typeOnly()),
$result->no() ? 'does' : 'might',
$propertyName,
),
],
Expand Down Expand Up @@ -278,7 +279,7 @@ public function isSuperTypeOf(Type $type): TrinaryLogic
try {
$otherProperty = $type->getProperty($propertyName, $scope);
} catch (MissingPropertyFromReflectionException) {
return TrinaryLogic::createNo();
return $result;
}

if (!$otherProperty->isPublic()) {
Expand Down
12 changes: 11 additions & 1 deletion tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2919,7 +2919,7 @@ public function testObjectShapes(): void
[
'Parameter #1 $o of method ObjectShapesAcceptance\Foo::doBar() expects object{foo: int, bar: string}, Exception given.',
14,
'Exception does not have property $foo.',
'Exception might not have property $foo.',
],
[
'Parameter #1 $o of method ObjectShapesAcceptance\Foo::doBar() expects object{foo: int, bar: string}, object{foo: string, bar: int} given.',
Expand Down Expand Up @@ -2989,6 +2989,16 @@ public function testObjectShapes(): void
157,
'Property ($foo) type int does not accept type string.',
],
[
'Parameter #1 $o of method ObjectShapesAcceptance\TestAcceptance::doFoo() expects object{foo: int}, Traversable given.',
209,
'Traversable might not have property $foo.',
],
[
'Parameter #1 $o of method ObjectShapesAcceptance\TestAcceptance::doFoo() expects object{foo: int}, ObjectShapesAcceptance\FinalClass given.',
210,
PHP_VERSION_ID < 80200 ? 'ObjectShapesAcceptance\FinalClass might not have property $foo.' : 'ObjectShapesAcceptance\FinalClass does not have property $foo.',
],
]);
}

Expand Down
38 changes: 38 additions & 0 deletions tests/PHPStan/Rules/Methods/data/object-shapes.php
Original file line number Diff line number Diff line change
Expand Up @@ -174,3 +174,41 @@ public function doBaz(object $o): void
}

}

final class FinalClass
{

}

class ClassWithFooIntProperty
{

/** @var int */
public $foo;

}

class TestAcceptance
{

/**
* @param object{foo: int} $o
* @return void
*/
public function doFoo(object $o): void
{

}

public function doBar(
\Traversable $traversable,
FinalClass $finalClass,
ClassWithFooIntProperty $classWithFooIntProperty
)
{
$this->doFoo($traversable);
$this->doFoo($finalClass);
$this->doFoo($classWithFooIntProperty);
}

}
66 changes: 66 additions & 0 deletions tests/PHPStan/Type/TypeCombinatorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use Exception;
use InvalidArgumentException;
use Iterator;
use ObjectShapesAcceptance\ClassWithFooIntProperty;
use PHPStan\Fixture\FinalClass;
use PHPStan\Testing\PHPStanTestCase;
use PHPStan\Type\Accessory\AccessoryLiteralStringType;
Expand Down Expand Up @@ -2396,6 +2397,39 @@ public function dataUnion(): iterable
UnionType::class,
'object{bar: string}|object{foo: int}',
];

yield [
[
new ObjectShapeType(['foo' => new IntegerType()], []),
new ObjectType(Traversable::class),
],
UnionType::class,
'object{foo: int}|Traversable',
];
yield [
[
new ObjectShapeType(['foo' => new IntegerType()], []),
new ObjectType(\ObjectShape\Foo::class),
],
UnionType::class,
'ObjectShape\Foo|object{foo: int}',
];
yield [
[
new ObjectShapeType(['foo' => new IntegerType()], []),
new ObjectType(ClassWithFooIntProperty::class),
],
ObjectShapeType::class,
'object{foo: int}',
];
yield [
[
new ObjectShapeType(['foo' => new IntegerType()], []),
new ObjectType(\ObjectShapesAcceptance\FinalClass::class),
],
UnionType::class,
'ObjectShapesAcceptance\FinalClass|object{foo: int}',
];
}

/**
Expand Down Expand Up @@ -3936,6 +3970,38 @@ public function dataIntersect(): iterable
NeverType::class,
'*NEVER*=implicit',
];
yield [
[
new ObjectShapeType(['foo' => new IntegerType()], []),
new ObjectType(Traversable::class),
],
IntersectionType::class,
'object{foo: int}&Traversable',
];
yield [
[
new ObjectShapeType(['foo' => new IntegerType()], []),
new ObjectType(\ObjectShapesAcceptance\Foo::class),
],
IntersectionType::class,
'ObjectShapesAcceptance\Foo&object{foo: int}',
];
yield [
[
new ObjectShapeType(['foo' => new IntegerType()], []),
new ObjectType(ClassWithFooIntProperty::class),
],
ObjectType::class,
'ObjectShapesAcceptance\ClassWithFooIntProperty',
];
yield [
[
new ObjectShapeType(['foo' => new IntegerType()], []),
new ObjectType(\ObjectShapesAcceptance\FinalClass::class),
],
PHP_VERSION_ID < 80200 ? IntersectionType::class : NeverType::class,
PHP_VERSION_ID < 80200 ? 'ObjectShapesAcceptance\FinalClass&object{foo: int}' : '*NEVER*=implicit',
];
}

/**
Expand Down

0 comments on commit f199980

Please sign in to comment.