Skip to content

Commit a53d242

Browse files
committed
Readonly properties cannot be unset()
1 parent 73d7b88 commit a53d242

File tree

3 files changed

+162
-0
lines changed

3 files changed

+162
-0
lines changed

src/Rules/Variables/UnsetRule.php

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,45 @@ private function canBeUnset(Node $node, Scope $scope): ?IdentifierRuleError
6969
}
7070

7171
return $this->canBeUnset($node->var, $scope);
72+
} elseif (
73+
$node instanceof Node\Expr\PropertyFetch
74+
&& $node->name instanceof Node\Identifier
75+
) {
76+
$type = $scope->getType($node->var);
77+
foreach ($type->getObjectClassReflections() as $classReflection) {
78+
if ($classReflection->isReadOnly() || $classReflection->isImmutable()) {
79+
return RuleErrorBuilder::message(
80+
sprintf(
81+
'Cannot unset property %s of %s class %s.',
82+
$node->name->name,
83+
$classReflection->isReadOnly() ? 'readonly' : 'immutable',
84+
$type->describe(VerbosityLevel::value()),
85+
),
86+
)
87+
->line($node->getStartLine())
88+
->identifier('unset.readonlyClass')
89+
->build();
90+
}
91+
92+
if (!$classReflection->hasProperty($node->name->name)) {
93+
continue;
94+
}
95+
96+
$propertyReflection = $classReflection->getNativeProperty($node->name->name);
97+
if ($propertyReflection->isReadOnly() || $propertyReflection->isReadOnlyByPhpDoc()) {
98+
return RuleErrorBuilder::message(
99+
sprintf(
100+
'Cannot unset %s property %s of %s.',
101+
$propertyReflection->isReadOnly() ? 'readonly' : '@readonly',
102+
$node->name->name,
103+
$type->describe(VerbosityLevel::value()),
104+
),
105+
)
106+
->line($node->getStartLine())
107+
->identifier('unset.readonlyProperty')
108+
->build();
109+
}
110+
}
72111
}
73112

74113
return null;

tests/PHPStan/Rules/Variables/UnsetRuleTest.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,4 +91,30 @@ public function testBug4565(): void
9191
$this->analyse([__DIR__ . '/../../Analyser/nsrt/bug-4565.php'], []);
9292
}
9393

94+
public function testBug12421(): void
95+
{
96+
$this->analyse([__DIR__ . '/data/bug-12421.php'], [
97+
[
98+
'Cannot unset property y of readonly class Bug12421\NativeReadonlyClass.',
99+
78,
100+
],
101+
[
102+
'Cannot unset readonly property y of Bug12421\NativeReadonlyProperty.',
103+
82,
104+
],
105+
[
106+
'Cannot unset property y of immutable class Bug12421\PhpdocReadonlyClass.',
107+
86,
108+
],
109+
[
110+
'Cannot unset @readonly property y of Bug12421\PhpdocReadonlyProperty.',
111+
90,
112+
],
113+
[
114+
'Cannot unset property y of immutable class Bug12421\PhpdocImmutableClass.',
115+
94,
116+
],
117+
]);
118+
}
119+
94120
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
<?php // lint >= 8.2
2+
3+
namespace Bug12421;
4+
5+
readonly class NativeReadonlyClass
6+
{
7+
public Y $y;
8+
9+
public function __construct()
10+
{
11+
$this->y = new Y();
12+
}
13+
}
14+
15+
class NativeReadonlyProperty
16+
{
17+
public readonly Y $y;
18+
19+
public function __construct()
20+
{
21+
$this->y = new Y();
22+
}
23+
}
24+
25+
/** @readonly */
26+
class PhpdocReadonlyClass
27+
{
28+
public Y $y;
29+
30+
public function __construct()
31+
{
32+
$this->y = new Y();
33+
}
34+
}
35+
36+
class PhpdocReadonlyProperty
37+
{
38+
/** @readonly */
39+
public Y $y;
40+
41+
public function __construct()
42+
{
43+
$this->y = new Y();
44+
}
45+
}
46+
47+
/** @immutable */
48+
class PhpdocImmutableClass
49+
{
50+
public Y $y;
51+
52+
public function __construct()
53+
{
54+
$this->y = new Y();
55+
}
56+
}
57+
58+
class RegularProperty
59+
{
60+
public Y $y;
61+
62+
public function __construct()
63+
{
64+
$this->y = new Y();
65+
}
66+
}
67+
68+
class Y
69+
{
70+
}
71+
72+
function doFoo() {
73+
$x = new RegularProperty();
74+
unset($x->y);
75+
var_dump($x->y);
76+
77+
$x = new NativeReadonlyClass();
78+
unset($x->y);
79+
var_dump($x->y);
80+
81+
$x = new NativeReadonlyProperty();
82+
unset($x->y);
83+
var_dump($x->y);
84+
85+
$x = new PhpdocReadonlyClass();
86+
unset($x->y);
87+
var_dump($x->y);
88+
89+
$x = new PhpdocReadonlyProperty();
90+
unset($x->y);
91+
var_dump($x->y);
92+
93+
$x = new PhpdocImmutableClass();
94+
unset($x->y);
95+
var_dump($x->y);
96+
}
97+

0 commit comments

Comments
 (0)