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

Fix #1742 - allow is_countable assertion to mean something

This commit is contained in:
Brown 2019-06-07 15:49:10 -04:00
parent 4660beb28e
commit c81a9a8737
5 changed files with 134 additions and 6 deletions

View File

@ -1666,6 +1666,10 @@ class AssertionFinder
if ($first_var_name) { if ($first_var_name) {
$if_types[$first_var_name] = [[$prefix . 'iterable']]; $if_types[$first_var_name] = [[$prefix . 'iterable']];
} }
} elseif (self::hasCountableCheck($expr)) {
if ($first_var_name) {
$if_types[$first_var_name] = [[$prefix . 'countable']];
}
} elseif ($class_exists_check_type = self::hasClassExistsCheck($expr)) { } elseif ($class_exists_check_type = self::hasClassExistsCheck($expr)) {
if ($first_var_name) { if ($first_var_name) {
if ($class_exists_check_type === 2) { if ($class_exists_check_type === 2) {
@ -2298,6 +2302,20 @@ class AssertionFinder
return false; return false;
} }
/**
* @param PhpParser\Node\Expr\FuncCall $stmt
*
* @return bool
*/
protected static function hasCountableCheck(PhpParser\Node\Expr\FuncCall $stmt)
{
if ($stmt->name instanceof PhpParser\Node\Name && strtolower($stmt->name->parts[0]) === 'is_countable') {
return true;
}
return false;
}
/** /**
* @param PhpParser\Node\Expr\FuncCall $stmt * @param PhpParser\Node\Expr\FuncCall $stmt
* *

View File

@ -990,7 +990,7 @@ class TypeAnalyzer
return false; return false;
} }
if ($input_type_part->isTraversable($codebase)) { if ($input_type_part->hasTraversableInterface($codebase)) {
return true; return true;
} }
} }

View File

@ -249,7 +249,7 @@ abstract class Atomic
public function isIterable(Codebase $codebase) public function isIterable(Codebase $codebase)
{ {
return $this instanceof TIterable return $this instanceof TIterable
|| $this->isTraversable($codebase) || $this->hasTraversableInterface($codebase)
|| $this instanceof TArray || $this instanceof TArray
|| $this instanceof ObjectLike; || $this instanceof ObjectLike;
} }
@ -257,7 +257,17 @@ abstract class Atomic
/** /**
* @return bool * @return bool
*/ */
public function isTraversable(Codebase $codebase) public function isCountable(Codebase $codebase)
{
return $this->hasCountableInterface($codebase)
|| $this instanceof TArray
|| $this instanceof ObjectLike;
}
/**
* @return bool
*/
public function hasTraversableInterface(Codebase $codebase)
{ {
return $this instanceof TNamedObject return $this instanceof TNamedObject
&& (strtolower($this->value) === 'traversable' && (strtolower($this->value) === 'traversable'
@ -269,6 +279,40 @@ abstract class Atomic
$this->value, $this->value,
'Traversable' 'Traversable'
))) )))
|| ($this->extra_types
&& array_filter(
$this->extra_types,
function (Atomic $a) use ($codebase) : bool {
return $a->hasTraversableInterface($codebase);
}
)
)
);
}
/**
* @return bool
*/
public function hasCountableInterface(Codebase $codebase)
{
return $this instanceof TNamedObject
&& (strtolower($this->value) === 'countable'
|| ($codebase->classOrInterfaceExists($this->value)
&& ($codebase->classExtendsOrImplements(
$this->value,
'Countable'
) || $codebase->interfaceExtends(
$this->value,
'Countable'
)))
|| ($this->extra_types
&& array_filter(
$this->extra_types,
function (Atomic $a) use ($codebase) : bool {
return $a->hasCountableInterface($codebase);
}
)
)
); );
} }

View File

@ -728,7 +728,11 @@ class Reconciler
return Type::getMixed(); return Type::getMixed();
} }
if ($new_var_type === 'iterable' && !$existing_var_type->hasMixed()) { if ($new_var_type === 'iterable') {
if ($existing_var_type->hasMixed()) {
return new Type\Union([new Type\Atomic\TIterable]);
}
$iterable_types = []; $iterable_types = [];
$did_remove_type = false; $did_remove_type = false;
@ -736,7 +740,57 @@ class Reconciler
if ($type->isIterable($codebase)) { if ($type->isIterable($codebase)) {
$iterable_types[] = $type; $iterable_types[] = $type;
} elseif ($type instanceof TObject) { } elseif ($type instanceof TObject) {
$iterable_types[] = new Type\Atomic\TCallableObject(); $iterable_types[] = new Type\Atomic\TNamedObject('Traversable');
$did_remove_type = true;
} else {
$did_remove_type = true;
}
}
if ((!$iterable_types || !$did_remove_type) && !$is_equality) {
if ($key && $code_location) {
self::triggerIssueForImpossible(
$existing_var_type,
$old_var_type_string,
$key,
$new_var_type,
!$did_remove_type,
$code_location,
$suppressed_issues
);
}
}
if ($iterable_types) {
return new Type\Union($iterable_types);
}
$failed_reconciliation = 2;
return Type::getMixed();
}
if ($new_var_type === 'countable') {
if ($existing_var_type->hasMixed()) {
return new Type\Union([
new Type\Atomic\TArray([Type::getArrayKey(), Type::getMixed()]),
new Type\Atomic\TNamedObject('Countable')
]);
}
$iterable_types = [];
$did_remove_type = false;
foreach ($existing_var_atomic_types as $type) {
if ($type->isCountable($codebase)) {
$iterable_types[] = $type;
} elseif ($type instanceof TObject) {
$iterable_types[] = new TNamedObject('Countable');
$did_remove_type = true;
} elseif ($type instanceof TNamedObject || $type instanceof Type\Atomic\TIterable) {
$countable = new TNamedObject('Countable');
$type->extra_types[$countable->getKey()] = $countable;
$iterable_types[] = $type;
$did_remove_type = true; $did_remove_type = true;
} else { } else {
$did_remove_type = true; $did_remove_type = true;
@ -1850,7 +1904,7 @@ class Reconciler
return $existing_var_type; return $existing_var_type;
} }
if ($new_var_type === 'iterable') { if ($new_var_type === 'iterable' || $new_var_type === 'countable') {
$existing_var_type->removeType('array'); $existing_var_type->removeType('array');
} }

View File

@ -1713,6 +1713,18 @@ class FunctionCallTest extends TestCase
/** @psalm-suppress TooFewArguments */ /** @psalm-suppress TooFewArguments */
min(0);', min(0);',
], ],
'PHP73-allowIsCountableToInformType' => [
'<?php
function getObject() : iterable{
return [];
}
$iterableObject = getObject();
if (is_countable($iterableObject)) {
if (count($iterableObject) === 0) {}
}',
],
]; ];
} }