Skip to content

Commit 2688515

Browse files
committed
Support magic __PROPERTY__ constant in hooks
1 parent 4679c62 commit 2688515

File tree

7 files changed

+91
-10
lines changed

7 files changed

+91
-10
lines changed

src/Analyser/NodeScopeResolver.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1988,7 +1988,7 @@ static function (Node $node, Scope $scope) use ($nodeCallback): void {
19881988
private function getDeprecatedAttribute(Scope $scope, Node\Stmt\Function_|Node\Stmt\ClassMethod|Node\PropertyHook $stmt): array
19891989
{
19901990
$initializerExprContext = InitializerExprContext::fromStubParameter(
1991-
null,
1991+
$scope->isInClass() ? $scope->getClassReflection()->getName() : null,
19921992
$scope->getFile(),
19931993
$stmt,
19941994
);

src/Reflection/InitializerExprContext.php

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
use PHPStan\BetterReflection\Reflection\Adapter\ReflectionFunction;
1010
use PHPStan\BetterReflection\Reflection\Adapter\ReflectionParameter;
1111
use PHPStan\BetterReflection\Reflection\ReflectionConstant;
12+
use PHPStan\Parser\PropertyHookNameVisitor;
13+
use PHPStan\Reflection\Php\PhpMethodFromParserNodeReflection;
1214
use PHPStan\ShouldNotHappenException;
1315
use function array_slice;
1416
use function count;
@@ -32,21 +34,25 @@ private function __construct(
3234
private ?string $traitName,
3335
private ?string $function,
3436
private ?string $method,
37+
private ?string $property,
3538
)
3639
{
3740
}
3841

3942
public static function fromScope(Scope $scope): self
4043
{
44+
$function = $scope->getFunction();
45+
4146
return new self(
4247
$scope->getFile(),
4348
$scope->getNamespace(),
4449
$scope->isInClass() ? $scope->getClassReflection()->getName() : null,
4550
$scope->isInTrait() ? $scope->getTraitReflection()->getName() : null,
46-
$scope->isInAnonymousFunction() ? '{closure}' : ($scope->getFunction() !== null ? $scope->getFunction()->getName() : null),
47-
$scope->isInAnonymousFunction() ? '{closure}' : ($scope->getFunction() instanceof MethodReflection
48-
? sprintf('%s::%s', $scope->getFunction()->getDeclaringClass()->getName(), $scope->getFunction()->getName())
49-
: ($scope->getFunction() instanceof FunctionReflection ? $scope->getFunction()->getName() : null)),
51+
$scope->isInAnonymousFunction() ? '{closure}' : ($function !== null ? $function->getName() : null),
52+
$scope->isInAnonymousFunction() ? '{closure}' : ($function instanceof MethodReflection
53+
? sprintf('%s::%s', $function->getDeclaringClass()->getName(), $function->getName())
54+
: ($function instanceof FunctionReflection ? $function->getName() : null)),
55+
$function instanceof PhpMethodFromParserNodeReflection && $function->isPropertyHook() ? $function->getHookedPropertyName() : null,
5056
);
5157
}
5258

@@ -81,6 +87,7 @@ public static function fromClass(string $className, ?string $fileName): self
8187
null,
8288
null,
8389
null,
90+
null,
8491
);
8592
}
8693

@@ -96,6 +103,7 @@ public static function fromReflectionParameter(ReflectionParameter $parameter):
96103
null,
97104
$declaringFunction->getName(),
98105
$declaringFunction->getName(),
106+
null, // Property hook parameter cannot have a default value. fromReflectionParameter is only used for that
99107
);
100108
}
101109

@@ -110,6 +118,7 @@ public static function fromReflectionParameter(ReflectionParameter $parameter):
110118
$betterReflection->getDeclaringClass()->isTrait() ? $betterReflection->getDeclaringClass()->getName() : null,
111119
$declaringFunction->getName(),
112120
sprintf('%s::%s', $declaringFunction->getDeclaringClass()->getName(), $declaringFunction->getName()),
121+
null, // Property hook parameter cannot have a default value. fromReflectionParameter is only used for that
113122
);
114123
}
115124

