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
10 changes: 9 additions & 1 deletion src/Analyser/MutatingScope.php
Original file line number Diff line number Diff line change
Expand Up @@ -3833,7 +3833,15 @@ public function filterBySpecifiedTypes(SpecifiedTypes $specifiedTypes): self
}

$conditions[$conditionalExprString][] = $conditionalExpression;
$specifiedExpressions[$conditionalExprString] = $conditionalExpression->getTypeHolder();
$newTypeHolder = $conditionalExpression->getTypeHolder();
if (array_key_exists($conditionalExprString, $specifiedExpressions) && $specifiedExpressions[$conditionalExprString]->getCertainty()->yes() && $newTypeHolder->getCertainty()->yes()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

It's unclear to me why we're only concerned about Yes & Yes.

Looking at this code, the last newTypeHolder override all the previous one if there is some Maybe certainty in the game.

Is this condition clear to you @staabm ?

Copy link
Contributor

Choose a reason for hiding this comment

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

Updating the code to

if (array_key_exists($conditionalExprString, $specifiedExpressions)) {

does not creates any failure. I feel like we're lacking of coverage. But I never worked with ExpressionTypeHolder...

$specifiedExpressions[$conditionalExprString] = ExpressionTypeHolder::createYes(
Copy link
Contributor

Choose a reason for hiding this comment

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

ExpressionTypeHolder has a method and

public function and(self $other): self
	{
		if ($this->type === $other->type || $this->type->equals($other->type)) {
			if ($this->certainty->and($other->certainty)->yes()) {
				return $this;
			}

			if ($this->certainty->maybe()) {
				return $this;
			}

			return $other;
		}

		return new self(
			$this->expr,
			TypeCombinator::union($this->type, $other->type),
			$this->certainty->and($other->certainty),
		);
	}

Should we introduce a new method for ExpressionTypeHolder which does the intersect ?

$newTypeHolder->getExpr(),
TypeCombinator::intersect($specifiedExpressions[$conditionalExprString]->getType(), $newTypeHolder->getType()),
Copy link
Contributor

Choose a reason for hiding this comment

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

This might add a lot of possibly useless intersect calls.

For instance if the next newTypeHolder does not have a certainty of Yes. we will override the value.

Might be better to check it first by computing all the newTypeHolder and

  • if they all have a yes certainty we intersect them
  • if not, we take the last one

WDYT @staabm ?

);
} else {
$specifiedExpressions[$conditionalExprString] = $newTypeHolder;
}
}
}
}
Expand Down
25 changes: 25 additions & 0 deletions tests/PHPStan/Analyser/nsrt/bug-14211.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php declare(strict_types=1);

namespace Bug14211;

use function PHPStan\Testing\assertType;

/** @param array<mixed> $data */
function doSomething(array $data): bool {

if (!isset($data['x']))
return false;

$m = isset($data['y']);

if ($m) {
assertType('true', $m);
}
assertType('bool', $m);

if ($m) {
assertType('true', $m);
}

return true;
}
Loading