1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-27 04:45:20 +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') { if ($assertion === 'falsy' || $assertion === 'empty') {
return self::reconcileFalsyOrEmpty( return self::reconcileFalsyOrEmpty(
$codebase,
$assertion, $assertion,
$existing_var_type, $existing_var_type,
$key, $key,
@ -981,7 +982,7 @@ class AssertionReconciler extends \Psalm\Type\Reconciler
} }
} }
if (!$object_types) { if (!$object_types || !$did_remove_type) {
if ($key && $code_location) { if ($key && $code_location) {
self::triggerIssueForImpossible( self::triggerIssueForImpossible(
$existing_var_type, $existing_var_type,
@ -2053,6 +2054,7 @@ class AssertionReconciler extends \Psalm\Type\Reconciler
* @param 0|1|2 $failed_reconciliation * @param 0|1|2 $failed_reconciliation
*/ */
private static function reconcileFalsyOrEmpty( private static function reconcileFalsyOrEmpty(
Codebase $codebase,
string $assertion, string $assertion,
Union $existing_var_type, Union $existing_var_type,
?string $key, ?string $key,
@ -2325,8 +2327,15 @@ class AssertionReconciler extends \Psalm\Type\Reconciler
) { ) {
$did_remove_type = true; $did_remove_type = true;
if ($type instanceof TNamedObject
&& $codebase->classlikes->classExists($type->value)
&& $codebase->classlikes->classImplements($type->value, 'Countable')
) {
// do nothing
} else {
$existing_var_type->removeType($type_key); $existing_var_type->removeType($type_key);
} }
}
if ($type instanceof TTemplateParam) { if ($type instanceof TTemplateParam) {
$did_remove_type = true; $did_remove_type = true;

View File

@ -185,7 +185,6 @@ class Reconciler
foreach ($new_types as $key => $new_type_parts) { foreach ($new_types as $key => $new_type_parts) {
$has_negation = false; $has_negation = false;
$has_equality = false;
$has_isset = false; $has_isset = false;
$has_inverted_isset = false; $has_inverted_isset = false;
$has_falsyish = false; $has_falsyish = false;
@ -198,9 +197,6 @@ class Reconciler
case '!': case '!':
$has_negation = true; $has_negation = true;
break; break;
case '=':
case '~':
$has_equality = true;
} }
$has_isset = $has_isset $has_isset = $has_isset
@ -300,37 +296,6 @@ class Reconciler
$result_type $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) { } elseif (!$has_negation && !$has_falsyish && !$has_isset) {
$changed_var_ids[$key] = true; $changed_var_ids[$key] = true;
} }

View File

@ -890,6 +890,41 @@ class AssertAnnotationTest extends TestCase
return $value; 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', 'error_message' => 'TypeDoesNotContainType',
], ],
'secondInterfaceAssertionIsRedundant' => [ 'SKIPPED-secondInterfaceAssertionIsRedundant' => [
'<?php '<?php
interface One {} interface One {}
interface Two {} interface Two {}