Skip to content

Commit e98565e

Browse files
committed
Minor property hook improvements
1 parent 1bdf644 commit e98565e

File tree

7 files changed

+41
-43
lines changed

7 files changed

+41
-43
lines changed

psalm-baseline.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2545,6 +2545,13 @@
25452545
<code><![CDATA[$this_var_id]]></code>
25462546
</RiskyTruthyFalsyComparison>
25472547
</file>
2548+
<file src="src/Psalm/Storage/PropertyHookStorage.php">
2549+
<PossiblyUnusedProperty>
2550+
<code><![CDATA[$by_ref]]></code>
2551+
<code><![CDATA[$is_final]]></code>
2552+
<code><![CDATA[$location]]></code>
2553+
</PossiblyUnusedProperty>
2554+
</file>
25482555
<file src="src/Psalm/Type.php">
25492556
<RiskyTruthyFalsyComparison>
25502557
<code><![CDATA[!$namespace]]></code>

src/Psalm/Internal/Analyzer/ClassAnalyzer.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1128,7 +1128,7 @@ private function checkPropertyInitialization(
11281128
$uninitialized_variables[] = '$this->' . $property_name;
11291129
$uninitialized_properties[$property_class_name . '::$' . $property_name] = $property;
11301130

1131-
if ($property->type) {
1131+
if ($property->type && !$property->hook_get) {
11321132
// Complain about all natively typed properties and all non-mixed docblock typed properties
11331133
if (!$property->type->from_docblock || !$property->type->isMixed()) {
11341134
$uninitialized_typed_properties[$property_class_name . '::$' . $property_name] = $property;

src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/InstancePropertyAssignmentAnalyzer.php

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,6 @@
6767
use Psalm\Node\VirtualIdentifier;
6868
use Psalm\Plugin\EventHandler\Event\AddRemoveTaintsEvent;
6969
use Psalm\Storage\ClassLikeStorage;
70-
use Psalm\Storage\PropertyHookStorage;
7170
use Psalm\Storage\PropertyStorage;
7271
use Psalm\Type;
7372
use Psalm\Type\Atomic;
@@ -80,7 +79,6 @@
8079
use Psalm\Type\Union;
8180
use UnexpectedValueException;
8281

83-
use function array_any;
8482
use function array_diff;
8583
use function array_merge;
8684
use function array_pop;
@@ -992,10 +990,8 @@ private static function analyzeAtomicAssignment(
992990
$interface_property = $stmt->name instanceof PhpParser\Node\Identifier
993991
? $interface_storage->properties[$stmt->name->name] ?? null
994992
: null;
995-
$has_set_hook = $codebase->analysis_php_version_id >= 8_04_00 && array_any(
996-
$interface_property?->hooks ?? [],
997-
static fn(PropertyHookStorage $hook) => $hook->name === 'set',
998-
);
993+
$has_set_hook = $codebase->analysis_php_version_id >= 8_04_00
994+
&& $interface_property?->hook_set !== null;
999995

1000996
if (!$class_exists && !$has_set_hook) {
1001997
if (IssueBuffer::accepts(

src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/AtomicPropertyFetchAnalyzer.php

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@
4848
use Psalm\Node\VirtualIdentifier;
4949
use Psalm\Plugin\EventHandler\Event\AddRemoveTaintsEvent;
5050
use Psalm\Storage\ClassLikeStorage;
51-
use Psalm\Storage\PropertyHookStorage;
5251
use Psalm\Type;
5352
use Psalm\Type\Atomic;
5453
use Psalm\Type\Atomic\TEnumCase;
@@ -64,7 +63,6 @@
6463
use Psalm\Type\Atomic\TTemplateParam;
6564
use Psalm\Type\Union;
6665

67-
use function array_any;
6866
use function array_diff;
6967
use function array_filter;
7068
use function array_keys;
@@ -1179,10 +1177,8 @@ private static function handleNonExistentClass(
11791177
$interface_property = $stmt->name instanceof PhpParser\Node\Identifier
11801178
? $interface_storage->properties[$stmt->name->name] ?? null
11811179
: null;
1182-
$has_get_hook = $codebase->analysis_php_version_id >= 8_04_00 && array_any(
1183-
$interface_property?->hooks ?? [],
1184-
static fn(PropertyHookStorage $hook) => $hook->name === 'get',
1185-
);
1180+
$has_get_hook = $codebase->analysis_php_version_id >= 8_04_00 &&
1181+
$interface_property?->hook_get !== null;
11861182

11871183
if (!$class_exists && !$is_enum_interface && !$has_get_hook) {
11881184
if (IssueBuffer::accepts(

src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeNodeScanner.php

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,6 @@
8080
use function assert;
8181
use function count;
8282
use function implode;
83-
use function in_array;
8483
use function ltrim;
8584
use function preg_match;
8685
use function preg_split;
@@ -1811,17 +1810,25 @@ private function visitPropertyDeclaration(
18111810
// Process property hooks
18121811
foreach ($stmt->hooks as $hook) {
18131812
$hook_name = strtolower($hook->name->toString());
1814-
if (in_array($hook_name, ['get', 'set'], true)) {
1815-
$hook_storage = new PropertyHookStorage($hook_name);
1816-
$hook_storage->is_final = $hook->isFinal();
1817-
$hook_storage->by_ref = $hook->byRef;
1818-
$hook_storage->location = new CodeLocation(
1819-
$this->file_scanner,
1820-
$hook,
1821-
null,
1822-
true,
1813+
if ($hook_name === 'get'
1814+
|| $hook_name === 'set'
1815+
) {
1816+
$hook_storage = new PropertyHookStorage(
1817+
$hook_name === 'get',
1818+
$hook->isFinal(),
1819+
$hook->byRef,
1820+
new CodeLocation(
1821+
$this->file_scanner,
1822+
$hook,
1823+
null,
1824+
true,
1825+
),
18231826
);
1824-
$property_storage->hooks[$hook_name] = $hook_storage;
1827+
if ($hook_storage->is_get) {
1828+
$property_storage->hook_get = $hook_storage;
1829+
} else {
1830+
$property_storage->hook_set = $hook_storage;
1831+
}
18251832
} else {
18261833
$storage->docblock_issues[] = new ParseError(
18271834
'Property hooks must be either "get" or "set"',
@@ -1832,7 +1839,7 @@ private function visitPropertyDeclaration(
18321839

18331840
// Validate interface properties
18341841
if ($storage->is_interface && $this->codebase->analysis_php_version_id >= 8_04_00) {
1835-
if (empty($property_storage->hooks)) {
1842+
if (!$property_storage->hook_get && !$property_storage->hook_set) {
18361843
$storage->docblock_issues[] = new ParseError(
18371844
'Interface properties must have at least one hook',
18381845
new CodeLocation($this->file_scanner, $stmt, null, true),

src/Psalm/Storage/PropertyHookStorage.php

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,24 +8,18 @@
88

99
/**
1010
* Storage for property hooks ('get' & 'set') introduced in PHP 8.4
11+
*
12+
* @psalm-immutable
1113
*/
1214
final class PropertyHookStorage
1315
{
1416
use UnserializeMemoryUsageSuppressionTrait;
1517

16-
public bool $is_final = false;
17-
18-
/**
19-
* Whether the hook returns by reference
20-
*/
21-
public bool $by_ref = false;
22-
23-
public ?CodeLocation $location = null;
24-
25-
/**
26-
* @param 'get'|'set' $name
27-
*/
28-
public function __construct(public string $name)
29-
{
18+
public function __construct(
19+
public bool $is_get,
20+
public bool $is_final,
21+
public bool $by_ref,
22+
public ?CodeLocation $location,
23+
) {
3024
}
3125
}

src/Psalm/Storage/PropertyStorage.php

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,8 @@ final class PropertyStorage implements HasAttributesInterface
6767

6868
public ?string $description = null;
6969

70-
/**
71-
* @var array<string, PropertyHookStorage>
72-
*/
73-
public array $hooks = [];
70+
public ?PropertyHookStorage $hook_get = null;
71+
public ?PropertyHookStorage $hook_set = null;
7472

7573
public function getInfo(): string
7674
{

0 commit comments

Comments
 (0)