Skip to content
Closed
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
9 changes: 8 additions & 1 deletion src/Analyser/ExprHandler/MatchHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -454,11 +454,18 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
}

if ($isExhaustive) {
$preMatchConditionalExpressions = $scope->getConditionalExpressions();
$armBodyFinalScope = null;
foreach ($armBodyScopes as $armBodyScope) {
$armBodyFinalScope = $armBodyScope->mergeWith($armBodyFinalScope);
}
$scope = $armBodyFinalScope ?? $scope;
if ($armBodyFinalScope !== null) {
// Prevent conditional expressions created during arm scope merging
// from leaking into the post-match scope. These encode relationships
// between arm-narrowed types (e.g. "if equals(A) is false, then
// equals(B) is true") that should not affect subsequent code.
$scope = $armBodyFinalScope->replaceConditionalExpressions($preMatchConditionalExpressions);
}
} else {
$armBodyFinalScope = null;
foreach ($armBodyScopes as $armBodyScope) {
Expand Down
33 changes: 33 additions & 0 deletions src/Analyser/MutatingScope.php
Original file line number Diff line number Diff line change
Expand Up @@ -3320,6 +3320,39 @@ public function addConditionalExpressions(string $exprString, array $conditional
);
}

/**
* @return array<string, ConditionalExpressionHolder[]>
*/
public function getConditionalExpressions(): array
{
return $this->conditionalExpressions;
}

/**
* @param array<string, ConditionalExpressionHolder[]> $conditionalExpressions
*/
public function replaceConditionalExpressions(array $conditionalExpressions): self
{
return $this->scopeFactory->create(
$this->context,
$this->isDeclareStrictTypes(),
$this->getFunction(),
$this->getNamespace(),
$this->expressionTypes,
$this->nativeExpressionTypes,
$conditionalExpressions,
$this->inClosureBindScopeClasses,
$this->anonymousFunctionReflection,
$this->inFirstLevelStatement,
$this->currentlyAssignedExpressions,
$this->currentlyAllowedUndefinedExpressions,
$this->inFunctionCallsStack,
$this->afterExtractCall,
$this->parentScope,
$this->nativeTypesPromoted,
);
}

public function exitFirstLevelStatements(): self
{
if (!$this->inFirstLevelStatement) {
Expand Down
6 changes: 6 additions & 0 deletions tests/PHPStan/Rules/Comparison/MatchExpressionRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,12 @@ public function testBug12790(): void
$this->analyse([__DIR__ . '/data/bug-12790.php'], []);
}

#[RequiresPhp('>= 8.0')]
public function testBug14368(): void
{
$this->analyse([__DIR__ . '/data/bug-14368.php'], []);
}

#[RequiresPhp('>= 8.0')]
public function testBug11310(): void
{
Expand Down
54 changes: 54 additions & 0 deletions tests/PHPStan/Rules/Comparison/data/bug-14368.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php // lint >= 8.0

namespace Bug14368;

final class Snowflake
{
public function __construct(
private readonly int $value,
) {}

public static function cast(int $snowflake): self
{
return new self($snowflake);
}

public function equals(?self $other): bool
{
return $this->value === $other?->value;
}
}

final class BalanceId
{
public static function work(): Snowflake
{
/** @var Snowflake */
static $work = Snowflake::cast(1);
return $work;
}

public static function holiday(): Snowflake
{
/** @var Snowflake */
static $holiday = Snowflake::cast(2);
return $holiday;
}
}

function test(Snowflake $balanceId): void
{
// First match — no error expected
$a = match (true) {
$balanceId->equals(BalanceId::work()) => -1.0,
$balanceId->equals(BalanceId::holiday()) => 1.0,
default => throw new \InvalidArgumentException(),
};

// Second match — should not report match.alwaysTrue
$b = match (true) {
$balanceId->equals(BalanceId::work()) => -2.0,
$balanceId->equals(BalanceId::holiday()) => 2.0,
default => throw new \InvalidArgumentException(),
};
}
Loading