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:
parent
b62719c9c8
commit
a0a7f8a98b
@ -384,6 +384,7 @@
|
||||
<xs:element name="UndefinedTrait" type="IssueHandlerType" minOccurs="0" />
|
||||
<xs:element name="UndefinedVariable" 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="UnimplementedInterfaceMethod" type="IssueHandlerType" minOccurs="0" />
|
||||
<xs:element name="UninitializedProperty" type="PropertyIssueHandlerType" minOccurs="0" />
|
||||
|
19
docs/running_psalm/issues/UnhandledMatchCondition.md
Normal file
19
docs/running_psalm/issues/UnhandledMatchCondition.md
Normal 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.
|
@ -4,6 +4,7 @@ namespace Psalm\Internal\Analyzer\Statements\Expression;
|
||||
use PhpParser;
|
||||
use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer;
|
||||
use Psalm\Internal\Analyzer\StatementsAnalyzer;
|
||||
use Psalm\Issue\UnhandledMatchCondition;
|
||||
use Psalm\Context;
|
||||
use Psalm\Type;
|
||||
use function strtolower;
|
||||
@ -132,6 +133,69 @@ class MatchAnalyzer
|
||||
$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);
|
||||
|
||||
$old_node_data->setType($stmt, $stmt_expr_type ?: Type::getMixed());
|
||||
|
@ -94,7 +94,7 @@ class DocumentationTest extends TestCase
|
||||
)
|
||||
);
|
||||
|
||||
$this->project_analyzer->setPhpVersion('7.3');
|
||||
$this->project_analyzer->setPhpVersion('8.0');
|
||||
}
|
||||
|
||||
public function testAllIssuesCoveredInConfigSchema(): void
|
||||
|
@ -126,6 +126,21 @@ class MatchTest extends TestCase
|
||||
false,
|
||||
'8.0'
|
||||
],
|
||||
'notAllEnumsMet' => [
|
||||
'<?php
|
||||
/**
|
||||
* @param "foo"|"bar" $foo
|
||||
*/
|
||||
function foo(string $foo): string {
|
||||
return match ($foo) {
|
||||
"foo" => "foo",
|
||||
};
|
||||
}',
|
||||
'error_message' => 'ParadoxicalCondition',
|
||||
[],
|
||||
false,
|
||||
'8.0',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user