mirror of
https://github.com/danog/psalm.git
synced 2024-11-30 04:39:00 +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;
|
||||
}
|
||||
|
@ -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