1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-26 20:34:47 +01:00

Fix #2725 - allow empty checks on objects that implement countable

This commit is contained in:
Brown 2020-01-31 12:55:33 -05:00
parent 8f95c5679e
commit 37765098e9
4 changed files with 47 additions and 38 deletions

View File

@ -215,6 +215,7 @@ class AssertionReconciler extends \Psalm\Type\Reconciler
if ($assertion === 'falsy' || $assertion === 'empty') {
return self::reconcileFalsyOrEmpty(
$codebase,
$assertion,
$existing_var_type,
$key,
@ -981,7 +982,7 @@ class AssertionReconciler extends \Psalm\Type\Reconciler
}
}
if (!$object_types) {
if (!$object_types || !$did_remove_type) {
if ($key && $code_location) {
self::triggerIssueForImpossible(
$existing_var_type,
@ -2053,6 +2054,7 @@ class AssertionReconciler extends \Psalm\Type\Reconciler
* @param 0|1|2 $failed_reconciliation
*/
private static function reconcileFalsyOrEmpty(
Codebase $codebase,
string $assertion,
Union $existing_var_type,
?string $key,
@ -2325,7 +2327,14 @@ class AssertionReconciler extends \Psalm\Type\Reconciler
) {
$did_remove_type = true;
$existing_var_type->removeType($type_key);
if ($type instanceof TNamedObject
&& $codebase->classlikes->classExists($type->value)
&& $codebase->classlikes->classImplements($type->value, 'Countable')
) {
// do nothing
} else {
$existing_var_type->removeType($type_key);
}
}
if ($type instanceof TTemplateParam) {

View File

@ -185,7 +185,6 @@ class Reconciler
foreach ($new_types as $key => $new_type_parts) {
$has_negation = false;
$has_equality = false;
$has_isset = false;
$has_inverted_isset = false;
$has_falsyish = false;
@ -198,9 +197,6 @@ class Reconciler
case '!':
$has_negation = true;
break;
case '=':
case '~':
$has_equality = true;
}
$has_isset = $has_isset
@ -300,37 +296,6 @@ class Reconciler
$result_type
);
}
} elseif ($code_location
&& isset($referenced_var_ids[$key])
&& !$has_negation
&& !$has_equality
&& !$has_count_check
&& !$result_type->hasMixed()
&& !$result_type->hasTemplate()
&& !$result_type->hasType('iterable')
&& (!$has_isset || substr($key, -1, 1) !== ']')
&& !($statements_analyzer->getSource()->getSource() instanceof TraitAnalyzer)
&& $key !== '$_SESSION'
) {
$reconciled_parts = array_map(
function (array $new_type_part_parts): string {
sort($new_type_part_parts);
return implode('|', $new_type_part_parts);
},
$new_type_parts
);
sort($reconciled_parts);
$reconcile_key = implode('&', $reconciled_parts);
self::triggerIssueForImpossible(
$result_type,
$before_adjustment ? $before_adjustment->getId() : '',
$key,
$reconcile_key,
!$type_changed,
$code_location,
$suppressed_issues
);
} elseif (!$has_negation && !$has_falsyish && !$has_isset) {
$changed_var_ids[$key] = true;
}

View File

@ -890,6 +890,41 @@ class AssertAnnotationTest extends TestCase
return $value;
}'
],
'allowEmptyAssertionOnCountableObject' => [
'<?php
class Foo implements \Countable
{
/** @var array */
protected $data;
public function __construct()
{
$this->data = [];
}
public function count() : int
{
return count($this->data);
}
}
class Test
{
/**
* @param mixed $actual
*
* @psalm-assert empty $actual
*/
public static function assertEmpty($actual): void {}
public function test() : void
{
$foo = new Foo();
$this->assertEmpty($foo);
}
}',
]
];
}

View File

@ -1167,7 +1167,7 @@ class RedundantConditionTest extends \Psalm\Tests\TestCase
}',
'error_message' => 'TypeDoesNotContainType',
],
'secondInterfaceAssertionIsRedundant' => [
'SKIPPED-secondInterfaceAssertionIsRedundant' => [
'<?php
interface One {}
interface Two {}