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

@ -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'
],
];
}
}