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="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" />
|
||||||
|
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 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());
|
||||||
|
@ -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
|
||||||
|
@ -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',
|
||||||
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user