Skip to content

Commit a387fa3

Browse files
committed
AttributeReflection for easy attributes reading
1 parent 0b28f60 commit a387fa3

File tree

70 files changed

+939
-5
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

70 files changed

+939
-5
lines changed

conf/config.neon

+3
Original file line numberDiff line numberDiff line change
@@ -677,6 +677,9 @@ services:
677677
-
678678
class: PHPStan\Process\CpuCoreCounter
679679

680+
-
681+
class: PHPStan\Reflection\AttributeReflectionFactory
682+
680683
-
681684
implement: PHPStan\Reflection\FunctionReflectionFactory
682685
arguments:

src/Analyser/DirectInternalScopeFactory.php

+3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use PHPStan\Node\Printer\ExprPrinter;
88
use PHPStan\Parser\Parser;
99
use PHPStan\Php\PhpVersion;
10+
use PHPStan\Reflection\AttributeReflectionFactory;
1011
use PHPStan\Reflection\InitializerExprTypeResolver;
1112
use PHPStan\Reflection\ParametersAcceptor;
1213
use PHPStan\Reflection\Php\PhpFunctionFromParserNodeReflection;
@@ -31,6 +32,7 @@ public function __construct(
3132
private NodeScopeResolver $nodeScopeResolver,
3233
private RicherScopeGetTypeHelper $richerScopeGetTypeHelper,
3334
private PhpVersion $phpVersion,
35+
private AttributeReflectionFactory $attributeReflectionFactory,
3436
private int|array|null $configPhpVersion,
3537
private ConstantResolver $constantResolver,
3638
)
@@ -71,6 +73,7 @@ public function create(
7173
$this->constantResolver,
7274
$context,
7375
$this->phpVersion,
76+
$this->attributeReflectionFactory,
7477
$this->configPhpVersion,
7578
$declareStrictTypes,
7679
$function,

src/Analyser/LazyInternalScopeFactory.php

+2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use PHPStan\DependencyInjection\Type\ExpressionTypeResolverExtensionRegistryProvider;
88
use PHPStan\Node\Printer\ExprPrinter;
99
use PHPStan\Php\PhpVersion;
10+
use PHPStan\Reflection\AttributeReflectionFactory;
1011
use PHPStan\Reflection\InitializerExprTypeResolver;
1112
use PHPStan\Reflection\ParametersAcceptor;
1213
use PHPStan\Reflection\Php\PhpFunctionFromParserNodeReflection;
@@ -56,6 +57,7 @@ public function create(
5657
$this->container->getByType(ConstantResolver::class),
5758
$context,
5859
$this->container->getByType(PhpVersion::class),
60+
$this->container->getByType(AttributeReflectionFactory::class),
5961
$this->container->getParameter('phpVersion'),
6062
$declareStrictTypes,
6163
$function,

src/Analyser/MutatingScope.php

+33
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,10 @@
2626
use PhpParser\Node\InterpolatedStringPart;
2727
use PhpParser\Node\Name;
2828
use PhpParser\Node\Name\FullyQualified;
29+
use PhpParser\Node\PropertyHook;
2930
use PhpParser\Node\Scalar\String_;
31+
use PhpParser\Node\Stmt\ClassMethod;
32+
use PhpParser\Node\Stmt\Function_;
3033
use PhpParser\NodeFinder;
3134
use PHPStan\Node\ExecutionEndNode;
3235
use PHPStan\Node\Expr\AlwaysRememberedExpr;
@@ -52,6 +55,8 @@
5255
use PHPStan\Php\PhpVersions;
5356
use PHPStan\PhpDoc\Tag\TemplateTag;
5457
use PHPStan\Reflection\Assertions;
58+
use PHPStan\Reflection\AttributeReflection;
59+
use PHPStan\Reflection\AttributeReflectionFactory;
5560
use PHPStan\Reflection\Callables\CallableParametersAcceptor;
5661
use PHPStan\Reflection\Callables\SimpleImpurePoint;
5762
use PHPStan\Reflection\Callables\SimpleThrowPoint;
@@ -212,6 +217,7 @@ public function __construct(
212217
private ConstantResolver $constantResolver,
213218
private ScopeContext $context,
214219
private PhpVersion $phpVersion,
220+
private AttributeReflectionFactory $attributeReflectionFactory,
215221
private int|array|null $configPhpVersion,
216222
private bool $declareStrictTypes = false,
217223
private PhpFunctionFromParserNodeReflection|null $function = null,
@@ -2974,6 +2980,7 @@ public function enterClassMethod(
29742980
$this->getRealParameterTypes($classMethod),
29752981
array_map(fn (Type $type): Type => $this->transformStaticType(TemplateTypeHelper::toArgument($type)), $phpDocParameterTypes),
29762982
$this->getRealParameterDefaultValues($classMethod),
2983+
$this->getParameterAttributes($classMethod),
29772984
$this->transformStaticType($this->getFunctionType($classMethod->returnType, false, false)),
29782985
$phpDocReturnType !== null ? $this->transformStaticType(TemplateTypeHelper::toArgument($phpDocReturnType)) : null,
29792986
$throwType,
@@ -2990,6 +2997,7 @@ public function enterClassMethod(
29902997
$immediatelyInvokedCallableParameters,
29912998
array_map(fn (Type $type): Type => $this->transformStaticType(TemplateTypeHelper::toArgument($type)), $phpDocClosureThisTypeParameters),
29922999
$isConstructor,
3000+
$this->attributeReflectionFactory->fromAttrGroups($classMethod->attrGroups, InitializerExprContext::fromStubParameter($this->getClassReflection()->getName(), $this->getFile(), $classMethod)),
29933001
),
29943002
!$classMethod->isStatic(),
29953003
);
@@ -3059,6 +3067,7 @@ public function enterPropertyHook(
30593067
$realParameterTypes,
30603068
$phpDocParameterTypes,
30613069
[],
3070+
$this->getParameterAttributes($hook),
30623071
$realReturnType,
30633072
$phpDocReturnType,
30643073
$throwType,
@@ -3075,6 +3084,7 @@ public function enterPropertyHook(
30753084
[],
30763085
[],
30773086
false,
3087+
$this->attributeReflectionFactory->fromAttrGroups($hook->attrGroups, InitializerExprContext::fromStubParameter($this->getClassReflection()->getName(), $this->getFile(), $hook)),
30783088
),
30793089
true,
30803090
);
@@ -3138,6 +3148,27 @@ private function getRealParameterDefaultValues(Node\FunctionLike $functionLike):
31383148
return $realParameterDefaultValues;
31393149
}
31403150

3151+
/**
3152+
* @return array<string, list<AttributeReflection>>
3153+
*/
3154+
private function getParameterAttributes(ClassMethod|Function_|PropertyHook $functionLike): array
3155+
{
3156+
$parameterAttributes = [];
3157+
$className = null;
3158+
if ($this->isInClass()) {
3159+
$className = $this->getClassReflection()->getName();
3160+
}
3161+
foreach ($functionLike->getParams() as $parameter) {
3162+
if (!$parameter->var instanceof Variable || !is_string($parameter->var->name)) {
3163+
throw new ShouldNotHappenException();
3164+
}
3165+
3166+
$parameterAttributes[$parameter->var->name] = $this->attributeReflectionFactory->fromAttrGroups($parameter->attrGroups, InitializerExprContext::fromStubParameter($className, $this->getFile(), $functionLike));
3167+
}
3168+
3169+
return $parameterAttributes;
3170+
}
3171+
31413172
/**
31423173
* @api
31433174
* @param Type[] $phpDocParameterTypes
@@ -3171,6 +3202,7 @@ public function enterFunction(
31713202
$this->getRealParameterTypes($function),
31723203
array_map(static fn (Type $type): Type => TemplateTypeHelper::toArgument($type), $phpDocParameterTypes),
31733204
$this->getRealParameterDefaultValues($function),
3205+
$this->getParameterAttributes($function),
31743206
$this->getFunctionType($function->returnType, $function->returnType === null, false),
31753207
$phpDocReturnType !== null ? TemplateTypeHelper::toArgument($phpDocReturnType) : null,
31763208
$throwType,
@@ -3184,6 +3216,7 @@ public function enterFunction(
31843216
array_map(static fn (Type $type): Type => TemplateTypeHelper::toArgument($type), $parameterOutTypes),
31853217
$immediatelyInvokedCallableParameters,
31863218
$phpDocClosureThisTypeParameters,
3219+
$this->attributeReflectionFactory->fromAttrGroups($function->attrGroups, InitializerExprContext::fromStubParameter(null, $this->getFile(), $function)),
31873220
),
31883221
false,
31893222
);

src/Analyser/NodeScopeResolver.php

+3
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@
132132
use PHPStan\PhpDoc\StubPhpDocProvider;
133133
use PHPStan\PhpDoc\Tag\VarTag;
134134
use PHPStan\Reflection\Assertions;
135+
use PHPStan\Reflection\AttributeReflectionFactory;
135136
use PHPStan\Reflection\Callables\CallableParametersAcceptor;
136137
use PHPStan\Reflection\Callables\SimpleImpurePoint;
137138
use PHPStan\Reflection\Callables\SimpleThrowPoint;
@@ -254,6 +255,7 @@ public function __construct(
254255
private readonly StubPhpDocProvider $stubPhpDocProvider,
255256
private readonly PhpVersion $phpVersion,
256257
private readonly SignatureMapProvider $signatureMapProvider,
258+
private readonly AttributeReflectionFactory $attributeReflectionFactory,
257259
private readonly PhpDocInheritanceResolver $phpDocInheritanceResolver,
258260
private readonly FileHelper $fileHelper,
259261
private readonly TypeSpecifier $typeSpecifier,
@@ -2134,6 +2136,7 @@ private function createAstClassReflection(Node\Stmt\ClassLike $stmt, string $cla
21342136
$this->phpDocInheritanceResolver,
21352137
$this->phpVersion,
21362138
$this->signatureMapProvider,
2139+
$this->attributeReflectionFactory,
21372140
$this->classReflectionExtensionRegistryProvider->getRegistry()->getPropertiesClassReflectionExtensions(),
21382141
$this->classReflectionExtensionRegistryProvider->getRegistry()->getMethodsClassReflectionExtensions(),
21392142
$this->classReflectionExtensionRegistryProvider->getRegistry()->getAllowedSubTypesClassReflectionExtensions(),

src/Reflection/Annotations/AnnotationMethodReflection.php

+5
Original file line numberDiff line numberDiff line change
@@ -176,4 +176,9 @@ public function isPure(): TrinaryLogic
176176
return TrinaryLogic::createMaybe();
177177
}
178178

179+
public function getAttributes(): array
180+
{
181+
return [];
182+
}
183+
179184
}

src/Reflection/Annotations/AnnotationPropertyReflection.php

+5
Original file line numberDiff line numberDiff line change
@@ -143,4 +143,9 @@ public function isPrivateSet(): bool
143143
return false;
144144
}
145145

146+
public function getAttributes(): array
147+
{
148+
return [];
149+
}
150+
146151
}

src/Reflection/Annotations/AnnotationsMethodParameterReflection.php

+5
Original file line numberDiff line numberDiff line change
@@ -75,4 +75,9 @@ public function getDefaultValue(): ?Type
7575
return $this->defaultValue;
7676
}
7777

78+
public function getAttributes(): array
79+
{
80+
return [];
81+
}
82+
7883
}
+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Reflection;
4+
5+
use PHPStan\Type\Type;
6+
7+
/**
8+
* @api
9+
*/
10+
final class AttributeReflection
11+
{
12+
13+
/**
14+
* @param array<string, Type> $argumentTypes
15+
*/
16+
public function __construct(private string $name, private array $argumentTypes)
17+
{
18+
}
19+
20+
public function getName(): string
21+
{
22+
return $this->name;
23+
}
24+
25+
/**
26+
* @return array<string, Type>
27+
*/
28+
public function getArgumentTypes(): array
29+
{
30+
return $this->argumentTypes;
31+
}
32+
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Reflection;
4+
5+
use PhpParser\Node\AttributeGroup;
6+
use PhpParser\Node\Expr;
7+
use PHPStan\BetterReflection\Reflection\Adapter\FakeReflectionAttribute;
8+
use PHPStan\BetterReflection\Reflection\Adapter\ReflectionAttribute;
9+
use PHPStan\Reflection\ReflectionProvider\ReflectionProviderProvider;
10+
use PHPStan\Type\TypeCombinator;
11+
use function array_key_exists;
12+
use function count;
13+
use function is_int;
14+
15+
final class AttributeReflectionFactory
16+
{
17+
18+
public function __construct(
19+
private InitializerExprTypeResolver $initializerExprTypeResolver,
20+
private ReflectionProviderProvider $reflectionProviderProvider,
21+
)
22+
{
23+
}
24+
25+
/**
26+
* @param list<ReflectionAttribute|FakeReflectionAttribute> $reflections
27+
* @return list<AttributeReflection>
28+
*/
29+
public function fromNativeReflection(array $reflections, InitializerExprContext $context): array
30+
{
31+
$attributes = [];
32+
foreach ($reflections as $reflection) {
33+
$attribute = $this->fromNameAndArgumentExpressions($reflection->getName(), $reflection->getArgumentsExpressions(), $context);
34+
if ($attribute === null) {
35+
continue;
36+
}
37+
38+
$attributes[] = $attribute;
39+
}
40+
41+
return $attributes;
42+
}
43+
44+
/**
45+
* @param AttributeGroup[] $attrGroups
46+
* @return list<AttributeReflection>
47+
*/
48+
public function fromAttrGroups(array $attrGroups, InitializerExprContext $context): array
49+
{
50+
$attributes = [];
51+
foreach ($attrGroups as $attrGroup) {
52+
foreach ($attrGroup->attrs as $attr) {
53+
$arguments = [];
54+
foreach ($attr->args as $i => $arg) {
55+
if ($arg->name === null) {
56+
$argName = $i;
57+
} else {
58+
$argName = $arg->name->toString();
59+
}
60+
61+
$arguments[$argName] = $arg->value;
62+
}
63+
$attributeReflection = $this->fromNameAndArgumentExpressions($attr->name->toString(), $arguments, $context);
64+
if ($attributeReflection === null) {
65+
continue;
66+
}
67+
68+
$attributes[] = $attributeReflection;
69+
}
70+
}
71+
72+
return $attributes;
73+
}
74+
75+
/**
76+
* @param array<int|string, Expr> $arguments
77+
*/
78+
private function fromNameAndArgumentExpressions(string $name, array $arguments, InitializerExprContext $context): ?AttributeReflection
79+
{
80+
if (count($arguments) === 0) {
81+
return new AttributeReflection($name, []);
82+
}
83+
84+
$reflectionProvider = $this->reflectionProviderProvider->getReflectionProvider();
85+
if (!$reflectionProvider->hasClass($name)) {
86+
return null;
87+
}
88+
89+
$classReflection = $reflectionProvider->getClass($name);
90+
if (!$classReflection->hasConstructor()) {
91+
return null;
92+
}
93+
94+
if (!$classReflection->isAttributeClass()) {
95+
return null;
96+
}
97+
98+
$constructor = $classReflection->getConstructor();
99+
$parameters = $constructor->getOnlyVariant()->getParameters();
100+
$namedArgTypes = [];
101+
foreach ($arguments as $i => $argExpr) {
102+
if (is_int($i)) {
103+
if (isset($parameters[$i])) {
104+
$namedArgTypes[$parameters[$i]->getName()] = $this->initializerExprTypeResolver->getType($argExpr, $context);
105+
continue;
106+
}
107+
if (count($parameters) > 0) {
108+
$lastParameter = $parameters[count($parameters) - 1];
109+
if ($lastParameter->isVariadic()) {
110+
$parameterName = $lastParameter->getName();
111+
if (array_key_exists($parameterName, $namedArgTypes)) {
112+
$namedArgTypes[$parameterName] = TypeCombinator::union($namedArgTypes[$parameterName], $this->initializerExprTypeResolver->getType($argExpr, $context));
113+
continue;
114+
}
115+
$namedArgTypes[$parameterName] = $this->initializerExprTypeResolver->getType($argExpr, $context);
116+
}
117+
}
118+
continue;
119+
}
120+
121+
foreach ($parameters as $parameter) {
122+
if ($parameter->getName() !== $i) {
123+
continue;
124+
}
125+
126+
$namedArgTypes[$i] = $this->initializerExprTypeResolver->getType($argExpr, $context);
127+
break;
128+
}
129+
}
130+
131+
return new AttributeReflection($classReflection->getName(), $namedArgTypes);
132+
}
133+
134+
}

0 commit comments

Comments
 (0)