@@ -127,15 +136,36 @@ public static function fromStubParameter(
127136
$namespace = self::parseNamespace($function->namespacedName->toString());
128137
}
129138
}
139+
140+
$functionName = null;
141+
$propertyName = null;
142+
if ($function instanceof Function_ && $function->namespacedName !== null) {
143+
$functionName = $function->namespacedName->toString();
144+
} elseif ($function instanceof ClassMethod) {
145+
$functionName = $function->name->toString();
146+
} elseif ($function instanceof PropertyHook) {
147+
$propertyName = $function->getAttribute(PropertyHookNameVisitor::ATTRIBUTE_NAME);
148+
$functionName = sprintf('$%s::%s', $propertyName, $function->name->toString());
149+
}
150+
151+
$methodName = null;
152+
if ($function instanceof ClassMethod && $className !== null) {
153+
$methodName = sprintf('%s::%s', $className, $function->name->toString());
154+
} elseif ($function instanceof PropertyHook) {
155+
$propertyName = $function->getAttribute(PropertyHookNameVisitor::ATTRIBUTE_NAME);
156+
$methodName = sprintf('%s::$%s::%s', $className, $propertyName, $function->name->toString());
157+
} elseif ($function instanceof Function_ && $function->namespacedName !== null) {
158+
$methodName = $function->namespacedName->toString();
159+
}
160+
130161
return new self(
131162
$stubFile,
132163
$namespace,
133164
$className,
134165
null,
135-
$function instanceof Function_ && $function->namespacedName !== null ? $function->namespacedName->toString() : ($function instanceof ClassMethod ? $function->name->toString() : null),
136-
$function instanceof ClassMethod && $className !== null
137-
? sprintf('%s::%s', $className, $function->name->toString())
138-
: ($function instanceof Function_ && $function->namespacedName !== null ? $function->namespacedName->toString() : null),
166+
$functionName,
167+
$methodName,
168+
$propertyName,
139169
);
140170
}
141171

@@ -148,12 +178,13 @@ public static function fromGlobalConstant(ReflectionConstant $constant): self
148178
null,
149179
null,
150180
null,
181+
null,
151182
);
152183
}
153184

154185
public static function createEmpty(): self
155186
{
156-
return new self(null, null, null, null, null, null);
187+
return new self(null, null, null, null, null, null, null);
157188
}
158189

159190
public function getFile(): ?string
@@ -186,4 +217,9 @@ public function getMethod(): ?string
186217
return $this->method;
187218
}
188219

220+
public function getProperty(): ?string
221+
{
222+
return $this->property;
223+
}
224+
189225
}

src/Reflection/InitializerExprTypeResolver.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,15 @@ public function getType(Expr $expr, InitializerExprContext $context): Type
392392
return new ConstantStringType($context->getTraitName(), true);
393393
}
394394

395+
if ($expr instanceof MagicConst\Property) {
396+
$contextProperty = $context->getProperty();
397+
if ($contextProperty === null) {
398+
return new ConstantStringType('');
399+
}
400+
401+
return new ConstantStringType($contextProperty);
402+
}
403+
395404
if ($expr instanceof PropertyFetch && $expr->name instanceof Identifier) {
396405
$fetchedOnType = $this->getType($expr->var, $context);
397406
if (!$fetchedOnType->hasProperty($expr->name->name)->yes()) {

tests/PHPStan/Analyser/nsrt/property-hooks.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,3 +357,21 @@ public function doFoo3(): void
357357
}
358358

359359
}
360+
361+
class MagicConstants
362+
{
363+
364+
public int $i {
365+
get {
366+
assertType("'\$i::get'", __FUNCTION__);
367+
assertType("'PropertyHooksTypes\\\\MagicConstants::\$i::get'", __METHOD__);
368+
assertType("'i'", __PROPERTY__);
369+
}
370+
set {
371+
assertType("'\$i::set'", __FUNCTION__);
372+
assertType("'PropertyHooksTypes\\\\MagicConstants::\$i::set'", __METHOD__);
373+
assertType("'i'", __PROPERTY__);
374+
}
375+
}
376+
377+
}

tests/PHPStan/Reflection/Annotations/DeprecatedAnnotationsTest.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,13 @@ public function dataDeprecatedAttributeAbovePropertyHook(): iterable
369369
TrinaryLogic::createYes(),
370370
'msg2',
371371
];
372+
yield [
373+
\DeprecatedAttributePropertyHooks\Foo::class,
374+
'm',
375+
'get',
376+
TrinaryLogic::createYes(),
377+
'$m::get+DeprecatedAttributePropertyHooks\Foo::$m::get+m',
378+
];
372379
}
373380

374381
/**

tests/PHPStan/Reflection/Annotations/DeprecatedAttributePhpFunctionFromParserReflectionRuleTest.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,10 @@ public function testPropertyHookRule(): void
130130
'Deprecated: msg2',
131131
31,
132132
],
133+
[
134+
'Deprecated: $m::get+DeprecatedAttributePropertyHooks\Foo::$m::get+m',
135+
38,
136+
],
133137
]);
134138
}
135139

tests/PHPStan/Reflection/Annotations/data/deprecated-attribute-property-hooks.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,11 @@ class Foo
3434
}
3535
}
3636

37+
public int $m {
38+
#[Deprecated(message: __FUNCTION__ . '+' . __METHOD__ . '+' . __PROPERTY__)]
39+
get {
40+
return 1;
41+
}
42+
}
43+
3744
}

0 commit comments

Comments
 (0)