Skip to content

Commit

Permalink
AttributeReflection for easy attributes reading
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Feb 2, 2025
1 parent 0b28f60 commit a387fa3
Show file tree
Hide file tree
Showing 70 changed files with 939 additions and 5 deletions.
3 changes: 3 additions & 0 deletions conf/config.neon
Original file line number Diff line number Diff line change
Expand Up @@ -677,6 +677,9 @@ services:
-
class: PHPStan\Process\CpuCoreCounter

-
class: PHPStan\Reflection\AttributeReflectionFactory

-
implement: PHPStan\Reflection\FunctionReflectionFactory
arguments:
Expand Down
3 changes: 3 additions & 0 deletions src/Analyser/DirectInternalScopeFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use PHPStan\Node\Printer\ExprPrinter;
use PHPStan\Parser\Parser;
use PHPStan\Php\PhpVersion;
use PHPStan\Reflection\AttributeReflectionFactory;
use PHPStan\Reflection\InitializerExprTypeResolver;
use PHPStan\Reflection\ParametersAcceptor;
use PHPStan\Reflection\Php\PhpFunctionFromParserNodeReflection;
Expand All @@ -31,6 +32,7 @@ public function __construct(
private NodeScopeResolver $nodeScopeResolver,
private RicherScopeGetTypeHelper $richerScopeGetTypeHelper,
private PhpVersion $phpVersion,
private AttributeReflectionFactory $attributeReflectionFactory,
private int|array|null $configPhpVersion,
private ConstantResolver $constantResolver,
)
Expand Down Expand Up @@ -71,6 +73,7 @@ public function create(
$this->constantResolver,
$context,
$this->phpVersion,
$this->attributeReflectionFactory,
$this->configPhpVersion,
$declareStrictTypes,
$function,
Expand Down
2 changes: 2 additions & 0 deletions src/Analyser/LazyInternalScopeFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use PHPStan\DependencyInjection\Type\ExpressionTypeResolverExtensionRegistryProvider;
use PHPStan\Node\Printer\ExprPrinter;
use PHPStan\Php\PhpVersion;
use PHPStan\Reflection\AttributeReflectionFactory;
use PHPStan\Reflection\InitializerExprTypeResolver;
use PHPStan\Reflection\ParametersAcceptor;
use PHPStan\Reflection\Php\PhpFunctionFromParserNodeReflection;
Expand Down Expand Up @@ -56,6 +57,7 @@ public function create(
$this->container->getByType(ConstantResolver::class),
$context,
$this->container->getByType(PhpVersion::class),
$this->container->getByType(AttributeReflectionFactory::class),
$this->container->getParameter('phpVersion'),
$declareStrictTypes,
$function,
Expand Down
33 changes: 33 additions & 0 deletions src/Analyser/MutatingScope.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@
use PhpParser\Node\InterpolatedStringPart;
use PhpParser\Node\Name;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\PropertyHook;
use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Function_;
use PhpParser\NodeFinder;
use PHPStan\Node\ExecutionEndNode;
use PHPStan\Node\Expr\AlwaysRememberedExpr;
Expand All @@ -52,6 +55,8 @@
use PHPStan\Php\PhpVersions;
use PHPStan\PhpDoc\Tag\TemplateTag;
use PHPStan\Reflection\Assertions;
use PHPStan\Reflection\AttributeReflection;
use PHPStan\Reflection\AttributeReflectionFactory;
use PHPStan\Reflection\Callables\CallableParametersAcceptor;
use PHPStan\Reflection\Callables\SimpleImpurePoint;
use PHPStan\Reflection\Callables\SimpleThrowPoint;
Expand Down Expand Up @@ -212,6 +217,7 @@ public function __construct(
private ConstantResolver $constantResolver,
private ScopeContext $context,
private PhpVersion $phpVersion,
private AttributeReflectionFactory $attributeReflectionFactory,
private int|array|null $configPhpVersion,
private bool $declareStrictTypes = false,
private PhpFunctionFromParserNodeReflection|null $function = null,
Expand Down Expand Up @@ -2974,6 +2980,7 @@ public function enterClassMethod(
$this->getRealParameterTypes($classMethod),
array_map(fn (Type $type): Type => $this->transformStaticType(TemplateTypeHelper::toArgument($type)), $phpDocParameterTypes),
$this->getRealParameterDefaultValues($classMethod),
$this->getParameterAttributes($classMethod),
$this->transformStaticType($this->getFunctionType($classMethod->returnType, false, false)),
$phpDocReturnType !== null ? $this->transformStaticType(TemplateTypeHelper::toArgument($phpDocReturnType)) : null,
$throwType,
Expand All @@ -2990,6 +2997,7 @@ public function enterClassMethod(
$immediatelyInvokedCallableParameters,
array_map(fn (Type $type): Type => $this->transformStaticType(TemplateTypeHelper::toArgument($type)), $phpDocClosureThisTypeParameters),
$isConstructor,
$this->attributeReflectionFactory->fromAttrGroups($classMethod->attrGroups, InitializerExprContext::fromStubParameter($this->getClassReflection()->getName(), $this->getFile(), $classMethod)),
),
!$classMethod->isStatic(),
);
Expand Down Expand Up @@ -3059,6 +3067,7 @@ public function enterPropertyHook(
$realParameterTypes,
$phpDocParameterTypes,
[],
$this->getParameterAttributes($hook),
$realReturnType,
$phpDocReturnType,
$throwType,
Expand All @@ -3075,6 +3084,7 @@ public function enterPropertyHook(
[],
[],
false,
$this->attributeReflectionFactory->fromAttrGroups($hook->attrGroups, InitializerExprContext::fromStubParameter($this->getClassReflection()->getName(), $this->getFile(), $hook)),
),
true,
);
Expand Down Expand Up @@ -3138,6 +3148,27 @@ private function getRealParameterDefaultValues(Node\FunctionLike $functionLike):
return $realParameterDefaultValues;
}

/**
* @return array<string, list<AttributeReflection>>
*/
private function getParameterAttributes(ClassMethod|Function_|PropertyHook $functionLike): array
{
$parameterAttributes = [];
$className = null;
if ($this->isInClass()) {
$className = $this->getClassReflection()->getName();
}
foreach ($functionLike->getParams() as $parameter) {
if (!$parameter->var instanceof Variable || !is_string($parameter->var->name)) {
throw new ShouldNotHappenException();
}

$parameterAttributes[$parameter->var->name] = $this->attributeReflectionFactory->fromAttrGroups($parameter->attrGroups, InitializerExprContext::fromStubParameter($className, $this->getFile(), $functionLike));
}

return $parameterAttributes;
}

/**
* @api
* @param Type[] $phpDocParameterTypes
Expand Down Expand Up @@ -3171,6 +3202,7 @@ public function enterFunction(
$this->getRealParameterTypes($function),
array_map(static fn (Type $type): Type => TemplateTypeHelper::toArgument($type), $phpDocParameterTypes),
$this->getRealParameterDefaultValues($function),
$this->getParameterAttributes($function),
$this->getFunctionType($function->returnType, $function->returnType === null, false),
$phpDocReturnType !== null ? TemplateTypeHelper::toArgument($phpDocReturnType) : null,
$throwType,
Expand All @@ -3184,6 +3216,7 @@ public function enterFunction(
array_map(static fn (Type $type): Type => TemplateTypeHelper::toArgument($type), $parameterOutTypes),
$immediatelyInvokedCallableParameters,
$phpDocClosureThisTypeParameters,
$this->attributeReflectionFactory->fromAttrGroups($function->attrGroups, InitializerExprContext::fromStubParameter(null, $this->getFile(), $function)),
),
false,
);
Expand Down
3 changes: 3 additions & 0 deletions src/Analyser/NodeScopeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@
use PHPStan\PhpDoc\StubPhpDocProvider;
use PHPStan\PhpDoc\Tag\VarTag;
use PHPStan\Reflection\Assertions;
use PHPStan\Reflection\AttributeReflectionFactory;
use PHPStan\Reflection\Callables\CallableParametersAcceptor;
use PHPStan\Reflection\Callables\SimpleImpurePoint;
use PHPStan\Reflection\Callables\SimpleThrowPoint;
Expand Down Expand Up @@ -254,6 +255,7 @@ public function __construct(
private readonly StubPhpDocProvider $stubPhpDocProvider,
private readonly PhpVersion $phpVersion,
private readonly SignatureMapProvider $signatureMapProvider,
private readonly AttributeReflectionFactory $attributeReflectionFactory,
private readonly PhpDocInheritanceResolver $phpDocInheritanceResolver,
private readonly FileHelper $fileHelper,
private readonly TypeSpecifier $typeSpecifier,
Expand Down Expand Up @@ -2134,6 +2136,7 @@ private function createAstClassReflection(Node\Stmt\ClassLike $stmt, string $cla
$this->phpDocInheritanceResolver,
$this->phpVersion,
$this->signatureMapProvider,
$this->attributeReflectionFactory,
$this->classReflectionExtensionRegistryProvider->getRegistry()->getPropertiesClassReflectionExtensions(),
$this->classReflectionExtensionRegistryProvider->getRegistry()->getMethodsClassReflectionExtensions(),
$this->classReflectionExtensionRegistryProvider->getRegistry()->getAllowedSubTypesClassReflectionExtensions(),
Expand Down
5 changes: 5 additions & 0 deletions src/Reflection/Annotations/AnnotationMethodReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -176,4 +176,9 @@ public function isPure(): TrinaryLogic
return TrinaryLogic::createMaybe();
}

public function getAttributes(): array
{
return [];
}

}
5 changes: 5 additions & 0 deletions src/Reflection/Annotations/AnnotationPropertyReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -143,4 +143,9 @@ public function isPrivateSet(): bool
return false;
}

public function getAttributes(): array
{
return [];
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,9 @@ public function getDefaultValue(): ?Type
return $this->defaultValue;
}

public function getAttributes(): array
{
return [];
}

}
33 changes: 33 additions & 0 deletions src/Reflection/AttributeReflection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php declare(strict_types = 1);

namespace PHPStan\Reflection;

use PHPStan\Type\Type;

/**
* @api
*/
final class AttributeReflection
{

/**
* @param array<string, Type> $argumentTypes
*/
public function __construct(private string $name, private array $argumentTypes)
{
}

public function getName(): string
{
return $this->name;
}

/**
* @return array<string, Type>
*/
public function getArgumentTypes(): array
{
return $this->argumentTypes;
}

}
134 changes: 134 additions & 0 deletions src/Reflection/AttributeReflectionFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
<?php declare(strict_types = 1);

namespace PHPStan\Reflection;

use PhpParser\Node\AttributeGroup;
use PhpParser\Node\Expr;
use PHPStan\BetterReflection\Reflection\Adapter\FakeReflectionAttribute;
use PHPStan\BetterReflection\Reflection\Adapter\ReflectionAttribute;
use PHPStan\Reflection\ReflectionProvider\ReflectionProviderProvider;
use PHPStan\Type\TypeCombinator;
use function array_key_exists;
use function count;
use function is_int;

final class AttributeReflectionFactory
{

public function __construct(
private InitializerExprTypeResolver $initializerExprTypeResolver,
private ReflectionProviderProvider $reflectionProviderProvider,
)
{
}

/**
* @param list<ReflectionAttribute|FakeReflectionAttribute> $reflections
* @return list<AttributeReflection>
*/
public function fromNativeReflection(array $reflections, InitializerExprContext $context): array
{
$attributes = [];
foreach ($reflections as $reflection) {
$attribute = $this->fromNameAndArgumentExpressions($reflection->getName(), $reflection->getArgumentsExpressions(), $context);
if ($attribute === null) {
continue;
}

$attributes[] = $attribute;
}

return $attributes;
}

/**
* @param AttributeGroup[] $attrGroups
* @return list<AttributeReflection>
*/
public function fromAttrGroups(array $attrGroups, InitializerExprContext $context): array
{
$attributes = [];
foreach ($attrGroups as $attrGroup) {
foreach ($attrGroup->attrs as $attr) {
$arguments = [];
foreach ($attr->args as $i => $arg) {
if ($arg->name === null) {
$argName = $i;
} else {
$argName = $arg->name->toString();
}

$arguments[$argName] = $arg->value;
}
$attributeReflection = $this->fromNameAndArgumentExpressions($attr->name->toString(), $arguments, $context);
if ($attributeReflection === null) {
continue;
}

$attributes[] = $attributeReflection;
}
}

return $attributes;
}

/**
* @param array<int|string, Expr> $arguments
*/
private function fromNameAndArgumentExpressions(string $name, array $arguments, InitializerExprContext $context): ?AttributeReflection
{
if (count($arguments) === 0) {
return new AttributeReflection($name, []);
}

$reflectionProvider = $this->reflectionProviderProvider->getReflectionProvider();
if (!$reflectionProvider->hasClass($name)) {
return null;
}

$classReflection = $reflectionProvider->getClass($name);
if (!$classReflection->hasConstructor()) {
return null;
}

if (!$classReflection->isAttributeClass()) {
return null;
}

$constructor = $classReflection->getConstructor();
$parameters = $constructor->getOnlyVariant()->getParameters();
$namedArgTypes = [];
foreach ($arguments as $i => $argExpr) {
if (is_int($i)) {
if (isset($parameters[$i])) {
$namedArgTypes[$parameters[$i]->getName()] = $this->initializerExprTypeResolver->getType($argExpr, $context);
continue;
}
if (count($parameters) > 0) {
$lastParameter = $parameters[count($parameters) - 1];
if ($lastParameter->isVariadic()) {
$parameterName = $lastParameter->getName();
if (array_key_exists($parameterName, $namedArgTypes)) {
$namedArgTypes[$parameterName] = TypeCombinator::union($namedArgTypes[$parameterName], $this->initializerExprTypeResolver->getType($argExpr, $context));
continue;
}
$namedArgTypes[$parameterName] = $this->initializerExprTypeResolver->getType($argExpr, $context);
}
}
continue;
}

foreach ($parameters as $parameter) {
if ($parameter->getName() !== $i) {
continue;
}

$namedArgTypes[$i] = $this->initializerExprTypeResolver->getType($argExpr, $context);
break;
}
}

return new AttributeReflection($classReflection->getName(), $namedArgTypes);
}

}
Loading

0 comments on commit a387fa3

Please sign in to comment.