Skip to content

Commit

Permalink
Support magic __PROPERTY__ constant in hooks
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Dec 19, 2024
1 parent 4679c62 commit 2688515
Show file tree
Hide file tree
Showing 7 changed files with 91 additions and 10 deletions.
2 changes: 1 addition & 1 deletion src/Analyser/NodeScopeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -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,
);
Expand Down
54 changes: 45 additions & 9 deletions src/Reflection/InitializerExprContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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,
);
}

Expand Down Expand Up @@ -81,6 +87,7 @@ public static function fromClass(string $className, ?string $fileName): self
null,
null,
null,
null,
);
}

Expand All @@ -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
);
}

Expand All @@ -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
);
}

Expand All @@ -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,
);
}

Expand All @@ -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
Expand Down Expand Up @@ -186,4 +217,9 @@ public function getMethod(): ?string
return $this->method;
}

public function getProperty(): ?string
{
return $this->property;
}

}
9 changes: 9 additions & 0 deletions src/Reflection/InitializerExprTypeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {
Expand Down
18 changes: 18 additions & 0 deletions tests/PHPStan/Analyser/nsrt/property-hooks.php
Original file line number Diff line number Diff line change
Expand Up @@ -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__);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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',
];
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,10 @@ public function testPropertyHookRule(): void
'Deprecated: msg2',
31,
],
[
'Deprecated: $m::get+DeprecatedAttributePropertyHooks\Foo::$m::get+m',
38,
],
]);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,11 @@ class Foo
}
}

public int $m {
#[Deprecated(message: __FUNCTION__ . '+' . __METHOD__ . '+' . __PROPERTY__)]
get {
return 1;
}
}

}

0 comments on commit 2688515

Please sign in to comment.