mirror of
https://github.com/danog/psalm.git
synced 2024-11-26 20:34:47 +01:00
parent
c64597fa96
commit
b217916f37
@ -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;
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -79,4 +79,8 @@ class MethodStorage extends FunctionLikeStorage
|
||||
* @var Type\Union|null
|
||||
*/
|
||||
public $if_this_is_type = null;
|
||||
/*
|
||||
* @var bool
|
||||
*/
|
||||
public $stubbed = false;
|
||||
}
|
||||
|
@ -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 {}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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'
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user