Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion src/Reflection/Type/UnionTypeMethodReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,13 @@ public function getDocComment(): ?string

public function getAsserts(): Assertions
{
return Assertions::createEmpty();
$assertions = Assertions::createEmpty();

foreach ($this->methods as $method) {
$assertions = $assertions->intersectWith($method->getAsserts());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will merge assertions from both types. Which is a wrong operation. (The method has a misleading name but it's used in IntersectionTypeMethodReflection where it's appropriate.)

In a union type what needs to be done is we need to look at assertions from all places and do some kind of intersection, figure out the common assertions.

Let's say we have Foo|Bar and we call a method that's on both types.

One method does some kind of assertion and the other method does a different assertion. The correct performed assertion on the union is none because they have nothing in common.

So we need to look at on which expression the assertion is performed (in this case $this->getParam()) and if all involved methods on the union are doing an assertion on the same expression.

Then we need to take a look at the asserted type and also take it into account only if it's the same. (It's possible that they don't have to be the same but we'd have to figure out the right operation that we can be sure about.)

}

return $assertions;
}

public function acceptsNamedArguments(): TrinaryLogic
Expand Down
39 changes: 39 additions & 0 deletions tests/PHPStan/Analyser/nsrt/bug-11441.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php declare(strict_types=1);

namespace Bug11441;

use function PHPStan\Testing\assertType;

class Foo {
public function getParam(): ?string {
return 'foo';
}

/** @phpstan-assert !null $this->getParam() */
public function checkNotNull(): void {
if ($this->getParam() === null) {
throw new \Exception();
}
}
}

class Bar {
public function getParam(): ?int {
return 1;
}

/** @phpstan-assert !null $this->getParam() */
public function checkNotNull(): void {
if ($this->getParam() === null) {
throw new \Exception();
}
}
}

function test(Foo|Bar $obj): void {
assertType('int|string|null', $obj->getParam());

$obj->checkNotNull();

assertType('int|string', $obj->getParam());
}
40 changes: 40 additions & 0 deletions tests/PHPStan/Analyser/nsrt/bug-13358.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php declare(strict_types=1);

namespace Bug13358;

use function PHPStan\Testing\assertType;

/**
* @phpstan-sealed SystemActor|AnonymousVisitorActor
*/
abstract class Actor
{
/**
* @phpstan-assert-if-true SystemActor $this
*/
public function isSystem(): bool
{
return $this instanceof SystemActor;
}

/**
* @phpstan-assert-if-true AnonymousVisitorActor $this
*/
public function isAnonymousVisitor(): bool
{
return $this instanceof AnonymousVisitorActor;
}
}

class SystemActor extends Actor {}
class AnonymousVisitorActor extends Actor {}

function test(AnonymousVisitorActor|SystemActor $actor): void {
assertType('Bug13358\AnonymousVisitorActor|Bug13358\SystemActor', $actor);

if ($actor->isSystem()) {
assertType('Bug13358\SystemActor', $actor);
} else {
assertType('Bug13358\AnonymousVisitorActor', $actor);
}
}
Loading