1
0
mirror of https://github.com/danog/psalm.git synced 2025-01-22 05:41:20 +01:00

Catch unmatched matches

This commit is contained in:
Brown 2020-08-31 22:59:47 -04:00 committed by Daniil Gentili
parent b62719c9c8
commit a0a7f8a98b
Signed by: danog
GPG Key ID: 8C1BE3B34B230CA7
5 changed files with 100 additions and 1 deletions

View File

@ -384,6 +384,7 @@
<xs:element name="UndefinedTrait" type="IssueHandlerType" minOccurs="0" /> <xs:element name="UndefinedTrait" type="IssueHandlerType" minOccurs="0" />
<xs:element name="UndefinedVariable" type="IssueHandlerType" minOccurs="0" /> <xs:element name="UndefinedVariable" type="IssueHandlerType" minOccurs="0" />
<xs:element name="UnevaluatedCode" type="IssueHandlerType" minOccurs="0" /> <xs:element name="UnevaluatedCode" type="IssueHandlerType" minOccurs="0" />
<xs:element name="UnhandledMatchCondition" type="IssueHandlerType" minOccurs="0" />
<xs:element name="UnimplementedAbstractMethod" type="IssueHandlerType" minOccurs="0" /> <xs:element name="UnimplementedAbstractMethod" type="IssueHandlerType" minOccurs="0" />
<xs:element name="UnimplementedInterfaceMethod" type="IssueHandlerType" minOccurs="0" /> <xs:element name="UnimplementedInterfaceMethod" type="IssueHandlerType" minOccurs="0" />
<xs:element name="UninitializedProperty" type="PropertyIssueHandlerType" minOccurs="0" /> <xs:element name="UninitializedProperty" type="PropertyIssueHandlerType" minOccurs="0" />

View File

@ -0,0 +1,19 @@
# UnhandledMatchCondition
Emitted when a match expression does not handle one or more options.
```php
<?php
function matchOne(): string {
$foo = rand(0, 1) ? "foo" : "bar";
return match ($foo) {
'foo' => 'foo',
};
}
```
## Why this is bad
The above code will fail 50% of the time with an `UnhandledMatchError` error.

View File

@ -4,6 +4,7 @@ namespace Psalm\Internal\Analyzer\Statements\Expression;
use PhpParser; use PhpParser;
use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer; use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer;
use Psalm\Internal\Analyzer\StatementsAnalyzer; use Psalm\Internal\Analyzer\StatementsAnalyzer;
use Psalm\Issue\UnhandledMatchCondition;
use Psalm\Context; use Psalm\Context;
use Psalm\Type; use Psalm\Type;
use function strtolower; use function strtolower;
@ -132,6 +133,69 @@ class MatchAnalyzer
$statements_analyzer->removeSuppressedIssues(['RedundantConditionGivenDocblockType']); $statements_analyzer->removeSuppressedIssues(['RedundantConditionGivenDocblockType']);
} }
if ($switch_var_id) {
$codebase = $statements_analyzer->getCodebase();
$all_conds = $last_arm->conds ?: [];
foreach ($arms as $arm) {
$all_conds = array_merge($arm->conds, $all_conds);
}
$all_match_condition = self::convertCondsToConditional(
$all_conds,
$match_condition,
$match_condition->getAttributes()
);
$clauses = \Psalm\Type\Algebra::getFormula(
\spl_object_id($all_match_condition),
\spl_object_id($all_match_condition),
$all_match_condition,
$context->self,
$statements_analyzer,
$codebase,
false,
false
);
$reconcilable_types = \Psalm\Type\Algebra::getTruthsFromFormula(
\Psalm\Type\Algebra::negateFormula($clauses)
);
// if the if has an || in the conditional, we cannot easily reason about it
if ($reconcilable_types) {
$changed_var_ids = [];
$vars_in_scope_reconciled = \Psalm\Type\Reconciler::reconcileKeyedTypes(
$reconcilable_types,
[],
$context->vars_in_scope,
$changed_var_ids,
[],
$statements_analyzer,
[],
$context->inside_loop,
null
);
if (isset($vars_in_scope_reconciled[$switch_var_id])) {
if ($vars_in_scope_reconciled[$switch_var_id]->hasLiteralValue()) {
if (\Psalm\IssueBuffer::accepts(
new UnhandledMatchCondition(
'This match expression is not exhaustive - consider values '
. $vars_in_scope_reconciled[$switch_var_id]->getId(),
new \Psalm\CodeLocation($statements_analyzer->getSource(), $match_condition)
),
$statements_analyzer->getSuppressedIssues()
)) {
return false;
}
}
}
}
}
$stmt_expr_type = $statements_analyzer->node_data->getType($ternary); $stmt_expr_type = $statements_analyzer->node_data->getType($ternary);
$old_node_data->setType($stmt, $stmt_expr_type ?: Type::getMixed()); $old_node_data->setType($stmt, $stmt_expr_type ?: Type::getMixed());

View File

@ -94,7 +94,7 @@ class DocumentationTest extends TestCase
) )
); );
$this->project_analyzer->setPhpVersion('7.3'); $this->project_analyzer->setPhpVersion('8.0');
} }
public function testAllIssuesCoveredInConfigSchema(): void public function testAllIssuesCoveredInConfigSchema(): void

View File

@ -126,6 +126,21 @@ class MatchTest extends TestCase
false, false,
'8.0' '8.0'
], ],
'notAllEnumsMet' => [
'<?php
/**
* @param "foo"|"bar" $foo
*/
function foo(string $foo): string {
return match ($foo) {
"foo" => "foo",
};
}',
'error_message' => 'ParadoxicalCondition',
[],
false,
'8.0',
],
]; ];
} }
} }