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

Use better inference for getAttributes return type

Fixes #4367
This commit is contained in:
Matt Brown 2020-10-30 17:37:16 -04:00 committed by Daniil Gentili
parent c64597fa96
commit b217916f37
Signed by: danog
GPG Key ID: 8C1BE3B34B230CA7
7 changed files with 221 additions and 68 deletions

View File

@ -621,6 +621,16 @@ class CommentAnalyzer
}
}
if (isset($parsed_docblock->tags['since'])) {
$since = trim(reset($parsed_docblock->tags['since']));
if (preg_match('/^[4578]\.\d(\.\d+)?$/', $since)) {
$since_parts = explode('.', $since);
$info->since_php_major_version = (int)$since_parts[0];
$info->since_php_minor_version = (int)$since_parts[1];
}
}
if (isset($parsed_docblock->tags['deprecated'])) {
$info->deprecated = true;
}

View File

@ -343,7 +343,9 @@ class Methods
if (InternalCallMapHandler::inCallMap((string) $callmap_id)) {
$class_storage = $this->classlike_storage_provider->get($callmap_id->fq_class_name);
if (!$class_storage->stubbed) {
$declaring_method_name = $declaring_method_id ? $declaring_method_id->method_name : $method_name;
if (!$class_storage->stubbed || empty($class_storage->methods[$declaring_method_name]->stubbed)) {
$function_callables = InternalCallMapHandler::getCallablesFromCallMap((string) $callmap_id);
if ($function_callables === null) {

View File

@ -129,7 +129,9 @@ class FunctionLikeNodeScanner
$fq_classlike_name = null;
$is_functionlike_override = false;
$function_id = null;
$method_name_lc = null;
$method_id = null;
if ($fake_method && $stmt instanceof PhpParser\Node\Stmt\ClassMethod) {
$cased_function_id = '@method ' . $stmt->name->name;
@ -208,16 +210,6 @@ class FunctionLikeNodeScanner
}
}
}
if ($this->codebase->register_stub_files
|| ($this->codebase->register_autoload_files
&& !$this->codebase->functions->hasStubbedFunction($function_id))
) {
$this->codebase->functions->addGlobalFunction($function_id, $storage);
}
$this->file_storage->functions[$function_id] = $storage;
$this->file_storage->declaring_function_ids[$function_id] = strtolower($this->file_path);
} elseif ($stmt instanceof PhpParser\Node\Stmt\ClassMethod) {
if (!$this->classlike_storage) {
throw new \LogicException('$this->classlike_storage should not be null');
@ -255,17 +247,17 @@ class FunctionLikeNodeScanner
$duplicate_method_storage->has_visitor_issues = true;
return false;
} else {
$is_functionlike_override = true;
}
$is_functionlike_override = true;
$storage = $this->storage = $classlike_storage->methods[$method_name_lc];
}
if (!$storage) {
$storage = $this->storage = $classlike_storage->methods[$method_name_lc] = new MethodStorage();
$storage = $this->storage = new MethodStorage();
}
$storage->stubbed = $this->codebase->register_stub_files;
$storage->defining_fqcln = $fq_classlike_name;
$class_name_parts = explode('\\', $fq_classlike_name);
@ -296,28 +288,11 @@ class FunctionLikeNodeScanner
$method_name_lc
);
$classlike_storage->declaring_method_ids[$method_name_lc]
= $classlike_storage->appearing_method_ids[$method_name_lc]
= $method_id;
if (!$stmt->isPrivate() || $method_name_lc === '__construct' || $classlike_storage->is_trait) {
$classlike_storage->inheritable_method_ids[$method_name_lc] = $method_id;
}
if (!isset($classlike_storage->overridden_method_ids[$method_name_lc])) {
$classlike_storage->overridden_method_ids[$method_name_lc] = [];
}
$storage->is_static = $stmt->isStatic();
$storage->abstract = $stmt->isAbstract();
$storage->final = $classlike_storage->final || $stmt->isFinal();
if ($storage->final && $method_name_lc === '__construct') {
// a bit of a hack, but makes sure that `new static` works for these classes
$classlike_storage->preserve_constructor_signature = true;
}
if ($stmt->isPrivate()) {
$storage->visibility = ClassLikeAnalyzer::VISIBILITY_PRIVATE;
} elseif ($stmt->isProtected()) {
@ -634,6 +609,18 @@ class FunctionLikeNodeScanner
}
if ($docblock_info) {
if ($docblock_info->since_php_major_version && !$this->aliases->namespace) {
if ($docblock_info->since_php_major_version > $this->codebase->php_major_version) {
return false;
}
if ($docblock_info->since_php_major_version === $this->codebase->php_major_version
&& $docblock_info->since_php_minor_version > $this->codebase->php_minor_version
) {
return false;
}
}
FunctionLike\DocblockScanner::addDocblockInfo(
$this->codebase,
$this->file_scanner,
@ -652,6 +639,53 @@ class FunctionLikeNodeScanner
}
}
// register the functionlike once the @since check has been completed
if ($stmt instanceof PhpParser\Node\Stmt\Function_
&& $function_id
&& $storage instanceof FunctionStorage
) {
if ($this->codebase->register_stub_files
|| ($this->codebase->register_autoload_files
&& !$this->codebase->functions->hasStubbedFunction($function_id))
) {
$this->codebase->functions->addGlobalFunction($function_id, $storage);
}
$this->file_storage->functions[$function_id] = $storage;
$this->file_storage->declaring_function_ids[$function_id] = strtolower($this->file_path);
} elseif ($stmt instanceof PhpParser\Node\Stmt\ClassMethod
&& $classlike_storage
&& $storage instanceof MethodStorage
&& $method_name_lc
&& !$fake_method
&& $method_id
) {
$classlike_storage->methods[$method_name_lc] = $storage;
$classlike_storage->declaring_method_ids[$method_name_lc]
= $classlike_storage->appearing_method_ids[$method_name_lc]
= $method_id;
if (!$stmt->isPrivate() || $method_name_lc === '__construct' || $classlike_storage->is_trait) {
$classlike_storage->inheritable_method_ids[$method_name_lc] = $method_id;
}
if (!isset($classlike_storage->overridden_method_ids[$method_name_lc])) {
$classlike_storage->overridden_method_ids[$method_name_lc] = [];
}
if ($storage->final && $method_name_lc === '__construct') {
// a bit of a hack, but makes sure that `new static` works for these classes
$classlike_storage->preserve_constructor_signature = true;
}
} elseif (($stmt instanceof PhpParser\Node\Expr\Closure
|| $stmt instanceof PhpParser\Node\Expr\ArrowFunction)
&& $function_id
&& $storage instanceof FunctionStorage
) {
$this->file_storage->functions[$function_id] = $storage;
}
if ($classlike_storage && $method_name_lc === '__construct') {
foreach ($stmt->getParams() as $param) {
if (!$param->flags || !$param->var instanceof PhpParser\Node\Expr\Variable) {

View File

@ -201,4 +201,14 @@ class FunctionDocblockComment
/** @var bool */
public $stub_override = false;
/**
* @var int
*/
public $since_php_major_version = 0;
/**
* @var int
*/
public $since_php_minor_version = 0;
}

View File

@ -79,4 +79,8 @@ class MethodStorage extends FunctionLikeStorage
* @var Type\Union|null
*/
public $if_this_is_type = null;
/*
* @var bool
*/
public $stubbed = false;
}

View File

@ -218,7 +218,7 @@ class CachingIterator extends IteratorIterator implements OuterIterator , ArrayA
/**
* @param Iterator<TKey, TValue> $iterator
*/
public function __construct(Iterator $iterator, int $flags = self::CALL_TOSTRING) {}
public function __construct(Iterator $iterator, int $flags = self::CALL_TOSTRING) {}
/** @return bool */
public function hasNext () {}
@ -246,7 +246,7 @@ class InfiniteIterator extends IteratorIterator implements OuterIterator {
/**
* @param Iterator<TKey, TValue> $iterator
*/
public function __construct(Iterator $iterator) {}
public function __construct(Iterator $iterator) {}
/**
* @return TValue Can return any type.
@ -267,11 +267,11 @@ class InfiniteIterator extends IteratorIterator implements OuterIterator {
*
* @template-extends IteratorIterator<TKey, TValue>
*/
class LimitIterator extends IteratorIterator implements OuterIterator {
class LimitIterator extends IteratorIterator implements OuterIterator {
/**
* @param Iterator<TKey, TValue> $iterator
*/
public function __construct(Iterator $iterator, int $offset = 0, int $count = -1) {}
public function __construct(Iterator $iterator, int $offset = 0, int $count = -1) {}
/**
* @return TValue Can return any type.
@ -292,12 +292,12 @@ class LimitIterator extends IteratorIterator implements OuterIterator {
*
* @template-extends FilterIterator<TKey, TValue>
*/
class CallbackFilterIterator extends FilterIterator implements OuterIterator {
class CallbackFilterIterator extends FilterIterator implements OuterIterator {
/**
* @param Iterator<TKey, TValue> $iterator
* @param callable(TValue, TKey, Iterator<TKey, TValue>): bool $callback
*/
public function __construct(Iterator $iterator, callable $callback) {}
public function __construct(Iterator $iterator, callable $callback) {}
/**
* @return TValue Can return any type.
@ -316,11 +316,11 @@ class CallbackFilterIterator extends FilterIterator implements OuterIterator {
*
* @template-extends IteratorIterator<TKey, TValue>
*/
class NoRewindIterator extends IteratorIterator {
class NoRewindIterator extends IteratorIterator {
/**
* @param Iterator<TKey, TValue> $iterator
*/
public function __construct(Iterator $iterator) {}
public function __construct(Iterator $iterator) {}
/**
* @return TValue Can return any type.
@ -1131,7 +1131,7 @@ class DOMNamedNodeMap implements Traversable, Countable {
* @template TKey
* @template TValue
* @template-implements Iterator<TKey, TValue>
* @template-implements ArrayAccess<TKey, TValue>
* @template-implements ArrayAccess<TKey, TValue>
*/
class SplDoublyLinkedList implements Iterator, Countable, ArrayAccess, Serializable
{
@ -1298,9 +1298,9 @@ class SplDoublyLinkedList implements Iterator, Countable, ArrayAccess, Serializa
}
/**
* The SplFixedArray class provides the main functionalities of array.
* The main differences between a SplFixedArray and a normal PHP array is that
* the SplFixedArray is of fixed length and allows only integers within the range as indexes.
* The SplFixedArray class provides the main functionalities of array.
* The main differences between a SplFixedArray and a normal PHP array is that
* the SplFixedArray is of fixed length and allows only integers within the range as indexes.
* The advantage is that it uses less memory than a standard array.
*
* @link https://php.net/manual/en/class.splfixedarray.php
@ -1313,7 +1313,7 @@ class SplFixedArray implements Iterator, ArrayAccess, Countable {
/**
* Constructs a new fixed array
*
* Initializes a fixed array with a number of NULL values equal to size.
* Initializes a fixed array with a number of NULL values equal to size.
* @link https://php.net/manual/en/splfixedarray.construct.php
*
* @param int $size The size of the fixed array. This expects a number between 0 and PHP_INT_MAX.
@ -1329,7 +1329,7 @@ class SplFixedArray implements Iterator, ArrayAccess, Countable {
* @template TInValue
* @param array<int, TInValue> $array The array to import
* @param bool $save_indexes [optional] Try to save the numeric indexes used in the original array.
*
*
* @return SplFixedArray<TInValue> Instance of SplFixedArray containing the array content
* @since 5.3.0
@ -1417,7 +1417,7 @@ class SplFixedArray implements Iterator, ArrayAccess, Countable {
* @since 5.3.0
*/
public function next(): void {}
/**
* Returns whether the specified index exists
* @link https://php.net/manual/en/splfixedarray.offsetexists.php
@ -1521,22 +1521,22 @@ class SplHeap implements Iterator, Countable {
*
* @param TValue $value1 The value of the first node being compared.
* @param TValue $value2 The value of the second node being compared.
* @return int Positive integer if value1 is greater than value2, 0 if they are equal, negative integer otherwise.
* @return int Positive integer if value1 is greater than value2, 0 if they are equal, negative integer otherwise.
*
* @since 5.3.0
*/
protected abstract function compare($value1, $value2): int {}
/**
* Counts the number of elements in the heap
* @link https://php.net/manual/en/splheap.count.php
*
* @return int The number of elements in the heap.
* @return int The number of elements in the heap.
*
* @since 5.3.0
*/
public function count(): int {}
/**
* Get the current datastructure node.
* @link https://php.net/manual/en/splheap.current.php
@ -1546,7 +1546,7 @@ class SplHeap implements Iterator, Countable {
* @since 5.3.0
*/
public function current() {}
/**
* Extracts a node from top of the heap and sift up
* @link https://php.net/manual/en/splheap.extract.php
@ -1589,7 +1589,7 @@ class SplHeap implements Iterator, Countable {
public function isEmpty(): bool {}
/**
* Return current node index
* Return current node index
* @link https://php.net/manual/en/splheap.key.php
*
* @return int The current node index
@ -1599,7 +1599,7 @@ class SplHeap implements Iterator, Countable {
public function key() {}
/**
* Move to the next node. This will delete the top node of the heap.
* Move to the next node. This will delete the top node of the heap.
* @link https://php.net/manual/en/splheap.next.php
*
* @return void
@ -1617,7 +1617,7 @@ class SplHeap implements Iterator, Countable {
* @since 5.3.0
*/
public function recoverFromCorruption(): void {}
/**
* Rewind iterator back to the start (no-op)
* @link https://php.net/manual/en/splheap.rewind.php
@ -1627,7 +1627,7 @@ class SplHeap implements Iterator, Countable {
* @since 5.3.0
*/
public function rewind(): void {}
/**
* Peeks at the node from the top of the heap
* @link https://php.net/manual/en/splheap.top.php
@ -1637,12 +1637,12 @@ class SplHeap implements Iterator, Countable {
* @since 5.3.0
*/
public function top() {}
/**
* Check whether the heap contains any more nodes
* @link https://php.net/manual/en/splheap.valid.php
*
* @return bool Returns true if the heap contains any more nodes, false otherwise.
* @return bool Returns true if the heap contains any more nodes, false otherwise.
*
* @since 5.3.0
*/
@ -1700,22 +1700,22 @@ class SplPriorityQueue implements Iterator, Countable {
*
* @param TValue $priority1 The priority of the first node being compared.
* @param TValue $priority2 The priority of the second node being compared.
* @return int Positive integer if priority1 is greater than priority2, 0 if they are equal, negative integer otherwise.
* @return int Positive integer if priority1 is greater than priority2, 0 if they are equal, negative integer otherwise.
*
* @since 5.3.0
*/
public function compare($priority1, $priority2): int {}
/**
* Counts the number of elements in the queue
* @link https://php.net/manual/en/splpriorityqueue.count.php
*
* @return int The number of elements in the queue.
* @return int The number of elements in the queue.
*
* @since 5.3.0
*/
public function count(): int {}
/**
* Get the current datastructure node.
* @link https://php.net/manual/en/splpriorityqueue.current.php
@ -1725,7 +1725,7 @@ class SplPriorityQueue implements Iterator, Countable {
* @since 5.3.0
*/
public function current() {}
/**
* Extracts a node from top of the queue and sift up
* @link https://php.net/manual/en/splpriorityqueue.extract.php
@ -1781,7 +1781,7 @@ class SplPriorityQueue implements Iterator, Countable {
public function isEmpty(): bool {}
/**
* Return current node index
* Return current node index
* @link https://php.net/manual/en/splpriorityqueue.key.php
*
* @return int The current node index
@ -1809,7 +1809,7 @@ class SplPriorityQueue implements Iterator, Countable {
* @since 5.3.0
*/
public function recoverFromCorruption(): void {}
/**
* Rewind iterator back to the start (no-op)
* @link https://php.net/manual/en/splpriorityqueue.rewind.php
@ -1819,12 +1819,12 @@ class SplPriorityQueue implements Iterator, Countable {
* @since 5.3.0
*/
public function rewind(): void {}
/**
* Sets the mode of extraction
* @link https://php.net/manual/en/splpriorityqueue.setextractflags.php
*
* @param SplPriorityQueue::EXTR_* $flags Defines what is extracted by SplPriorityQueue::current(), SplPriorityQueue::top() and SplPriorityQueue::extract().
* @param SplPriorityQueue::EXTR_* $flags Defines what is extracted by SplPriorityQueue::current(), SplPriorityQueue::top() and SplPriorityQueue::extract().
*
* @return void
*
@ -1841,12 +1841,12 @@ class SplPriorityQueue implements Iterator, Countable {
* @since 5.3.0
*/
public function top() {}
/**
* Check whether the queue contains any more nodes
* @link https://php.net/manual/en/splpriorityqueue.valid.php
*
* @return bool Returns true if the queue contains any more nodes, false otherwise.
* @return bool Returns true if the queue contains any more nodes, false otherwise.
*
* @since 5.3.0
*/
@ -2141,6 +2141,58 @@ class ReflectionClass implements Reflector {
* @psalm-ignore-nullable-return
*/
public function getTraitNames(): array {}
/**
* @since 8.0
* @template TClass as object
* @param class-string<TClass>|null $name
* @return array<ReflectionAttribute<TClass>>
*/
public function getAttributes(?string $name = null, int $flags = 0): array {}
}
class ReflectionFunction
{
/**
* @since 8.0
* @template TClass as object
* @param class-string<TClass>|null $name
* @return array<ReflectionAttribute<TClass>>
*/
public function getAttributes(?string $name = null, int $flags = 0): array {}
}
class ReflectionProperty
{
/**
* @since 8.0
* @template TClass as object
* @param class-string<TClass>|null $name
* @return array<ReflectionAttribute<TClass>>
*/
public function getAttributes(?string $name = null, int $flags = 0): array {}
}
class ReflectionMethod
{
/**
* @since 8.0
* @template TClass as object
* @param class-string<TClass>|null $name
* @return array<ReflectionAttribute<TClass>>
*/
public function getAttributes(?string $name = null, int $flags = 0): array {}
}
class ReflectionClassConstant
{
/**
* @since 8.0
* @template TClass as object
* @param class-string<TClass>|null $name
* @return array<ReflectionAttribute<TClass>>
*/
public function getAttributes(?string $name = null, int $flags = 0): array {}
}
/**

View File

@ -82,6 +82,26 @@ class AttributeTest extends TestCase
[],
'8.0'
],
'testReflectingClass' => [
'<?php
abstract class BaseAttribute {
public function __construct(public string $name) {}
}
#[Attribute(Attribute::TARGET_CLASS)]
class Table extends BaseAttribute {}
/** @param class-string $s */
function foo(string $s) : void {
foreach ((new ReflectionClass($s))->getAttributes(BaseAttribute::class, 2) as $attr) {
$attribute = $attr->newInstance();
echo $attribute->name;
}
}',
[],
[],
'8.0'
],
];
}
@ -155,6 +175,27 @@ class AttributeTest extends TestCase
false,
'8.0'
],
'testReflectingClass74' => [
'<?php
abstract class BaseAttribute {
public function __construct(public string $name) {}
}
#[Attribute(Attribute::TARGET_CLASS)]
class Table extends BaseAttribute {}
/** @param class-string $s */
function foo(string $s) : void {
foreach ((new ReflectionClass($s))->getAttributes(BaseAttribute::class, 2) as $attr) {
$attribute = $attr->newInstance();
echo $attribute->name;
}
}',
'error_message' => 'UndefinedMethod',
[],
false,
'7.4'
],
];
}
}