1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-27 04:45:20 +01:00

Merge pull request #7692 from AndrolGenhald/bugfix/7685-attribute-analysis

Analyze attribute statements instead of constructing virtual statements.
This commit is contained in:
orklah 2022-02-20 18:57:11 +01:00 committed by GitHub
commit ad91df5ee1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 77 additions and 95 deletions

View File

@ -2,19 +2,17 @@
namespace Psalm\Internal\Analyzer;
use PhpParser\Node\AttributeGroup;
use PhpParser\Node\Expr\New_;
use PhpParser\Node\Stmt;
use PhpParser\Node\Stmt\Expression;
use Psalm\Context;
use Psalm\Internal\Analyzer\StatementsAnalyzer;
use Psalm\Internal\Codebase\ConstantTypeResolver;
use Psalm\Internal\Provider\NodeDataProvider;
use Psalm\Internal\Scanner\UnresolvedConstantComponent;
use Psalm\Internal\Stubs\Generator\StubsGenerator;
use Psalm\Issue\InvalidAttribute;
use Psalm\IssueBuffer;
use Psalm\Node\Expr\VirtualNew;
use Psalm\Node\Name\VirtualFullyQualified;
use Psalm\Node\Stmt\VirtualExpression;
use Psalm\Node\VirtualArg;
use Psalm\Node\VirtualIdentifier;
use Psalm\Storage\AttributeStorage;
use Psalm\Storage\ClassLikeStorage;
use Psalm\Type\Union;
@ -30,6 +28,7 @@ class AttributeAnalyzer
public static function analyze(
SourceAnalyzer $source,
AttributeStorage $attribute,
AttributeGroup $attribute_group,
array $suppressed_issues,
int $target,
?ClassLikeStorage $classlike_storage = null
@ -107,77 +106,12 @@ class AttributeAnalyzer
self::checkAttributeTargets($source, $attribute, $target);
$node_args = [];
foreach ($attribute->args as $storage_arg) {
$type = $storage_arg->type;
if ($type instanceof UnresolvedConstantComponent) {
$type = new Union([
ConstantTypeResolver::resolve(
$codebase->classlikes,
$type,
$source instanceof StatementsAnalyzer ? $source : null
)
]);
}
if ($type->isMixed()) {
return;
}
$type_expr = StubsGenerator::getExpressionFromType(
$type
);
$arg_attributes = [
'startFilePos' => $storage_arg->location->raw_file_start,
'endFilePos' => $storage_arg->location->raw_file_end,
'startLine' => $storage_arg->location->raw_line_number
];
$type_expr->setAttributes($arg_attributes);
$node_args[] = new VirtualArg(
$type_expr,
false,
false,
$arg_attributes,
$storage_arg->name
? new VirtualIdentifier(
$storage_arg->name,
$arg_attributes
)
: null
);
}
$new_stmt = new VirtualNew(
new VirtualFullyQualified(
$attribute->fq_class_name,
[
'startFilePos' => $attribute->name_location->raw_file_start,
'endFilePos' => $attribute->name_location->raw_file_end,
'startLine' => $attribute->name_location->raw_line_number
]
),
$node_args,
[
'startFilePos' => $attribute->location->raw_file_start,
'endFilePos' => $attribute->location->raw_file_end,
'startLine' => $attribute->location->raw_line_number
]
);
$statements_analyzer = new StatementsAnalyzer(
$source,
new NodeDataProvider()
);
$statements_analyzer->analyze(
[new VirtualExpression($new_stmt)],
new Context()
);
$statements_analyzer->analyze(self::attributeGroupToStmts($attribute_group), new Context());
}
/**
@ -253,4 +187,16 @@ class AttributeAnalyzer
);
}
}
/**
* @return list<Stmt>
*/
private static function attributeGroupToStmts(AttributeGroup $attribute_group): array
{
$stmts = [];
foreach ($attribute_group->attrs as $attr) {
$stmts[] = new Expression(new New_($attr->name, $attr->args, $attr->getAttributes()));
}
return $stmts;
}
}

View File

