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:
commit
ad91df5ee1
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
);
|
||||
|
@ -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
|
||||
);
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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'
|
||||
|
Loading…
Reference in New Issue
Block a user