diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 1639a12522..3bc323341c 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -1988,7 +1988,7 @@ static function (Node $node, Scope $scope) use ($nodeCallback): void { private function getDeprecatedAttribute(Scope $scope, Node\Stmt\Function_|Node\Stmt\ClassMethod|Node\PropertyHook $stmt): array { $initializerExprContext = InitializerExprContext::fromStubParameter( - null, + $scope->isInClass() ? $scope->getClassReflection()->getName() : null, $scope->getFile(), $stmt, ); diff --git a/src/Reflection/InitializerExprContext.php b/src/Reflection/InitializerExprContext.php index f1587ef242..eb64cacdbb 100644 --- a/src/Reflection/InitializerExprContext.php +++ b/src/Reflection/InitializerExprContext.php @@ -9,6 +9,8 @@ use PHPStan\BetterReflection\Reflection\Adapter\ReflectionFunction; use PHPStan\BetterReflection\Reflection\Adapter\ReflectionParameter; use PHPStan\BetterReflection\Reflection\ReflectionConstant; +use PHPStan\Parser\PropertyHookNameVisitor; +use PHPStan\Reflection\Php\PhpMethodFromParserNodeReflection; use PHPStan\ShouldNotHappenException; use function array_slice; use function count; @@ -32,21 +34,25 @@ private function __construct( private ?string $traitName, private ?string $function, private ?string $method, + private ?string $property, ) { } public static function fromScope(Scope $scope): self { + $function = $scope->getFunction(); + return new self( $scope->getFile(), $scope->getNamespace(), $scope->isInClass() ? $scope->getClassReflection()->getName() : null, $scope->isInTrait() ? $scope->getTraitReflection()->getName() : null, - $scope->isInAnonymousFunction() ? '{closure}' : ($scope->getFunction() !== null ? $scope->getFunction()->getName() : null), - $scope->isInAnonymousFunction() ? '{closure}' : ($scope->getFunction() instanceof MethodReflection - ? sprintf('%s::%s', $scope->getFunction()->getDeclaringClass()->getName(), $scope->getFunction()->getName()) - : ($scope->getFunction() instanceof FunctionReflection ? $scope->getFunction()->getName() : null)), + $scope->isInAnonymousFunction() ? '{closure}' : ($function !== null ? $function->getName() : null), + $scope->isInAnonymousFunction() ? '{closure}' : ($function instanceof MethodReflection + ? sprintf('%s::%s', $function->getDeclaringClass()->getName(), $function->getName()) + : ($function instanceof FunctionReflection ? $function->getName() : null)), + $function instanceof PhpMethodFromParserNodeReflection && $function->isPropertyHook() ? $function->getHookedPropertyName() : null, ); } @@ -81,6 +87,7 @@ public static function fromClass(string $className, ?string $fileName): self null, null, null, + null, ); } @@ -96,6 +103,7 @@ public static function fromReflectionParameter(ReflectionParameter $parameter): null, $declaringFunction->getName(), $declaringFunction->getName(), + null, // Property hook parameter cannot have a default value. fromReflectionParameter is only used for that ); } @@ -110,6 +118,7 @@ public static function fromReflectionParameter(ReflectionParameter $parameter): $betterReflection->getDeclaringClass()->isTrait() ? $betterReflection->getDeclaringClass()->getName() : null, $declaringFunction->getName(), sprintf('%s::%s', $declaringFunction->getDeclaringClass()->getName(), $declaringFunction->getName()), + null, // Property hook parameter cannot have a default value. fromReflectionParameter is only used for that ); } @@ -127,15 +136,36 @@ public static function fromStubParameter( $namespace = self::parseNamespace($function->namespacedName->toString()); } } + + $functionName = null; + $propertyName = null; + if ($function instanceof Function_ && $function->namespacedName !== null) { + $functionName = $function->namespacedName->toString(); + } elseif ($function instanceof ClassMethod) { + $functionName = $function->name->toString(); + } elseif ($function instanceof PropertyHook) { + $propertyName = $function->getAttribute(PropertyHookNameVisitor::ATTRIBUTE_NAME); + $functionName = sprintf('$%s::%s', $propertyName, $function->name->toString()); + } + + $methodName = null; + if ($function instanceof ClassMethod && $className !== null) { + $methodName = sprintf('%s::%s', $className, $function->name->toString()); + } elseif ($function instanceof PropertyHook) { + $propertyName = $function->getAttribute(PropertyHookNameVisitor::ATTRIBUTE_NAME); + $methodName = sprintf('%s::$%s::%s', $className, $propertyName, $function->name->toString()); + } elseif ($function instanceof Function_ && $function->namespacedName !== null) { + $methodName = $function->namespacedName->toString(); + } + return new self( $stubFile, $namespace, $className, null, - $function instanceof Function_ && $function->namespacedName !== null ? $function->namespacedName->toString() : ($function instanceof ClassMethod ? $function->name->toString() : null), - $function instanceof ClassMethod && $className !== null - ? sprintf('%s::%s', $className, $function->name->toString()) - : ($function instanceof Function_ && $function->namespacedName !== null ? $function->namespacedName->toString() : null), + $functionName, + $methodName, + $propertyName, ); } @@ -148,12 +178,13 @@ public static function fromGlobalConstant(ReflectionConstant $constant): self null, null, null, + null, ); } public static function createEmpty(): self { - return new self(null, null, null, null, null, null); + return new self(null, null, null, null, null, null, null); } public function getFile(): ?string @@ -186,4 +217,9 @@ public function getMethod(): ?string return $this->method; } + public function getProperty(): ?string + { + return $this->property; + } + } diff --git a/src/Reflection/InitializerExprTypeResolver.php b/src/Reflection/InitializerExprTypeResolver.php index fc8ad21c17..9fef24a587 100644 --- a/src/Reflection/InitializerExprTypeResolver.php +++ b/src/Reflection/InitializerExprTypeResolver.php @@ -392,6 +392,15 @@ public function getType(Expr $expr, InitializerExprContext $context): Type return new ConstantStringType($context->getTraitName(), true); } + if ($expr instanceof MagicConst\Property) { + $contextProperty = $context->getProperty(); + if ($contextProperty === null) { + return new ConstantStringType(''); + } + + return new ConstantStringType($contextProperty); + } + if ($expr instanceof PropertyFetch && $expr->name instanceof Identifier) { $fetchedOnType = $this->getType($expr->var, $context); if (!$fetchedOnType->hasProperty($expr->name->name)->yes()) { diff --git a/tests/PHPStan/Analyser/nsrt/property-hooks.php b/tests/PHPStan/Analyser/nsrt/property-hooks.php index 4a7f0d9f3a..8e32e4c96d 100644 --- a/tests/PHPStan/Analyser/nsrt/property-hooks.php +++ b/tests/PHPStan/Analyser/nsrt/property-hooks.php @@ -357,3 +357,21 @@ public function doFoo3(): void } } + +class MagicConstants +{ + + public int $i { + get { + assertType("'\$i::get'", __FUNCTION__); + assertType("'PropertyHooksTypes\\\\MagicConstants::\$i::get'", __METHOD__); + assertType("'i'", __PROPERTY__); + } + set { + assertType("'\$i::set'", __FUNCTION__); + assertType("'PropertyHooksTypes\\\\MagicConstants::\$i::set'", __METHOD__); + assertType("'i'", __PROPERTY__); + } + } + +} diff --git a/tests/PHPStan/Reflection/Annotations/DeprecatedAnnotationsTest.php b/tests/PHPStan/Reflection/Annotations/DeprecatedAnnotationsTest.php index 835a08348f..1535cfa6a9 100644 --- a/tests/PHPStan/Reflection/Annotations/DeprecatedAnnotationsTest.php +++ b/tests/PHPStan/Reflection/Annotations/DeprecatedAnnotationsTest.php @@ -369,6 +369,13 @@ public function dataDeprecatedAttributeAbovePropertyHook(): iterable TrinaryLogic::createYes(), 'msg2', ]; + yield [ + \DeprecatedAttributePropertyHooks\Foo::class, + 'm', + 'get', + TrinaryLogic::createYes(), + '$m::get+DeprecatedAttributePropertyHooks\Foo::$m::get+m', + ]; } /** diff --git a/tests/PHPStan/Reflection/Annotations/DeprecatedAttributePhpFunctionFromParserReflectionRuleTest.php b/tests/PHPStan/Reflection/Annotations/DeprecatedAttributePhpFunctionFromParserReflectionRuleTest.php index b1d41db889..a65b08a57b 100644 --- a/tests/PHPStan/Reflection/Annotations/DeprecatedAttributePhpFunctionFromParserReflectionRuleTest.php +++ b/tests/PHPStan/Reflection/Annotations/DeprecatedAttributePhpFunctionFromParserReflectionRuleTest.php @@ -130,6 +130,10 @@ public function testPropertyHookRule(): void 'Deprecated: msg2', 31, ], + [ + 'Deprecated: $m::get+DeprecatedAttributePropertyHooks\Foo::$m::get+m', + 38, + ], ]); } diff --git a/tests/PHPStan/Reflection/Annotations/data/deprecated-attribute-property-hooks.php b/tests/PHPStan/Reflection/Annotations/data/deprecated-attribute-property-hooks.php index 3caf94adf3..0da2fbe4e5 100644 --- a/tests/PHPStan/Reflection/Annotations/data/deprecated-attribute-property-hooks.php +++ b/tests/PHPStan/Reflection/Annotations/data/deprecated-attribute-property-hooks.php @@ -34,4 +34,11 @@ class Foo } } + public int $m { + #[Deprecated(message: __FUNCTION__ . '+' . __METHOD__ . '+' . __PROPERTY__)] + get { + return 1; + } + } + }