@ -397,10 +397,11 @@ class ClassAnalyzer extends ClassLikeAnalyzer
}
}
foreach ($storage->attributes as $attribute) {
foreach ($storage->attributes as $i => $attribute) {
AttributeAnalyzer::analyze(
$this,
$attribute,
$class->attrGroups[$i],
$storage->suppressed_issues + $this->getSuppressedIssues(),
1,
$storage
@ -1522,10 +1523,11 @@ class ClassAnalyzer extends ClassLikeAnalyzer
$property_storage = $class_storage->properties[$property_name];
foreach ($property_storage->attributes as $attribute) {
foreach ($property_storage->attributes as $i => $attribute) {
AttributeAnalyzer::analyze(
$source,
$attribute,
$stmt->attrGroups[$i],
$this->source->getSuppressedIssues(),
8
);

View File

@ -5,6 +5,7 @@ namespace Psalm\Internal\Analyzer;
use PhpParser;
use PhpParser\Node\Expr\ArrowFunction;
use PhpParser\Node\Expr\Closure;
use PhpParser\Node\Param;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Function_;
use Psalm\CodeLocation;
@ -63,6 +64,7 @@ use function array_key_exists;
use function array_keys;
use function array_merge;
use function array_search;
use function array_values;
use function count;
use function end;
use function in_array;
@ -351,6 +353,7 @@ abstract class FunctionLikeAnalyzer extends SourceAnalyzer
$storage,
$cased_method_id,
$params,
array_values($this->function->params),
$context,
(bool) $template_types
);
@ -816,10 +819,11 @@ abstract class FunctionLikeAnalyzer extends SourceAnalyzer
);
}
foreach ($storage->attributes as $attribute) {
foreach ($storage->attributes as $i => $attribute) {
AttributeAnalyzer::analyze(
$this,
$attribute,
$this->function->attrGroups[$i],
$storage->suppressed_issues + $this->getSuppressedIssues(),
$storage instanceof MethodStorage ? 4 : 2
);
@ -968,13 +972,15 @@ abstract class FunctionLikeAnalyzer extends SourceAnalyzer
}
/**
* @param array<int, FunctionLikeParameter> $params
* @param list<FunctionLikeParameter> $params
* @param list<Param> $param_stmts
*/
private function processParams(
StatementsAnalyzer $statements_analyzer,
FunctionLikeStorage $storage,
?string $cased_method_id,
array $params,
array $param_stmts,
Context $context,
bool $has_template_types
): bool {
@ -1262,10 +1268,11 @@ abstract class FunctionLikeAnalyzer extends SourceAnalyzer
$context->hasVariable('$' . $function_param->name);
}
foreach ($function_param->attributes as $attribute) {
foreach ($function_param->attributes as $i => $attribute) {
AttributeAnalyzer::analyze(
$this,
$attribute,
$param_stmts[$offset]->attrGroups[$i],
$storage->suppressed_issues,
$function_param->promoted_property ? 8 : 32
);

View File

@ -96,10 +96,11 @@ class InterfaceAnalyzer extends ClassLikeAnalyzer
$class_storage = $codebase->classlike_storage_provider->get($fq_interface_name);
foreach ($class_storage->attributes as $attribute) {
foreach ($class_storage->attributes as $i => $attribute) {
AttributeAnalyzer::analyze(
$this,
$attribute,
$this->class->attrGroups[$i],
$class_storage->suppressed_issues + $this->getSuppressedIssues(),
1,
$class_storage

View File

@ -361,7 +361,7 @@ class Methods
/**
* @param list<PhpParser\Node\Arg> $args
*
* @return array<int, FunctionLikeParameter>
* @return list<FunctionLikeParameter>
*/
public function getMethodParams(
MethodIdentifier $method_id,

View File

@ -13,6 +13,7 @@ use Psalm\Plugin\Hook\MethodParamsProviderInterface as LegacyMethodParamsProvide
use Psalm\StatementsSource;
use Psalm\Storage\FunctionLikeParameter;
use function array_values;
use function is_subclass_of;
use function strtolower;
@ -101,7 +102,7 @@ class MethodParamsProvider
/**
* @param ?list<Arg> $call_args
*
* @return ?array<int, FunctionLikeParameter>
* @return ?list<FunctionLikeParameter>
*/
public function getMethodParams(
string $fq_classlike_name,
@ -122,7 +123,7 @@ class MethodParamsProvider
);
if ($result !== null) {
return $result;
return array_values($result);
}
}
@ -138,7 +139,7 @@ class MethodParamsProvider
$result = $class_handler($event);
if ($result !== null) {
return $result;
return array_values($result);
}
}

View File

@ -10,6 +10,7 @@ class AttributeArg
{
/**
* @var ?string
* @psalm-suppress PossiblyUnusedProperty It's part of the public API for now
*/
public $name;
@ -20,11 +21,12 @@ class AttributeArg
/**
* @var CodeLocation
* @psalm-suppress PossiblyUnusedProperty It's part of the public API for now
*/
public $location;
/**
* @param Union|UnresolvedConstantComponent $type
* @param Union|UnresolvedConstantComponent $type
*/
public function __construct(
?string $name,

View File

@ -5,6 +5,8 @@ namespace Psalm\Tests;
use Psalm\Tests\Traits\InvalidCodeAnalysisTestTrait;
use Psalm\Tests\Traits\ValidCodeAnalysisTestTrait;
use const DIRECTORY_SEPARATOR;
class AttributeTest extends TestCase
{
use InvalidCodeAnalysisTestTrait;
@ -240,7 +242,28 @@ class AttributeTest extends TestCase
[],
[],
'8.1'
]
],
'createObjectAsAttributeArg' => [
'<?php
#[Attribute]
class B
{
public function __construct(?array $listOfB = null) {}
}
#[Attribute(Attribute::TARGET_CLASS)]
class A
{
/**
* @param B[] $listOfB
*/
public function __construct(?array $listOfB = null) {}
}
#[A([new B])]
class C {}
',
],
];
}
@ -256,7 +279,7 @@ class AttributeTest extends TestCase
#[A]
class B {}',
'error_message' => 'InvalidAttribute',
'error_message' => 'InvalidAttribute - src' . DIRECTORY_SEPARATOR . 'somefile.php:4:23',
[],
false,
'8.0'
@ -267,7 +290,7 @@ class AttributeTest extends TestCase
#[Pure]
class Video {}',
'error_message' => 'UndefinedAttributeClass',
'error_message' => 'UndefinedAttributeClass - src' . DIRECTORY_SEPARATOR . 'somefile.php:4:23',
[],
false,
'8.0'
@ -278,7 +301,7 @@ class AttributeTest extends TestCase
#[Pure]
function foo() : void {}',
'error_message' => 'UndefinedAttributeClass',
'error_message' => 'UndefinedAttributeClass - src' . DIRECTORY_SEPARATOR . 'somefile.php:4:23',
[],
false,
'8.0'
@ -288,7 +311,7 @@ class AttributeTest extends TestCase
use Foo\Bar\Pure;
function foo(#[Pure] string $str) : void {}',
'error_message' => 'UndefinedAttributeClass',
'error_message' => 'UndefinedAttributeClass - src' . DIRECTORY_SEPARATOR . 'somefile.php:4:36',
[],
false,
'8.0'
@ -304,7 +327,7 @@ class AttributeTest extends TestCase
#[Table()]
class Video {}',
'error_message' => 'TooFewArguments',
'error_message' => 'TooFewArguments - src' . DIRECTORY_SEPARATOR . 'somefile.php:9:23',
[],
false,
'8.0'
@ -321,7 +344,7 @@ class AttributeTest extends TestCase
#[Foo("foo")]
class Bar{}',
'error_message' => 'InvalidScalarArgument',
'error_message' => 'InvalidScalarArgument - src' . DIRECTORY_SEPARATOR . 'somefile.php:10:27',
[],
false,
'8.0'
@ -337,7 +360,7 @@ class AttributeTest extends TestCase
#[Table("videos")]
function foo() : void {}',
'error_message' => 'InvalidAttribute',
'error_message' => 'InvalidAttribute - src' . DIRECTORY_SEPARATOR . 'somefile.php:9:23',
[],
false,
'8.0'
@ -346,7 +369,7 @@ class AttributeTest extends TestCase
'<?php
#[Attribute]
interface Foo {}',
'error_message' => 'InvalidAttribute',
'error_message' => 'InvalidAttribute - src' . DIRECTORY_SEPARATOR . 'somefile.php:2:23',
[],
false,
'8.0'
@ -355,7 +378,7 @@ class AttributeTest extends TestCase
'<?php
#[Attribute]
interface Foo {}',
'error_message' => 'InvalidAttribute',
'error_message' => 'InvalidAttribute - src' . DIRECTORY_SEPARATOR . 'somefile.php:2:23',
[],
false,
'8.0'
@ -364,7 +387,7 @@ class AttributeTest extends TestCase
'<?php
#[Attribute]
abstract class Baz {}',
'error_message' => 'InvalidAttribute',
'error_message' => 'InvalidAttribute - src' . DIRECTORY_SEPARATOR . 'somefile.php:2:23',
[],
false,
'8.0'
@ -375,7 +398,7 @@ class AttributeTest extends TestCase
class Baz {
private function __construct() {}
}',
'error_message' => 'InvalidAttribute',
'error_message' => 'InvalidAttribute - src' . DIRECTORY_SEPARATOR . 'somefile.php:2:23',
[],
false,
'8.0'