From a387fa32788fd38bc35313558f6e6a46fc2c451c Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 1 Feb 2025 20:14:50 +0100 Subject: [PATCH] AttributeReflection for easy attributes reading --- conf/config.neon | 3 + src/Analyser/DirectInternalScopeFactory.php | 3 + src/Analyser/LazyInternalScopeFactory.php | 2 + src/Analyser/MutatingScope.php | 33 ++++ src/Analyser/NodeScopeResolver.php | 3 + .../AnnotationMethodReflection.php | 5 + .../AnnotationPropertyReflection.php | 5 + .../AnnotationsMethodParameterReflection.php | 5 + src/Reflection/AttributeReflection.php | 33 ++++ src/Reflection/AttributeReflectionFactory.php | 134 +++++++++++++ .../BetterReflectionProvider.php | 5 + src/Reflection/ClassConstantReflection.php | 5 + src/Reflection/ClassReflection.php | 16 +- .../Dummy/ChangedTypeMethodReflection.php | 5 + .../Dummy/ChangedTypePropertyReflection.php | 5 + .../Dummy/DummyClassConstantReflection.php | 5 + .../Dummy/DummyConstructorReflection.php | 5 + .../Dummy/DummyMethodReflection.php | 5 + .../Dummy/DummyPropertyReflection.php | 5 + src/Reflection/EnumCaseReflection.php | 18 +- src/Reflection/ExtendedMethodReflection.php | 5 + .../ExtendedParameterReflection.php | 5 + src/Reflection/ExtendedPropertyReflection.php | 5 + src/Reflection/FunctionReflection.php | 5 + src/Reflection/FunctionReflectionFactory.php | 2 + .../GenericParametersAcceptorResolver.php | 1 + src/Reflection/InitializerExprContext.php | 26 +++ .../ExtendedNativeParameterReflection.php | 10 + .../Native/NativeFunctionReflection.php | 8 + .../Native/NativeMethodReflection.php | 8 + src/Reflection/ParametersAcceptorSelector.php | 5 + .../Php/ClosureCallMethodReflection.php | 6 + .../Php/EnumCasesMethodReflection.php | 5 + src/Reflection/Php/EnumPropertyReflection.php | 5 + src/Reflection/Php/ExitFunctionReflection.php | 6 + src/Reflection/Php/ExtendedDummyParameter.php | 10 + .../Php/PhpClassReflectionExtension.php | 9 +- .../PhpFunctionFromParserNodeReflection.php | 11 ++ src/Reflection/Php/PhpFunctionReflection.php | 12 ++ .../Php/PhpMethodFromParserNodeReflection.php | 7 + src/Reflection/Php/PhpMethodReflection.php | 16 ++ .../Php/PhpMethodReflectionFactory.php | 3 + .../PhpParameterFromParserNodeReflection.php | 10 + src/Reflection/Php/PhpParameterReflection.php | 10 + src/Reflection/Php/PhpPropertyReflection.php | 10 + .../Php/SimpleXMLElementProperty.php | 5 + .../Php/UniversalObjectCrateProperty.php | 5 + .../RealClassClassConstantReflection.php | 9 + .../ResolvedFunctionVariantWithOriginal.php | 1 + src/Reflection/ResolvedMethodReflection.php | 5 + src/Reflection/ResolvedPropertyReflection.php | 5 + .../NativeFunctionReflectionProvider.php | 15 +- ...ackUnresolvedMethodPrototypeReflection.php | 1 + ...ypeUnresolvedMethodPrototypeReflection.php | 1 + .../Type/IntersectionTypeMethodReflection.php | 5 + .../IntersectionTypePropertyReflection.php | 5 + .../Type/UnionTypeMethodReflection.php | 5 + .../Type/UnionTypePropertyReflection.php | 5 + .../WrappedExtendedMethodReflection.php | 6 + .../WrappedExtendedPropertyReflection.php | 5 + .../Properties/FoundPropertyReflection.php | 5 + src/Testing/PHPStanTestCase.php | 2 + src/Testing/RuleTestCase.php | 2 + src/Testing/TypeInferenceTestCase.php | 2 + src/Type/ObjectShapePropertyReflection.php | 5 + tests/PHPStan/Analyser/AnalyserTest.php | 2 + .../AttributeReflectionFromNodeRuleTest.php | 96 ++++++++++ .../Reflection/AttributeReflectionTest.php | 179 ++++++++++++++++++ .../data/attribute-reflection-enum.php | 11 ++ .../Reflection/data/attribute-reflection.php | 62 ++++++ 70 files changed, 939 insertions(+), 5 deletions(-) create mode 100644 src/Reflection/AttributeReflection.php create mode 100644 src/Reflection/AttributeReflectionFactory.php create mode 100644 tests/PHPStan/Reflection/AttributeReflectionFromNodeRuleTest.php create mode 100644 tests/PHPStan/Reflection/AttributeReflectionTest.php create mode 100644 tests/PHPStan/Reflection/data/attribute-reflection-enum.php create mode 100644 tests/PHPStan/Reflection/data/attribute-reflection.php diff --git a/conf/config.neon b/conf/config.neon index bd13fe0612..b9f6445b08 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -677,6 +677,9 @@ services: - class: PHPStan\Process\CpuCoreCounter + - + class: PHPStan\Reflection\AttributeReflectionFactory + - implement: PHPStan\Reflection\FunctionReflectionFactory arguments: diff --git a/src/Analyser/DirectInternalScopeFactory.php b/src/Analyser/DirectInternalScopeFactory.php index 22f709cbc9..f65a599113 100644 --- a/src/Analyser/DirectInternalScopeFactory.php +++ b/src/Analyser/DirectInternalScopeFactory.php @@ -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; @@ -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, ) @@ -71,6 +73,7 @@ public function create( $this->constantResolver, $context, $this->phpVersion, + $this->attributeReflectionFactory, $this->configPhpVersion, $declareStrictTypes, $function, diff --git a/src/Analyser/LazyInternalScopeFactory.php b/src/Analyser/LazyInternalScopeFactory.php index 657cb8c865..1d4154261d 100644 --- a/src/Analyser/LazyInternalScopeFactory.php +++ b/src/Analyser/LazyInternalScopeFactory.php @@ -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; @@ -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, diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 27b2f00f45..073a08368c 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -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; @@ -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; @@ -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, @@ -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, @@ -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(), ); @@ -3059,6 +3067,7 @@ public function enterPropertyHook( $realParameterTypes, $phpDocParameterTypes, [], + $this->getParameterAttributes($hook), $realReturnType, $phpDocReturnType, $throwType, @@ -3075,6 +3084,7 @@ public function enterPropertyHook( [], [], false, + $this->attributeReflectionFactory->fromAttrGroups($hook->attrGroups, InitializerExprContext::fromStubParameter($this->getClassReflection()->getName(), $this->getFile(), $hook)), ), true, ); @@ -3138,6 +3148,27 @@ private function getRealParameterDefaultValues(Node\FunctionLike $functionLike): return $realParameterDefaultValues; } + /** + * @return array> + */ + 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 @@ -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, @@ -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, ); diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index b6c5798b8d..d5fde30813 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -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; @@ -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, @@ -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(), diff --git a/src/Reflection/Annotations/AnnotationMethodReflection.php b/src/Reflection/Annotations/AnnotationMethodReflection.php index 865abdbe04..b7aab264ff 100644 --- a/src/Reflection/Annotations/AnnotationMethodReflection.php +++ b/src/Reflection/Annotations/AnnotationMethodReflection.php @@ -176,4 +176,9 @@ public function isPure(): TrinaryLogic return TrinaryLogic::createMaybe(); } + public function getAttributes(): array + { + return []; + } + } diff --git a/src/Reflection/Annotations/AnnotationPropertyReflection.php b/src/Reflection/Annotations/AnnotationPropertyReflection.php index cc1994bc9a..ea0d7e2418 100644 --- a/src/Reflection/Annotations/AnnotationPropertyReflection.php +++ b/src/Reflection/Annotations/AnnotationPropertyReflection.php @@ -143,4 +143,9 @@ public function isPrivateSet(): bool return false; } + public function getAttributes(): array + { + return []; + } + } diff --git a/src/Reflection/Annotations/AnnotationsMethodParameterReflection.php b/src/Reflection/Annotations/AnnotationsMethodParameterReflection.php index 4f6b640785..b01a6db6ff 100644 --- a/src/Reflection/Annotations/AnnotationsMethodParameterReflection.php +++ b/src/Reflection/Annotations/AnnotationsMethodParameterReflection.php @@ -75,4 +75,9 @@ public function getDefaultValue(): ?Type return $this->defaultValue; } + public function getAttributes(): array + { + return []; + } + } diff --git a/src/Reflection/AttributeReflection.php b/src/Reflection/AttributeReflection.php new file mode 100644 index 0000000000..74d6874223 --- /dev/null +++ b/src/Reflection/AttributeReflection.php @@ -0,0 +1,33 @@ + $argumentTypes + */ + public function __construct(private string $name, private array $argumentTypes) + { + } + + public function getName(): string + { + return $this->name; + } + + /** + * @return array + */ + public function getArgumentTypes(): array + { + return $this->argumentTypes; + } + +} diff --git a/src/Reflection/AttributeReflectionFactory.php b/src/Reflection/AttributeReflectionFactory.php new file mode 100644 index 0000000000..74a7d0efaa --- /dev/null +++ b/src/Reflection/AttributeReflectionFactory.php @@ -0,0 +1,134 @@ + $reflections + * @return list + */ + 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 + */ + 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 $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); + } + +} diff --git a/src/Reflection/BetterReflection/BetterReflectionProvider.php b/src/Reflection/BetterReflection/BetterReflectionProvider.php index 72f918f62b..4029d26554 100644 --- a/src/Reflection/BetterReflection/BetterReflectionProvider.php +++ b/src/Reflection/BetterReflection/BetterReflectionProvider.php @@ -31,6 +31,7 @@ use PHPStan\PhpDoc\Tag\ParamClosureThisTag; use PHPStan\PhpDoc\Tag\ParamOutTag; use PHPStan\Reflection\Assertions; +use PHPStan\Reflection\AttributeReflectionFactory; use PHPStan\Reflection\ClassNameHelper; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\Constant\RuntimeConstantReflection; @@ -93,6 +94,7 @@ public function __construct( private FileHelper $fileHelper, private PhpStormStubsSourceStubber $phpstormStubsSourceStubber, private SignatureMapProvider $signatureMapProvider, + private AttributeReflectionFactory $attributeReflectionFactory, private array $universalObjectCratesClasses, ) { @@ -146,6 +148,7 @@ public function getClass(string $className): ClassReflection $this->phpDocInheritanceResolver, $this->phpVersion, $this->signatureMapProvider, + $this->attributeReflectionFactory, $this->classReflectionExtensionRegistryProvider->getRegistry()->getPropertiesClassReflectionExtensions(), $this->classReflectionExtensionRegistryProvider->getRegistry()->getMethodsClassReflectionExtensions(), $this->classReflectionExtensionRegistryProvider->getRegistry()->getAllowedSubTypesClassReflectionExtensions(), @@ -240,6 +243,7 @@ public function getAnonymousClassReflection(Node\Stmt\Class_ $classNode, Scope $ $this->phpDocInheritanceResolver, $this->phpVersion, $this->signatureMapProvider, + $this->attributeReflectionFactory, $this->classReflectionExtensionRegistryProvider->getRegistry()->getPropertiesClassReflectionExtensions(), $this->classReflectionExtensionRegistryProvider->getRegistry()->getMethodsClassReflectionExtensions(), $this->classReflectionExtensionRegistryProvider->getRegistry()->getAllowedSubTypesClassReflectionExtensions(), @@ -354,6 +358,7 @@ private function getCustomFunction(string $functionName): PhpFunctionReflection array_map(static fn (ParamOutTag $paramOutTag): Type => $paramOutTag->getType(), $phpDocParameterOutTags), $phpDocParameterImmediatelyInvokedCallable, array_map(static fn (ParamClosureThisTag $tag): Type => $tag->getType(), $phpDocParameterClosureThisTypeTags), + $this->attributeReflectionFactory->fromNativeReflection($reflectionFunction->getAttributes(), InitializerExprContext::fromFunction($reflectionFunction->getName(), $reflectionFunction->getFileName() !== false ? $reflectionFunction->getFileName() : null)), ); } diff --git a/src/Reflection/ClassConstantReflection.php b/src/Reflection/ClassConstantReflection.php index cafc201341..6d0452caff 100644 --- a/src/Reflection/ClassConstantReflection.php +++ b/src/Reflection/ClassConstantReflection.php @@ -21,4 +21,9 @@ public function hasNativeType(): bool; public function getNativeType(): ?Type; + /** + * @return list + */ + public function getAttributes(): array; + } diff --git a/src/Reflection/ClassReflection.php b/src/Reflection/ClassReflection.php index 909a6b50b9..334311302a 100644 --- a/src/Reflection/ClassReflection.php +++ b/src/Reflection/ClassReflection.php @@ -155,6 +155,7 @@ public function __construct( private PhpDocInheritanceResolver $phpDocInheritanceResolver, private PhpVersion $phpVersion, private SignatureMapProvider $signatureMapProvider, + private AttributeReflectionFactory $attributeReflectionFactory, private array $propertiesClassReflectionExtensions, private array $methodsClassReflectionExtensions, private array $allowedSubTypesClassReflectionExtensions, @@ -773,7 +774,7 @@ public function getEnumCases(): array $valueType = $this->initializerExprTypeResolver->getType($case->getValueExpression(), $initializerExprContext); } $caseName = $case->getName(); - $cases[$caseName] = new EnumCaseReflection($this, $case, $valueType); + $cases[$caseName] = new EnumCaseReflection($this, $case, $valueType, $this->attributeReflectionFactory->fromNativeReflection($case->getAttributes(), InitializerExprContext::fromClass($this->getName(), $this->getFileName()))); } return $this->enumCases = $cases; @@ -799,7 +800,7 @@ public function getEnumCase(string $name): EnumCaseReflection $valueType = $this->initializerExprTypeResolver->getType($case->getValueExpression(), InitializerExprContext::fromClassReflection($this)); } - return new EnumCaseReflection($this, $case, $valueType); + return new EnumCaseReflection($this, $case, $valueType, $this->attributeReflectionFactory->fromNativeReflection($case->getAttributes(), InitializerExprContext::fromClass($this->getName(), $this->getFileName()))); } public function isClass(): bool @@ -1092,6 +1093,7 @@ public function getConstant(string $name): ClassConstantReflection $isDeprecated, $isInternal, $isFinal, + $this->attributeReflectionFactory->fromNativeReflection($reflectionConstant->getAttributes(), InitializerExprContext::fromClass($declaringClass->getName(), $fileName)), ); } return $this->constants[$name]; @@ -1322,6 +1324,14 @@ private function findAttributeFlags(): ?int return null; } + /** + * @return list + */ + public function getAttributes(): array + { + return $this->attributeReflectionFactory->fromNativeReflection($this->reflection->getAttributes(), InitializerExprContext::fromClass($this->getName(), $this->getFileName())); + } + public function getAttributeClassFlags(): int { $flags = $this->findAttributeFlags(); @@ -1505,6 +1515,7 @@ public function withTypes(array $types): self $this->phpDocInheritanceResolver, $this->phpVersion, $this->signatureMapProvider, + $this->attributeReflectionFactory, $this->propertiesClassReflectionExtensions, $this->methodsClassReflectionExtensions, $this->allowedSubTypesClassReflectionExtensions, @@ -1534,6 +1545,7 @@ public function withVariances(array $variances): self $this->phpDocInheritanceResolver, $this->phpVersion, $this->signatureMapProvider, + $this->attributeReflectionFactory, $this->propertiesClassReflectionExtensions, $this->methodsClassReflectionExtensions, $this->allowedSubTypesClassReflectionExtensions, diff --git a/src/Reflection/Dummy/ChangedTypeMethodReflection.php b/src/Reflection/Dummy/ChangedTypeMethodReflection.php index 3b3279596a..932d100db2 100644 --- a/src/Reflection/Dummy/ChangedTypeMethodReflection.php +++ b/src/Reflection/Dummy/ChangedTypeMethodReflection.php @@ -149,4 +149,9 @@ public function isPure(): TrinaryLogic return $this->reflection->isPure(); } + public function getAttributes(): array + { + return $this->reflection->getAttributes(); + } + } diff --git a/src/Reflection/Dummy/ChangedTypePropertyReflection.php b/src/Reflection/Dummy/ChangedTypePropertyReflection.php index f235b2e6e3..0421bb3ff9 100644 --- a/src/Reflection/Dummy/ChangedTypePropertyReflection.php +++ b/src/Reflection/Dummy/ChangedTypePropertyReflection.php @@ -141,4 +141,9 @@ public function isPrivateSet(): bool return $this->reflection->isPrivateSet(); } + public function getAttributes(): array + { + return $this->reflection->getAttributes(); + } + } diff --git a/src/Reflection/Dummy/DummyClassConstantReflection.php b/src/Reflection/Dummy/DummyClassConstantReflection.php index e38a8740dc..ffc6afe7c9 100644 --- a/src/Reflection/Dummy/DummyClassConstantReflection.php +++ b/src/Reflection/Dummy/DummyClassConstantReflection.php @@ -106,4 +106,9 @@ public function getNativeType(): ?Type return null; } + public function getAttributes(): array + { + return []; + } + } diff --git a/src/Reflection/Dummy/DummyConstructorReflection.php b/src/Reflection/Dummy/DummyConstructorReflection.php index e3dff92c38..4c2efda773 100644 --- a/src/Reflection/Dummy/DummyConstructorReflection.php +++ b/src/Reflection/Dummy/DummyConstructorReflection.php @@ -147,4 +147,9 @@ public function isPure(): TrinaryLogic return TrinaryLogic::createYes(); } + public function getAttributes(): array + { + return []; + } + } diff --git a/src/Reflection/Dummy/DummyMethodReflection.php b/src/Reflection/Dummy/DummyMethodReflection.php index c02b5ea370..a67f79e00b 100644 --- a/src/Reflection/Dummy/DummyMethodReflection.php +++ b/src/Reflection/Dummy/DummyMethodReflection.php @@ -139,4 +139,9 @@ public function isPure(): TrinaryLogic return TrinaryLogic::createMaybe(); } + public function getAttributes(): array + { + return []; + } + } diff --git a/src/Reflection/Dummy/DummyPropertyReflection.php b/src/Reflection/Dummy/DummyPropertyReflection.php index c2c6d4c768..133629a45c 100644 --- a/src/Reflection/Dummy/DummyPropertyReflection.php +++ b/src/Reflection/Dummy/DummyPropertyReflection.php @@ -137,4 +137,9 @@ public function isPrivateSet(): bool return false; } + public function getAttributes(): array + { + return []; + } + } diff --git a/src/Reflection/EnumCaseReflection.php b/src/Reflection/EnumCaseReflection.php index 7c250f0cf5..a84fd205ef 100644 --- a/src/Reflection/EnumCaseReflection.php +++ b/src/Reflection/EnumCaseReflection.php @@ -14,7 +14,15 @@ final class EnumCaseReflection { - public function __construct(private ClassReflection $declaringEnum, private ReflectionEnumUnitCase|ReflectionEnumBackedCase $reflection, private ?Type $backingValueType) + /** + * @param list $attributes + */ + public function __construct( + private ClassReflection $declaringEnum, + private ReflectionEnumUnitCase|ReflectionEnumBackedCase $reflection, + private ?Type $backingValueType, + private array $attributes, + ) { } @@ -48,4 +56,12 @@ public function getDeprecatedDescription(): ?string return null; } + /** + * @return list + */ + public function getAttributes(): array + { + return $this->attributes; + } + } diff --git a/src/Reflection/ExtendedMethodReflection.php b/src/Reflection/ExtendedMethodReflection.php index b49a71bb1a..03a9a2b2ea 100644 --- a/src/Reflection/ExtendedMethodReflection.php +++ b/src/Reflection/ExtendedMethodReflection.php @@ -58,4 +58,9 @@ public function isAbstract(): TrinaryLogic|bool; */ public function isPure(): TrinaryLogic; + /** + * @return list + */ + public function getAttributes(): array; + } diff --git a/src/Reflection/ExtendedParameterReflection.php b/src/Reflection/ExtendedParameterReflection.php index aff5f65822..ab50b76bd8 100644 --- a/src/Reflection/ExtendedParameterReflection.php +++ b/src/Reflection/ExtendedParameterReflection.php @@ -21,4 +21,9 @@ public function isImmediatelyInvokedCallable(): TrinaryLogic; public function getClosureThisType(): ?Type; + /** + * @return list + */ + public function getAttributes(): array; + } diff --git a/src/Reflection/ExtendedPropertyReflection.php b/src/Reflection/ExtendedPropertyReflection.php index 63b6246dd3..25f79396a1 100644 --- a/src/Reflection/ExtendedPropertyReflection.php +++ b/src/Reflection/ExtendedPropertyReflection.php @@ -54,4 +54,9 @@ public function isProtectedSet(): bool; public function isPrivateSet(): bool; + /** + * @return list + */ + public function getAttributes(): array; + } diff --git a/src/Reflection/FunctionReflection.php b/src/Reflection/FunctionReflection.php index 33b355b844..297e4dd7d3 100644 --- a/src/Reflection/FunctionReflection.php +++ b/src/Reflection/FunctionReflection.php @@ -57,4 +57,9 @@ public function returnsByReference(): TrinaryLogic; */ public function isPure(): TrinaryLogic; + /** + * @return list + */ + public function getAttributes(): array; + } diff --git a/src/Reflection/FunctionReflectionFactory.php b/src/Reflection/FunctionReflectionFactory.php index 405eea46b1..993bf34b3b 100644 --- a/src/Reflection/FunctionReflectionFactory.php +++ b/src/Reflection/FunctionReflectionFactory.php @@ -15,6 +15,7 @@ interface FunctionReflectionFactory * @param array $phpDocParameterOutTypes * @param array $phpDocParameterImmediatelyInvokedCallable * @param array $phpDocParameterClosureThisTypes + * @param list $attributes */ public function create( ReflectionFunction $reflection, @@ -33,6 +34,7 @@ public function create( array $phpDocParameterOutTypes, array $phpDocParameterImmediatelyInvokedCallable, array $phpDocParameterClosureThisTypes, + array $attributes, ): PhpFunctionReflection; } diff --git a/src/Reflection/GenericParametersAcceptorResolver.php b/src/Reflection/GenericParametersAcceptorResolver.php index e680908c32..35f70bf37d 100644 --- a/src/Reflection/GenericParametersAcceptorResolver.php +++ b/src/Reflection/GenericParametersAcceptorResolver.php @@ -103,6 +103,7 @@ public static function resolve(array $argTypes, ParametersAcceptor $parametersAc null, TrinaryLogic::createMaybe(), null, + [], ), $parametersAcceptor->getParameters()), $parametersAcceptor->isVariadic(), $parametersAcceptor->getReturnType(), diff --git a/src/Reflection/InitializerExprContext.php b/src/Reflection/InitializerExprContext.php index 650408f349..e6fb8ab6e5 100644 --- a/src/Reflection/InitializerExprContext.php +++ b/src/Reflection/InitializerExprContext.php @@ -90,6 +90,32 @@ public static function fromClass(string $className, ?string $fileName): self ); } + public static function fromFunction(string $functionName, ?string $fileName): self + { + return new self( + $fileName, + self::parseNamespace($functionName), + null, + null, + $functionName, + $functionName, + null, + ); + } + + public static function fromClassMethod(string $className, ?string $traitName, string $methodName, ?string $fileName): self + { + return new self( + $fileName, + self::parseNamespace($className), + $className, + $traitName, + $methodName, + sprintf('%s::%s', $className, $methodName), + null, + ); + } + public static function fromReflectionParameter(ReflectionParameter $parameter): self { $declaringFunction = $parameter->getDeclaringFunction(); diff --git a/src/Reflection/Native/ExtendedNativeParameterReflection.php b/src/Reflection/Native/ExtendedNativeParameterReflection.php index 90c653484b..00e2ea1a99 100644 --- a/src/Reflection/Native/ExtendedNativeParameterReflection.php +++ b/src/Reflection/Native/ExtendedNativeParameterReflection.php @@ -2,6 +2,7 @@ namespace PHPStan\Reflection\Native; +use PHPStan\Reflection\AttributeReflection; use PHPStan\Reflection\ExtendedParameterReflection; use PHPStan\Reflection\PassedByReference; use PHPStan\TrinaryLogic; @@ -11,6 +12,9 @@ final class ExtendedNativeParameterReflection implements ExtendedParameterReflection { + /** + * @param list $attributes + */ public function __construct( private string $name, private bool $optional, @@ -23,6 +27,7 @@ public function __construct( private ?Type $outType, private TrinaryLogic $immediatelyInvokedCallable, private ?Type $closureThisType, + private array $attributes, ) { } @@ -87,4 +92,9 @@ public function getClosureThisType(): ?Type return $this->closureThisType; } + public function getAttributes(): array + { + return $this->attributes; + } + } diff --git a/src/Reflection/Native/NativeFunctionReflection.php b/src/Reflection/Native/NativeFunctionReflection.php index 730f8c61e9..7668d51f9e 100644 --- a/src/Reflection/Native/NativeFunctionReflection.php +++ b/src/Reflection/Native/NativeFunctionReflection.php @@ -3,6 +3,7 @@ namespace PHPStan\Reflection\Native; use PHPStan\Reflection\Assertions; +use PHPStan\Reflection\AttributeReflection; use PHPStan\Reflection\ExtendedParametersAcceptor; use PHPStan\Reflection\FunctionReflection; use PHPStan\ShouldNotHappenException; @@ -20,6 +21,7 @@ final class NativeFunctionReflection implements FunctionReflection /** * @param list $variants * @param list|null $namedArgumentsVariants + * @param list $attributes */ public function __construct( private string $name, @@ -32,6 +34,7 @@ public function __construct( private ?string $phpDocComment, ?TrinaryLogic $returnsByReference, private bool $acceptsNamedArguments, + private array $attributes, ) { $this->assertions = $assertions ?? Assertions::createEmpty(); @@ -142,4 +145,9 @@ public function acceptsNamedArguments(): TrinaryLogic return TrinaryLogic::createFromBoolean($this->acceptsNamedArguments); } + public function getAttributes(): array + { + return $this->attributes; + } + } diff --git a/src/Reflection/Native/NativeMethodReflection.php b/src/Reflection/Native/NativeMethodReflection.php index 731d4973fe..a9ec6063d8 100644 --- a/src/Reflection/Native/NativeMethodReflection.php +++ b/src/Reflection/Native/NativeMethodReflection.php @@ -4,6 +4,7 @@ use PHPStan\BetterReflection\Reflection\Adapter\ReflectionMethod; use PHPStan\Reflection\Assertions; +use PHPStan\Reflection\AttributeReflection; use PHPStan\Reflection\ClassMemberReflection; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ExtendedMethodReflection; @@ -24,6 +25,7 @@ final class NativeMethodReflection implements ExtendedMethodReflection /** * @param list $variants * @param list|null $namedArgumentsVariants + * @param list $attributes */ public function __construct( private ReflectionProvider $reflectionProvider, @@ -37,6 +39,7 @@ public function __construct( private bool $acceptsNamedArguments, private ?Type $selfOutType, private ?string $phpDocComment, + private array $attributes, ) { } @@ -215,4 +218,9 @@ public function returnsByReference(): TrinaryLogic return TrinaryLogic::createFromBoolean($this->reflection->returnsReference()); } + public function getAttributes(): array + { + return $this->attributes; + } + } diff --git a/src/Reflection/ParametersAcceptorSelector.php b/src/Reflection/ParametersAcceptorSelector.php index 703d345806..81bc3a2a99 100644 --- a/src/Reflection/ParametersAcceptorSelector.php +++ b/src/Reflection/ParametersAcceptorSelector.php @@ -648,6 +648,7 @@ public static function combineAcceptors(array $acceptors): ExtendedParametersAcc $parameter instanceof ExtendedParameterReflection ? $parameter->getOutType() : null, $parameter instanceof ExtendedParameterReflection ? $parameter->isImmediatelyInvokedCallable() : TrinaryLogic::createMaybe(), $parameter instanceof ExtendedParameterReflection ? $parameter->getClosureThisType() : null, + $parameter instanceof ExtendedParameterReflection ? $parameter->getAttributes() : [], ); continue; } @@ -667,6 +668,7 @@ public static function combineAcceptors(array $acceptors): ExtendedParametersAcc $outType = $parameters[$i]->getOutType(); $immediatelyInvokedCallable = $parameters[$i]->isImmediatelyInvokedCallable(); $closureThisType = $parameters[$i]->getClosureThisType(); + $attributes = $parameters[$i]->getAttributes(); if ($parameter instanceof ExtendedParameterReflection) { $nativeType = TypeCombinator::union($nativeType, $parameter->getNativeType()); $phpDocType = TypeCombinator::union($phpDocType, $parameter->getPhpDocType()); @@ -684,6 +686,7 @@ public static function combineAcceptors(array $acceptors): ExtendedParametersAcc } $immediatelyInvokedCallable = $parameter->isImmediatelyInvokedCallable()->or($immediatelyInvokedCallable); + $attributes = array_merge($attributes, $parameter->getAttributes()); } else { $nativeType = new MixedType(); $phpDocType = $type; @@ -704,6 +707,7 @@ public static function combineAcceptors(array $acceptors): ExtendedParametersAcc $outType, $immediatelyInvokedCallable, $closureThisType, + $attributes, ); if ($isVariadic) { @@ -798,6 +802,7 @@ private static function wrapParameter(ParameterReflection $parameter): ExtendedP null, TrinaryLogic::createMaybe(), null, + [], ); } diff --git a/src/Reflection/Php/ClosureCallMethodReflection.php b/src/Reflection/Php/ClosureCallMethodReflection.php index e28f4cd259..ae8292e32d 100644 --- a/src/Reflection/Php/ClosureCallMethodReflection.php +++ b/src/Reflection/Php/ClosureCallMethodReflection.php @@ -96,6 +96,7 @@ public function getVariants(): array null, TrinaryLogic::createMaybe(), null, + [], ), $parameters), $this->closureType->isVariadic(), $this->closureType->getReturnType(), @@ -186,4 +187,9 @@ public function isPure(): TrinaryLogic return $this->nativeMethodReflection->isPure(); } + public function getAttributes(): array + { + return $this->nativeMethodReflection->getAttributes(); + } + } diff --git a/src/Reflection/Php/EnumCasesMethodReflection.php b/src/Reflection/Php/EnumCasesMethodReflection.php index 2758c04c27..61ee5d767b 100644 --- a/src/Reflection/Php/EnumCasesMethodReflection.php +++ b/src/Reflection/Php/EnumCasesMethodReflection.php @@ -151,4 +151,9 @@ public function isPure(): TrinaryLogic return TrinaryLogic::createYes(); } + public function getAttributes(): array + { + return []; + } + } diff --git a/src/Reflection/Php/EnumPropertyReflection.php b/src/Reflection/Php/EnumPropertyReflection.php index a74bb419ff..ca92d9258c 100644 --- a/src/Reflection/Php/EnumPropertyReflection.php +++ b/src/Reflection/Php/EnumPropertyReflection.php @@ -137,4 +137,9 @@ public function isPrivateSet(): bool return false; } + public function getAttributes(): array + { + return []; + } + } diff --git a/src/Reflection/Php/ExitFunctionReflection.php b/src/Reflection/Php/ExitFunctionReflection.php index 76c8e7cf7a..4020bbdc09 100644 --- a/src/Reflection/Php/ExitFunctionReflection.php +++ b/src/Reflection/Php/ExitFunctionReflection.php @@ -58,6 +58,7 @@ public function getVariants(): array null, TrinaryLogic::createNo(), null, + [], ), ], false, @@ -137,4 +138,9 @@ public function isPure(): TrinaryLogic return TrinaryLogic::createNo(); } + public function getAttributes(): array + { + return []; + } + } diff --git a/src/Reflection/Php/ExtendedDummyParameter.php b/src/Reflection/Php/ExtendedDummyParameter.php index 43151a7a7f..19a917e0a1 100644 --- a/src/Reflection/Php/ExtendedDummyParameter.php +++ b/src/Reflection/Php/ExtendedDummyParameter.php @@ -2,6 +2,7 @@ namespace PHPStan\Reflection\Php; +use PHPStan\Reflection\AttributeReflection; use PHPStan\Reflection\ExtendedParameterReflection; use PHPStan\Reflection\PassedByReference; use PHPStan\TrinaryLogic; @@ -11,6 +12,9 @@ final class ExtendedDummyParameter extends DummyParameter implements ExtendedParameterReflection { + /** + * @param list $attributes + */ public function __construct( string $name, Type $type, @@ -23,6 +27,7 @@ public function __construct( private ?Type $outType, private TrinaryLogic $immediatelyInvokedCallable, private ?Type $closureThisType, + private array $attributes, ) { parent::__construct($name, $type, $optional, $passedByReference, $variadic, $defaultValue); @@ -58,4 +63,9 @@ public function getClosureThisType(): ?Type return $this->closureThisType; } + public function getAttributes(): array + { + return $this->attributes; + } + } diff --git a/src/Reflection/Php/PhpClassReflectionExtension.php b/src/Reflection/Php/PhpClassReflectionExtension.php index 413f5bca73..a0d4d17471 100644 --- a/src/Reflection/Php/PhpClassReflectionExtension.php +++ b/src/Reflection/Php/PhpClassReflectionExtension.php @@ -20,10 +20,12 @@ use PHPStan\Reflection\Annotations\AnnotationsMethodsClassReflectionExtension; use PHPStan\Reflection\Annotations\AnnotationsPropertiesClassReflectionExtension; use PHPStan\Reflection\Assertions; +use PHPStan\Reflection\AttributeReflectionFactory; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ExtendedFunctionVariant; use PHPStan\Reflection\ExtendedMethodReflection; use PHPStan\Reflection\ExtendedPropertyReflection; +use PHPStan\Reflection\InitializerExprContext; use PHPStan\Reflection\MethodReflection; use PHPStan\Reflection\MethodsClassReflectionExtension; use PHPStan\Reflection\Native\ExtendedNativeParameterReflection; @@ -96,6 +98,7 @@ public function __construct( private StubPhpDocProvider $stubPhpDocProvider, private ReflectionProvider\ReflectionProviderProvider $reflectionProviderProvider, private FileTypeMapper $fileTypeMapper, + private AttributeReflectionFactory $attributeReflectionFactory, private bool $inferPrivatePropertyTypeFromConstructor, ) { @@ -213,7 +216,7 @@ private function createProperty( $types[] = $value; } - return new PhpPropertyReflection($declaringClassReflection, null, null, TypeCombinator::union(...$types), $classReflection->getNativeReflection()->getProperty($propertyName), null, null, null, false, false, false, false); + return new PhpPropertyReflection($declaringClassReflection, null, null, TypeCombinator::union(...$types), $classReflection->getNativeReflection()->getProperty($propertyName), null, null, null, false, false, false, false, []); } } @@ -425,6 +428,7 @@ private function createProperty( $isInternal, $isReadOnlyByPhpDoc, $isAllowedPrivateMutation, + $this->attributeReflectionFactory->fromNativeReflection($propertyReflection->getAttributes(), InitializerExprContext::fromClass($declaringClassReflection->getName(), $declaringClassReflection->getFileName())), ); } @@ -681,6 +685,7 @@ private function createMethod( $acceptsNamedArguments, $selfOutType, $phpDocComment, + $this->attributeReflectionFactory->fromNativeReflection($methodReflection->getAttributes(), InitializerExprContext::fromClassMethod($declaringClassName, null, $methodReflection->getName(), null)), ); } @@ -849,6 +854,7 @@ public function createUserlandMethodReflection(ClassReflection $fileDeclaringCla $immediatelyInvokedCallableParameters, $closureThisParameters, $acceptsNamedArguments, + $this->attributeReflectionFactory->fromNativeReflection($methodReflection->getAttributes(), InitializerExprContext::fromClassMethod($actualDeclaringClass->getName(), $declaringTraitName, $methodReflection->getName(), $actualDeclaringClass->getFileName())), ); } @@ -931,6 +937,7 @@ private function createNativeMethodVariant( $parameterOutType ?? $parameterSignature->getOutType(), $immediatelyInvoked, $closureThisType, + [], ); } diff --git a/src/Reflection/Php/PhpFunctionFromParserNodeReflection.php b/src/Reflection/Php/PhpFunctionFromParserNodeReflection.php index 809e32f888..02a5b642af 100644 --- a/src/Reflection/Php/PhpFunctionFromParserNodeReflection.php +++ b/src/Reflection/Php/PhpFunctionFromParserNodeReflection.php @@ -8,6 +8,7 @@ use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Function_; use PHPStan\Reflection\Assertions; +use PHPStan\Reflection\AttributeReflection; use PHPStan\Reflection\ExtendedFunctionVariant; use PHPStan\Reflection\ExtendedParameterReflection; use PHPStan\Reflection\ExtendedParametersAcceptor; @@ -41,9 +42,11 @@ class PhpFunctionFromParserNodeReflection implements FunctionReflection, Extende * @param Type[] $realParameterTypes * @param Type[] $phpDocParameterTypes * @param Type[] $realParameterDefaultValues + * @param array> $parameterAttributes * @param Type[] $parameterOutTypes * @param array $immediatelyInvokedCallableParameters * @param array $phpDocClosureThisTypeParameters + * @param list $attributes */ public function __construct( FunctionLike $functionLike, @@ -52,6 +55,7 @@ public function __construct( private array $realParameterTypes, private array $phpDocParameterTypes, private array $realParameterDefaultValues, + private array $parameterAttributes, private Type $realReturnType, private ?Type $phpDocReturnType, private ?Type $throwType, @@ -65,6 +69,7 @@ public function __construct( private array $parameterOutTypes, private array $immediatelyInvokedCallableParameters, private array $phpDocClosureThisTypeParameters, + private array $attributes, ) { $this->functionLike = $functionLike; @@ -180,6 +185,7 @@ public function getParameters(): array $this->parameterOutTypes[$parameter->var->name] ?? null, $immediatelyInvokedCallable, $closureThisType, + $this->parameterAttributes[$parameter->var->name] ?? [], ); } @@ -323,4 +329,9 @@ public function isPure(): TrinaryLogic return TrinaryLogic::createFromBoolean($this->isPure); } + public function getAttributes(): array + { + return $this->attributes; + } + } diff --git a/src/Reflection/Php/PhpFunctionReflection.php b/src/Reflection/Php/PhpFunctionReflection.php index ea6764ec2e..368ac3f471 100644 --- a/src/Reflection/Php/PhpFunctionReflection.php +++ b/src/Reflection/Php/PhpFunctionReflection.php @@ -8,10 +8,13 @@ use PHPStan\Parser\Parser; use PHPStan\Parser\VariadicFunctionsVisitor; use PHPStan\Reflection\Assertions; +use PHPStan\Reflection\AttributeReflection; +use PHPStan\Reflection\AttributeReflectionFactory; use PHPStan\Reflection\ExtendedFunctionVariant; use PHPStan\Reflection\ExtendedParameterReflection; use PHPStan\Reflection\ExtendedParametersAcceptor; use PHPStan\Reflection\FunctionReflection; +use PHPStan\Reflection\InitializerExprContext; use PHPStan\Reflection\InitializerExprTypeResolver; use PHPStan\TrinaryLogic; use PHPStan\Type\Generic\TemplateTypeMap; @@ -37,11 +40,13 @@ final class PhpFunctionReflection implements FunctionReflection * @param array $phpDocParameterOutTypes * @param array $phpDocParameterImmediatelyInvokedCallable * @param array $phpDocParameterClosureThisTypes + * @param list $attributes */ public function __construct( private InitializerExprTypeResolver $initializerExprTypeResolver, private ReflectionFunction $reflection, private Parser $parser, + private AttributeReflectionFactory $attributeReflectionFactory, private TemplateTypeMap $templateTypeMap, private array $phpDocParameterTypes, private ?Type $phpDocReturnType, @@ -57,6 +62,7 @@ public function __construct( private array $phpDocParameterOutTypes, private array $phpDocParameterImmediatelyInvokedCallable, private array $phpDocParameterClosureThisTypes, + private array $attributes, ) { } @@ -127,6 +133,7 @@ private function getParameters(): array $this->phpDocParameterOutTypes[$reflection->getName()] ?? null, $immediatelyInvokedCallable, $this->phpDocParameterClosureThisTypes[$reflection->getName()] ?? null, + $this->attributeReflectionFactory->fromNativeReflection($reflection->getAttributes(), InitializerExprContext::fromReflectionParameter($reflection)), ); }, $this->reflection->getParameters()); } @@ -262,4 +269,9 @@ public function acceptsNamedArguments(): TrinaryLogic return TrinaryLogic::createFromBoolean($this->acceptsNamedArguments); } + public function getAttributes(): array + { + return $this->attributes; + } + } diff --git a/src/Reflection/Php/PhpMethodFromParserNodeReflection.php b/src/Reflection/Php/PhpMethodFromParserNodeReflection.php index 66dfbd59ed..cd0e6fcbf6 100644 --- a/src/Reflection/Php/PhpMethodFromParserNodeReflection.php +++ b/src/Reflection/Php/PhpMethodFromParserNodeReflection.php @@ -5,6 +5,7 @@ use PhpParser\Node; use PhpParser\Node\Stmt\ClassMethod; use PHPStan\Reflection\Assertions; +use PHPStan\Reflection\AttributeReflection; use PHPStan\Reflection\ClassMemberReflection; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ExtendedMethodReflection; @@ -35,8 +36,10 @@ final class PhpMethodFromParserNodeReflection extends PhpFunctionFromParserNodeR * @param Type[] $realParameterTypes * @param Type[] $phpDocParameterTypes * @param Type[] $realParameterDefaultValues + * @param array> $parameterAttributes * @param array $immediatelyInvokedCallableParameters * @param array $phpDocClosureThisTypeParameters + * @param list $attributes */ public function __construct( private ClassReflection $declaringClass, @@ -47,6 +50,7 @@ public function __construct( array $realParameterTypes, array $phpDocParameterTypes, array $realParameterDefaultValues, + array $parameterAttributes, Type $realReturnType, ?Type $phpDocReturnType, ?Type $throwType, @@ -63,6 +67,7 @@ public function __construct( array $immediatelyInvokedCallableParameters, array $phpDocClosureThisTypeParameters, private bool $isConstructor, + array $attributes, ) { if ($this->classMethod instanceof Node\PropertyHook) { @@ -116,6 +121,7 @@ public function __construct( $realParameterTypes, $phpDocParameterTypes, $realParameterDefaultValues, + $parameterAttributes, $realReturnType, $phpDocReturnType, $throwType, @@ -129,6 +135,7 @@ public function __construct( $parameterOutTypes, $immediatelyInvokedCallableParameters, $phpDocClosureThisTypeParameters, + $attributes, ); } diff --git a/src/Reflection/Php/PhpMethodReflection.php b/src/Reflection/Php/PhpMethodReflection.php index 6209a57bc8..b2b45932a3 100644 --- a/src/Reflection/Php/PhpMethodReflection.php +++ b/src/Reflection/Php/PhpMethodReflection.php @@ -8,12 +8,15 @@ use PHPStan\Parser\Parser; use PHPStan\Parser\VariadicMethodsVisitor; use PHPStan\Reflection\Assertions; +use PHPStan\Reflection\AttributeReflection; +use PHPStan\Reflection\AttributeReflectionFactory; use PHPStan\Reflection\ClassMemberReflection; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ExtendedFunctionVariant; use PHPStan\Reflection\ExtendedMethodReflection; use PHPStan\Reflection\ExtendedParameterReflection; use PHPStan\Reflection\ExtendedParametersAcceptor; +use PHPStan\Reflection\InitializerExprContext; use PHPStan\Reflection\InitializerExprTypeResolver; use PHPStan\Reflection\MethodPrototypeReflection; use PHPStan\Reflection\ReflectionProvider; @@ -63,6 +66,7 @@ final class PhpMethodReflection implements ExtendedMethodReflection * @param Type[] $phpDocParameterOutTypes * @param array $immediatelyInvokedCallableParameters * @param array $phpDocClosureThisTypeParameters + * @param list $attributes */ public function __construct( private InitializerExprTypeResolver $initializerExprTypeResolver, @@ -70,6 +74,7 @@ public function __construct( private ?ClassReflection $declaringTrait, private ReflectionMethod $reflection, private ReflectionProvider $reflectionProvider, + private AttributeReflectionFactory $attributeReflectionFactory, private Parser $parser, private TemplateTypeMap $templateTypeMap, private array $phpDocParameterTypes, @@ -87,6 +92,7 @@ public function __construct( private array $phpDocParameterOutTypes, private array $immediatelyInvokedCallableParameters, private array $phpDocClosureThisTypeParameters, + private array $attributes, ) { } @@ -230,6 +236,7 @@ private function getParameters(): array $this->phpDocParameterOutTypes[$reflection->getName()] ?? null, $this->immediatelyInvokedCallableParameters[$reflection->getName()] ?? TrinaryLogic::createMaybe(), $this->phpDocClosureThisTypeParameters[$reflection->getName()] ?? null, + $this->attributeReflectionFactory->fromNativeReflection($reflection->getAttributes(), InitializerExprContext::fromReflectionParameter($reflection)), ), $this->reflection->getParameters()); } @@ -458,6 +465,7 @@ public function changePropertyGetHookPhpDocType(Type $phpDocType): self $this->declaringTrait, $this->reflection, $this->reflectionProvider, + $this->attributeReflectionFactory, $this->parser, $this->templateTypeMap, $this->phpDocParameterTypes, @@ -475,6 +483,7 @@ public function changePropertyGetHookPhpDocType(Type $phpDocType): self $this->phpDocParameterOutTypes, $this->immediatelyInvokedCallableParameters, $this->phpDocClosureThisTypeParameters, + $this->attributes, ); } @@ -489,6 +498,7 @@ public function changePropertySetHookPhpDocType(string $parameterName, Type $php $this->declaringTrait, $this->reflection, $this->reflectionProvider, + $this->attributeReflectionFactory, $this->parser, $this->templateTypeMap, $phpDocParameterTypes, @@ -506,7 +516,13 @@ public function changePropertySetHookPhpDocType(string $parameterName, Type $php $this->phpDocParameterOutTypes, $this->immediatelyInvokedCallableParameters, $this->phpDocClosureThisTypeParameters, + $this->attributes, ); } + public function getAttributes(): array + { + return $this->attributes; + } + } diff --git a/src/Reflection/Php/PhpMethodReflectionFactory.php b/src/Reflection/Php/PhpMethodReflectionFactory.php index 22028286d3..ec95a2de81 100644 --- a/src/Reflection/Php/PhpMethodReflectionFactory.php +++ b/src/Reflection/Php/PhpMethodReflectionFactory.php @@ -4,6 +4,7 @@ use PHPStan\BetterReflection\Reflection\Adapter\ReflectionMethod; use PHPStan\Reflection\Assertions; +use PHPStan\Reflection\AttributeReflection; use PHPStan\Reflection\ClassReflection; use PHPStan\TrinaryLogic; use PHPStan\Type\Generic\TemplateTypeMap; @@ -17,6 +18,7 @@ interface PhpMethodReflectionFactory * @param Type[] $phpDocParameterOutTypes * @param array $immediatelyInvokedCallableParameters * @param array $phpDocClosureThisTypeParameters + * @param list $attributes */ public function create( ClassReflection $declaringClass, @@ -38,6 +40,7 @@ public function create( array $immediatelyInvokedCallableParameters, array $phpDocClosureThisTypeParameters, bool $acceptsNamedArguments, + array $attributes, ): PhpMethodReflection; } diff --git a/src/Reflection/Php/PhpParameterFromParserNodeReflection.php b/src/Reflection/Php/PhpParameterFromParserNodeReflection.php index f9bdddc13e..f048ea7100 100644 --- a/src/Reflection/Php/PhpParameterFromParserNodeReflection.php +++ b/src/Reflection/Php/PhpParameterFromParserNodeReflection.php @@ -2,6 +2,7 @@ namespace PHPStan\Reflection\Php; +use PHPStan\Reflection\AttributeReflection; use PHPStan\Reflection\ExtendedParameterReflection; use PHPStan\Reflection\PassedByReference; use PHPStan\TrinaryLogic; @@ -15,6 +16,9 @@ final class PhpParameterFromParserNodeReflection implements ExtendedParameterRef private ?Type $type = null; + /** + * @param list $attributes + */ public function __construct( private string $name, private bool $optional, @@ -26,6 +30,7 @@ public function __construct( private ?Type $outType, private TrinaryLogic $immediatelyInvokedCallable, private ?Type $closureThisType, + private array $attributes, ) { } @@ -103,4 +108,9 @@ public function getClosureThisType(): ?Type return $this->closureThisType; } + public function getAttributes(): array + { + return $this->attributes; + } + } diff --git a/src/Reflection/Php/PhpParameterReflection.php b/src/Reflection/Php/PhpParameterReflection.php index c4c2713c4b..01f69e3ccb 100644 --- a/src/Reflection/Php/PhpParameterReflection.php +++ b/src/Reflection/Php/PhpParameterReflection.php @@ -3,6 +3,7 @@ namespace PHPStan\Reflection\Php; use PHPStan\BetterReflection\Reflection\Adapter\ReflectionParameter; +use PHPStan\Reflection\AttributeReflection; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ExtendedParameterReflection; use PHPStan\Reflection\InitializerExprContext; @@ -21,6 +22,9 @@ final class PhpParameterReflection implements ExtendedParameterReflection private ?Type $nativeType = null; + /** + * @param list $attributes + */ public function __construct( private InitializerExprTypeResolver $initializerExprTypeResolver, private ReflectionParameter $reflection, @@ -29,6 +33,7 @@ public function __construct( private ?Type $outType, private TrinaryLogic $immediatelyInvokedCallable, private ?Type $closureThisType, + private array $attributes, ) { } @@ -138,4 +143,9 @@ public function getClosureThisType(): ?Type return $this->closureThisType; } + public function getAttributes(): array + { + return $this->attributes; + } + } diff --git a/src/Reflection/Php/PhpPropertyReflection.php b/src/Reflection/Php/PhpPropertyReflection.php index 1284d4a699..c667c6e5e3 100644 --- a/src/Reflection/Php/PhpPropertyReflection.php +++ b/src/Reflection/Php/PhpPropertyReflection.php @@ -6,6 +6,7 @@ use PHPStan\BetterReflection\Reflection\Adapter\ReflectionNamedType; use PHPStan\BetterReflection\Reflection\Adapter\ReflectionProperty; use PHPStan\BetterReflection\Reflection\Adapter\ReflectionUnionType; +use PHPStan\Reflection\AttributeReflection; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ExtendedMethodReflection; use PHPStan\Reflection\ExtendedPropertyReflection; @@ -26,6 +27,9 @@ final class PhpPropertyReflection implements ExtendedPropertyReflection private ?Type $type = null; + /** + * @param list $attributes + */ public function __construct( private ClassReflection $declaringClass, private ?ClassReflection $declaringTrait, @@ -39,6 +43,7 @@ public function __construct( private bool $isInternal, private bool $isReadOnlyByPhpDoc, private bool $isAllowedPrivateMutation, + private array $attributes, ) { } @@ -278,4 +283,9 @@ public function isPrivateSet(): bool return $this->reflection->isPrivateSet(); } + public function getAttributes(): array + { + return $this->attributes; + } + } diff --git a/src/Reflection/Php/SimpleXMLElementProperty.php b/src/Reflection/Php/SimpleXMLElementProperty.php index d354ae5fe2..45438eb3c2 100644 --- a/src/Reflection/Php/SimpleXMLElementProperty.php +++ b/src/Reflection/Php/SimpleXMLElementProperty.php @@ -151,4 +151,9 @@ public function isPrivateSet(): bool return false; } + public function getAttributes(): array + { + return []; + } + } diff --git a/src/Reflection/Php/UniversalObjectCrateProperty.php b/src/Reflection/Php/UniversalObjectCrateProperty.php index 0a2f8faf35..0b478d51c5 100644 --- a/src/Reflection/Php/UniversalObjectCrateProperty.php +++ b/src/Reflection/Php/UniversalObjectCrateProperty.php @@ -141,4 +141,9 @@ public function isPrivateSet(): bool return false; } + public function getAttributes(): array + { + return []; + } + } diff --git a/src/Reflection/RealClassClassConstantReflection.php b/src/Reflection/RealClassClassConstantReflection.php index f9194090d3..96795f6427 100644 --- a/src/Reflection/RealClassClassConstantReflection.php +++ b/src/Reflection/RealClassClassConstantReflection.php @@ -14,6 +14,9 @@ final class RealClassClassConstantReflection implements ClassConstantReflection private ?Type $valueType = null; + /** + * @param list $attributes + */ public function __construct( private InitializerExprTypeResolver $initializerExprTypeResolver, private ClassReflection $declaringClass, @@ -24,6 +27,7 @@ public function __construct( private bool $isDeprecated, private bool $isInternal, private bool $isFinal, + private array $attributes, ) { } @@ -144,4 +148,9 @@ public function getDocComment(): ?string return $docComment; } + public function getAttributes(): array + { + return $this->attributes; + } + } diff --git a/src/Reflection/ResolvedFunctionVariantWithOriginal.php b/src/Reflection/ResolvedFunctionVariantWithOriginal.php index d3dd2aa2f1..d7d2f09acc 100644 --- a/src/Reflection/ResolvedFunctionVariantWithOriginal.php +++ b/src/Reflection/ResolvedFunctionVariantWithOriginal.php @@ -120,6 +120,7 @@ function (ExtendedParameterReflection $param): ExtendedParameterReflection { $paramOutType, $param->isImmediatelyInvokedCallable(), $closureThisType, + $param->getAttributes(), ); }, $this->parametersAcceptor->getParameters(), diff --git a/src/Reflection/ResolvedMethodReflection.php b/src/Reflection/ResolvedMethodReflection.php index 33b70bafe4..bd7ef46f2b 100644 --- a/src/Reflection/ResolvedMethodReflection.php +++ b/src/Reflection/ResolvedMethodReflection.php @@ -214,4 +214,9 @@ public function isAbstract(): TrinaryLogic return $abstract; } + public function getAttributes(): array + { + return $this->reflection->getAttributes(); + } + } diff --git a/src/Reflection/ResolvedPropertyReflection.php b/src/Reflection/ResolvedPropertyReflection.php index e964d99b5e..797b1ede60 100644 --- a/src/Reflection/ResolvedPropertyReflection.php +++ b/src/Reflection/ResolvedPropertyReflection.php @@ -203,4 +203,9 @@ public function isPrivateSet(): bool return $this->reflection->isPrivateSet(); } + public function getAttributes(): array + { + return $this->reflection->getAttributes(); + } + } diff --git a/src/Reflection/SignatureMap/NativeFunctionReflectionProvider.php b/src/Reflection/SignatureMap/NativeFunctionReflectionProvider.php index 6332238c33..766e665115 100644 --- a/src/Reflection/SignatureMap/NativeFunctionReflectionProvider.php +++ b/src/Reflection/SignatureMap/NativeFunctionReflectionProvider.php @@ -9,7 +9,9 @@ use PHPStan\PhpDoc\ResolvedPhpDocBlock; use PHPStan\PhpDoc\StubPhpDocProvider; use PHPStan\Reflection\Assertions; +use PHPStan\Reflection\AttributeReflectionFactory; use PHPStan\Reflection\ExtendedFunctionVariant; +use PHPStan\Reflection\InitializerExprContext; use PHPStan\Reflection\Native\ExtendedNativeParameterReflection; use PHPStan\Reflection\Native\NativeFunctionReflection; use PHPStan\TrinaryLogic; @@ -28,7 +30,13 @@ final class NativeFunctionReflectionProvider /** @var NativeFunctionReflection[] */ private array $functionMap = []; - public function __construct(private SignatureMapProvider $signatureMapProvider, private Reflector $reflector, private FileTypeMapper $fileTypeMapper, private StubPhpDocProvider $stubPhpDocProvider) + public function __construct( + private SignatureMapProvider $signatureMapProvider, + private Reflector $reflector, + private FileTypeMapper $fileTypeMapper, + private StubPhpDocProvider $stubPhpDocProvider, + private AttributeReflectionFactory $attributeReflectionFactory, + ) { } @@ -52,9 +60,12 @@ public function findFunctionReflection(string $functionName): ?NativeFunctionRef $docComment = null; $returnsByReference = TrinaryLogic::createMaybe(); $acceptsNamedArguments = true; + $fileName = null; + $attributes = []; try { $reflectionFunction = $this->reflector->reflectFunction($functionName); $reflectionFunctionAdapter = new ReflectionFunction($reflectionFunction); + $attributes = $reflectionFunctionAdapter->getAttributes(); $returnsByReference = TrinaryLogic::createFromBoolean($reflectionFunctionAdapter->returnsReference()); $realFunctionName = $reflectionFunction->getName(); $isDeprecated = $reflectionFunction->isDeprecated(); @@ -124,6 +135,7 @@ public function findFunctionReflection(string $functionName): ?NativeFunctionRef $phpDoc !== null ? NativeFunctionReflectionProvider::getParamOutTypeFromPhpDoc($parameterSignature->getName(), $phpDoc) : null, $immediatelyInvokedCallable, $closureThisType, + [], ); }, $functionSignature->getParameters()), $functionSignature->isVariadic(), @@ -151,6 +163,7 @@ public function findFunctionReflection(string $functionName): ?NativeFunctionRef $docComment, $returnsByReference, $acceptsNamedArguments, + $this->attributeReflectionFactory->fromNativeReflection($attributes, InitializerExprContext::fromFunction($realFunctionName, $fileName)), ); $this->functionMap[$lowerCasedFunctionName] = $functionReflection; diff --git a/src/Reflection/Type/CallbackUnresolvedMethodPrototypeReflection.php b/src/Reflection/Type/CallbackUnresolvedMethodPrototypeReflection.php index eaca01ec4c..3b9572b61e 100644 --- a/src/Reflection/Type/CallbackUnresolvedMethodPrototypeReflection.php +++ b/src/Reflection/Type/CallbackUnresolvedMethodPrototypeReflection.php @@ -98,6 +98,7 @@ private function transformMethodWithStaticType(ClassReflection $declaringClass, $parameter->getOutType() !== null ? $this->transformStaticType($parameter->getOutType()) : null, $parameter->isImmediatelyInvokedCallable(), $parameter->getClosureThisType() !== null ? $this->transformStaticType($parameter->getClosureThisType()) : null, + $parameter->getAttributes(), ), $acceptor->getParameters(), ), diff --git a/src/Reflection/Type/CalledOnTypeUnresolvedMethodPrototypeReflection.php b/src/Reflection/Type/CalledOnTypeUnresolvedMethodPrototypeReflection.php index 1e254b94b7..6fce0b017f 100644 --- a/src/Reflection/Type/CalledOnTypeUnresolvedMethodPrototypeReflection.php +++ b/src/Reflection/Type/CalledOnTypeUnresolvedMethodPrototypeReflection.php @@ -95,6 +95,7 @@ private function transformMethodWithStaticType(ClassReflection $declaringClass, $parameter->getOutType() !== null ? $this->transformStaticType($parameter->getOutType()) : null, $parameter->isImmediatelyInvokedCallable(), $parameter->getClosureThisType() !== null ? $this->transformStaticType($parameter->getClosureThisType()) : null, + $parameter->getAttributes(), ), $acceptor->getParameters(), ), diff --git a/src/Reflection/Type/IntersectionTypeMethodReflection.php b/src/Reflection/Type/IntersectionTypeMethodReflection.php index c19986d71c..f0ce213ad7 100644 --- a/src/Reflection/Type/IntersectionTypeMethodReflection.php +++ b/src/Reflection/Type/IntersectionTypeMethodReflection.php @@ -218,4 +218,9 @@ public function isAbstract(): TrinaryLogic return TrinaryLogic::lazyMaxMin($this->methods, static fn (ExtendedMethodReflection $method): TrinaryLogic => is_bool($method->isAbstract()) ? TrinaryLogic::createFromBoolean($method->isAbstract()) : $method->isAbstract()); } + public function getAttributes(): array + { + return $this->methods[0]->getAttributes(); + } + } diff --git a/src/Reflection/Type/IntersectionTypePropertyReflection.php b/src/Reflection/Type/IntersectionTypePropertyReflection.php index 9976bab57d..d0729a2260 100644 --- a/src/Reflection/Type/IntersectionTypePropertyReflection.php +++ b/src/Reflection/Type/IntersectionTypePropertyReflection.php @@ -190,4 +190,9 @@ public function isPrivateSet(): bool return $this->computeResult(static fn (ExtendedPropertyReflection $property) => $property->isPrivateSet()); } + public function getAttributes(): array + { + return $this->properties[0]->getAttributes(); + } + } diff --git a/src/Reflection/Type/UnionTypeMethodReflection.php b/src/Reflection/Type/UnionTypeMethodReflection.php index 167493c0b8..3d8015ddaf 100644 --- a/src/Reflection/Type/UnionTypeMethodReflection.php +++ b/src/Reflection/Type/UnionTypeMethodReflection.php @@ -195,4 +195,9 @@ public function isAbstract(): TrinaryLogic return TrinaryLogic::lazyExtremeIdentity($this->methods, static fn (ExtendedMethodReflection $method): TrinaryLogic => is_bool($method->isAbstract()) ? TrinaryLogic::createFromBoolean($method->isAbstract()) : $method->isAbstract()); } + public function getAttributes(): array + { + return $this->methods[0]->getAttributes(); + } + } diff --git a/src/Reflection/Type/UnionTypePropertyReflection.php b/src/Reflection/Type/UnionTypePropertyReflection.php index 24e2e91156..eb2d00aed5 100644 --- a/src/Reflection/Type/UnionTypePropertyReflection.php +++ b/src/Reflection/Type/UnionTypePropertyReflection.php @@ -190,4 +190,9 @@ public function isPrivateSet(): bool return $this->computeResult(static fn (ExtendedPropertyReflection $property) => $property->isPrivateSet()); } + public function getAttributes(): array + { + return $this->properties[0]->getAttributes(); + } + } diff --git a/src/Reflection/WrappedExtendedMethodReflection.php b/src/Reflection/WrappedExtendedMethodReflection.php index c14e51656e..42bc13b430 100644 --- a/src/Reflection/WrappedExtendedMethodReflection.php +++ b/src/Reflection/WrappedExtendedMethodReflection.php @@ -75,6 +75,7 @@ public function getVariants(): array null, TrinaryLogic::createMaybe(), null, + [], ), $variant->getParameters()), $variant->isVariadic(), $variant->getReturnType(), @@ -162,4 +163,9 @@ public function isAbstract(): TrinaryLogic return TrinaryLogic::createNo(); } + public function getAttributes(): array + { + return []; + } + } diff --git a/src/Reflection/WrappedExtendedPropertyReflection.php b/src/Reflection/WrappedExtendedPropertyReflection.php index fe64beb0f0..9f4fb2d904 100644 --- a/src/Reflection/WrappedExtendedPropertyReflection.php +++ b/src/Reflection/WrappedExtendedPropertyReflection.php @@ -134,4 +134,9 @@ public function isPrivateSet(): bool return false; } + public function getAttributes(): array + { + return []; + } + } diff --git a/src/Rules/Properties/FoundPropertyReflection.php b/src/Rules/Properties/FoundPropertyReflection.php index 19e77db7e0..1b14b785aa 100644 --- a/src/Rules/Properties/FoundPropertyReflection.php +++ b/src/Rules/Properties/FoundPropertyReflection.php @@ -173,4 +173,9 @@ public function isPrivateSet(): bool return $this->originalPropertyReflection->isPrivateSet(); } + public function getAttributes(): array + { + return $this->originalPropertyReflection->getAttributes(); + } + } diff --git a/src/Testing/PHPStanTestCase.php b/src/Testing/PHPStanTestCase.php index 849fbfac11..7587ac8d05 100644 --- a/src/Testing/PHPStanTestCase.php +++ b/src/Testing/PHPStanTestCase.php @@ -25,6 +25,7 @@ use PHPStan\Php\PhpVersion; use PHPStan\PhpDoc\TypeNodeResolver; use PHPStan\PhpDoc\TypeStringResolver; +use PHPStan\Reflection\AttributeReflectionFactory; use PHPStan\Reflection\InitializerExprTypeResolver; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Reflection\ReflectionProvider\DirectReflectionProviderProvider; @@ -163,6 +164,7 @@ public static function createScopeFactory(ReflectionProvider $reflectionProvider $container->getByType(NodeScopeResolver::class), new RicherScopeGetTypeHelper($initializerExprTypeResolver), $container->getByType(PhpVersion::class), + $container->getByType(AttributeReflectionFactory::class), $container->getParameter('phpVersion'), $constantResolver, ), diff --git a/src/Testing/RuleTestCase.php b/src/Testing/RuleTestCase.php index 8018b44181..b40a8ebca0 100644 --- a/src/Testing/RuleTestCase.php +++ b/src/Testing/RuleTestCase.php @@ -22,6 +22,7 @@ use PHPStan\Php\PhpVersion; use PHPStan\PhpDoc\PhpDocInheritanceResolver; use PHPStan\PhpDoc\StubPhpDocProvider; +use PHPStan\Reflection\AttributeReflectionFactory; use PHPStan\Reflection\InitializerExprTypeResolver; use PHPStan\Reflection\SignatureMap\SignatureMapProvider; use PHPStan\Rules\DirectRegistry as DirectRuleRegistry; @@ -90,6 +91,7 @@ private function getAnalyser(DirectRuleRegistry $ruleRegistry): Analyser self::getContainer()->getByType(StubPhpDocProvider::class), self::getContainer()->getByType(PhpVersion::class), self::getContainer()->getByType(SignatureMapProvider::class), + self::getContainer()->getByType(AttributeReflectionFactory::class), self::getContainer()->getByType(PhpDocInheritanceResolver::class), self::getContainer()->getByType(FileHelper::class), $typeSpecifier, diff --git a/src/Testing/TypeInferenceTestCase.php b/src/Testing/TypeInferenceTestCase.php index 614db4f0c5..da7cbe82c2 100644 --- a/src/Testing/TypeInferenceTestCase.php +++ b/src/Testing/TypeInferenceTestCase.php @@ -16,6 +16,7 @@ use PHPStan\Php\PhpVersion; use PHPStan\PhpDoc\PhpDocInheritanceResolver; use PHPStan\PhpDoc\StubPhpDocProvider; +use PHPStan\Reflection\AttributeReflectionFactory; use PHPStan\Reflection\InitializerExprTypeResolver; use PHPStan\Reflection\SignatureMap\SignatureMapProvider; use PHPStan\Rules\Properties\ReadWritePropertiesExtensionProvider; @@ -70,6 +71,7 @@ public static function processFile( self::getContainer()->getByType(StubPhpDocProvider::class), self::getContainer()->getByType(PhpVersion::class), self::getContainer()->getByType(SignatureMapProvider::class), + self::getContainer()->getByType(AttributeReflectionFactory::class), self::getContainer()->getByType(PhpDocInheritanceResolver::class), self::getContainer()->getByType(FileHelper::class), $typeSpecifier, diff --git a/src/Type/ObjectShapePropertyReflection.php b/src/Type/ObjectShapePropertyReflection.php index d5fb99f546..971a96b2f6 100644 --- a/src/Type/ObjectShapePropertyReflection.php +++ b/src/Type/ObjectShapePropertyReflection.php @@ -139,4 +139,9 @@ public function isPrivateSet(): bool return false; } + public function getAttributes(): array + { + return []; + } + } diff --git a/tests/PHPStan/Analyser/AnalyserTest.php b/tests/PHPStan/Analyser/AnalyserTest.php index 0a27ee2889..6162106ac5 100644 --- a/tests/PHPStan/Analyser/AnalyserTest.php +++ b/tests/PHPStan/Analyser/AnalyserTest.php @@ -19,6 +19,7 @@ use PHPStan\Php\PhpVersion; use PHPStan\PhpDoc\PhpDocInheritanceResolver; use PHPStan\PhpDoc\StubPhpDocProvider; +use PHPStan\Reflection\AttributeReflectionFactory; use PHPStan\Reflection\InitializerExprTypeResolver; use PHPStan\Reflection\SignatureMap\SignatureMapProvider; use PHPStan\Rules\AlwaysFailRule; @@ -713,6 +714,7 @@ private function createAnalyser(): Analyser self::getContainer()->getByType(StubPhpDocProvider::class), self::getContainer()->getByType(PhpVersion::class), self::getContainer()->getByType(SignatureMapProvider::class), + self::getContainer()->getByType(AttributeReflectionFactory::class), $phpDocInheritanceResolver, $fileHelper, $typeSpecifier, diff --git a/tests/PHPStan/Reflection/AttributeReflectionFromNodeRuleTest.php b/tests/PHPStan/Reflection/AttributeReflectionFromNodeRuleTest.php new file mode 100644 index 0000000000..99ef6d9495 --- /dev/null +++ b/tests/PHPStan/Reflection/AttributeReflectionFromNodeRuleTest.php @@ -0,0 +1,96 @@ +> + */ +class AttributeReflectionFromNodeRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + return new /** @implements Rule */ class implements Rule { + + public function getNodeType(): string + { + return NodeAbstract::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ($node instanceof InClassMethodNode) { + $reflection = $node->getMethodReflection(); + } elseif ($node instanceof InFunctionNode) { + $reflection = $node->getFunctionReflection(); + } else { + return []; + } + + $parts = []; + foreach ($reflection->getAttributes() as $attribute) { + $args = []; + foreach ($attribute->getArgumentTypes() as $argName => $argType) { + $args[] = sprintf('%s: %s', $argName, $argType->describe(VerbosityLevel::precise())); + } + + $parts[] = sprintf('#[%s(%s)]', $attribute->getName(), implode(', ', $args)); + } + + if (count($parts) === 0) { + return []; + } + + return [ + RuleErrorBuilder::message(implode(', ', $parts))->identifier('test.attributes')->build(), + ]; + } + + }; + } + + public function testRule(): void + { + if (PHP_VERSION_ID < 80000) { + self::markTestSkipped('Test requires PHP 8.0'); + } + + $this->analyse([__DIR__ . '/data/attribute-reflection.php'], [ + [ + '#[AttributeReflectionTest\MyAttr(one: 7, two: 8)]', + 28, + ], + [ + '#[AttributeReflectionTest\MyAttr()]', + 39, + ], + [ + '#[AttributeReflectionTest\Nonexistent()]', + 44, + ], + [ + '#[AttributeReflectionTest\MyAttr(one: 11, two: 12)]', + 54, + ], + [ + '#[AttributeReflectionTest\MyAttr(one: 28, two: 29)]', + 59, + ], + ]); + } + +} diff --git a/tests/PHPStan/Reflection/AttributeReflectionTest.php b/tests/PHPStan/Reflection/AttributeReflectionTest.php new file mode 100644 index 0000000000..f0c22f45ec --- /dev/null +++ b/tests/PHPStan/Reflection/AttributeReflectionTest.php @@ -0,0 +1,179 @@ +createReflectionProvider(); + + yield [ + $reflectionProvider->getFunction(new Name('AttributeReflectionTest\\myFunction'), null)->getAttributes(), + [ + [MyAttr::class, []], + ], + ]; + + yield [ + $reflectionProvider->getFunction(new Name('AttributeReflectionTest\\myFunction2'), null)->getAttributes(), + [ + ['AttributeReflectionTest\\Nonexistent', []], + ], + ]; + + yield [ + $reflectionProvider->getFunction(new Name('AttributeReflectionTest\\myFunction3'), null)->getAttributes(), + [], + ]; + + yield [ + $reflectionProvider->getFunction(new Name('AttributeReflectionTest\\myFunction4'), null)->getAttributes(), + [ + [ + MyAttr::class, + [ + 'one' => '11', + 'two' => '12', + ], + ], + ], + ]; + + $foo = $reflectionProvider->getClass(Foo::class); + + yield [ + $foo->getAttributes(), + [ + [ + MyAttr::class, + [ + 'one' => '1', + 'two' => '2', + ], + ], + ], + ]; + + yield [ + $foo->getConstant('MY_CONST')->getAttributes(), + [ + [ + MyAttr::class, + [ + 'one' => '3', + 'two' => '4', + ], + ], + ], + ]; + + yield [ + $foo->getNativeProperty('prop')->getAttributes(), + [ + [ + MyAttr::class, + [ + 'one' => '5', + 'two' => '6', + ], + ], + ], + ]; + + yield [ + $foo->getConstructor()->getAttributes(), + [ + [ + MyAttr::class, + [ + 'one' => '7', + 'two' => '8', + ], + ], + ], + ]; + + if (PHP_VERSION_ID >= 80100) { + $enum = $reflectionProvider->getClass('AttributeReflectionTest\\FooEnum'); + + yield [ + $enum->getEnumCase('TEST')->getAttributes(), + [ + [ + MyAttr::class, + [ + 'one' => '15', + 'two' => '16', + ], + ], + ], + ]; + + yield [ + $enum->getEnumCases()['TEST']->getAttributes(), + [ + [ + MyAttr::class, + [ + 'one' => '15', + 'two' => '16', + ], + ], + ], + ]; + } + + yield [ + $foo->getConstructor()->getOnlyVariant()->getParameters()[0]->getAttributes(), + [ + [ + MyAttr::class, + [ + 'one' => '9', + 'two' => '10', + ], + ], + ], + ]; + } + + /** + * @dataProvider dataAttributeReflections + * @param list $attributeReflections + * @param list}> $expectations + */ + public function testAttributeReflections( + array $attributeReflections, + array $expectations, + ): void + { + if (PHP_VERSION_ID < 80000) { + self::markTestSkipped('Test requires PHP 8.0'); + } + + $this->assertCount(count($expectations), $attributeReflections); + foreach ($expectations as $i => [$name, $argumentTypes]) { + $attribute = $attributeReflections[$i]; + $this->assertSame($name, $attribute->getName()); + + $attributeArgumentTypes = $attribute->getArgumentTypes(); + $this->assertCount(count($argumentTypes), $attributeArgumentTypes); + + foreach ($argumentTypes as $argumentName => $argumentType) { + $this->assertArrayHasKey($argumentName, $attributeArgumentTypes); + $this->assertSame($argumentType, $attributeArgumentTypes[$argumentName]->describe(VerbosityLevel::precise())); + } + } + } + +} diff --git a/tests/PHPStan/Reflection/data/attribute-reflection-enum.php b/tests/PHPStan/Reflection/data/attribute-reflection-enum.php new file mode 100644 index 0000000000..fd02812679 --- /dev/null +++ b/tests/PHPStan/Reflection/data/attribute-reflection-enum.php @@ -0,0 +1,11 @@ += 8.1 + +namespace AttributeReflectionTest; + +enum FooEnum +{ + + #[MyAttr(one: 15, two: 16)] + case TEST; + +} diff --git a/tests/PHPStan/Reflection/data/attribute-reflection.php b/tests/PHPStan/Reflection/data/attribute-reflection.php new file mode 100644 index 0000000000..34ec36599f --- /dev/null +++ b/tests/PHPStan/Reflection/data/attribute-reflection.php @@ -0,0 +1,62 @@ += 8.0 + +namespace AttributeReflectionTest; + +use Attribute; + +#[Attribute] +class MyAttr +{ + + public function __construct($one, $two) + { + + } + +} + +#[MyAttr(1, 2)] +class Foo +{ + + #[MyAttr(one: 3, two: 4)] + public const MY_CONST = 1; + + #[MyAttr(two: 6, one: 5)] + private $prop; + + #[MyAttr(7, 8)] + public function __construct( + #[MyAttr(9, 10)] + int $test + ) + { + + } + +} + +#[MyAttr()] +function myFunction() { + +} + +#[Nonexistent()] +function myFunction2() { + +} + +#[Nonexistent(1, 2)] +function myFunction3() { + +} + +#[MyAttr(11, 12)] +function myFunction4() { + +} + +#[MyAttr(28, two: 29)] +function myFunction5() { + +}