3
3
namespace PHPStan \Rules \Variables ;
4
4
5
5
use PhpParser \Node ;
6
- use PhpParser \Node \Expr ;
7
6
use PHPStan \Analyser \Scope ;
8
- use PHPStan \Rules \Properties \PropertyDescriptor ;
9
- use PHPStan \Rules \Properties \PropertyReflectionFinder ;
10
- use PHPStan \Rules \RuleError ;
11
- use PHPStan \Rules \RuleErrorBuilder ;
12
- use PHPStan \Type \NullType ;
13
- use PHPStan \Type \Type ;
14
- use PHPStan \Type \VerbosityLevel ;
7
+ use PHPStan \Rules \IssetCheck ;
15
8
16
9
/**
17
10
* @implements \PHPStan\Rules\Rule<\PhpParser\Node\Expr>
18
11
*/
19
12
class NullCoalesceRule implements \PHPStan \Rules \Rule
20
13
{
21
14
22
- /** @var \PHPStan\Rules\Properties\PropertyDescriptor */
23
- private $ propertyDescriptor ;
15
+ /** @var IssetCheck */
16
+ private $ issetCheck ;
24
17
25
- /** @var \PHPStan\Rules\Properties\PropertyReflectionFinder */
26
- private $ propertyReflectionFinder ;
27
-
28
- public function __construct (
29
- PropertyDescriptor $ propertyDescriptor ,
30
- PropertyReflectionFinder $ propertyReflectionFinder
31
- )
18
+ public function __construct (IssetCheck $ issetCheck )
32
19
{
33
- $ this ->propertyDescriptor = $ propertyDescriptor ;
34
- $ this ->propertyReflectionFinder = $ propertyReflectionFinder ;
20
+ $ this ->issetCheck = $ issetCheck ;
35
21
}
36
22
37
23
public function getNodeType (): string
@@ -42,9 +28,9 @@ public function getNodeType(): string
42
28
public function processNode (Node $ node , Scope $ scope ): array
43
29
{
44
30
if ($ node instanceof Node \Expr \BinaryOp \Coalesce) {
45
- $ error = $ this ->canBeCoalesced ($ node ->left , $ scope , '?? ' );
31
+ $ error = $ this ->issetCheck -> check ($ node ->left , $ scope , '?? ' );
46
32
} elseif ($ node instanceof Node \Expr \AssignOp \Coalesce) {
47
- $ error = $ this ->canBeCoalesced ($ node ->var , $ scope , '??= ' );
33
+ $ error = $ this ->issetCheck -> check ($ node ->var , $ scope , '??= ' );
48
34
} else {
49
35
return [];
50
36
}
@@ -56,123 +42,4 @@ public function processNode(Node $node, Scope $scope): array
56
42
return [$ error ];
57
43
}
58
44
59
- private function canBeCoalesced (Expr $ expr , Scope $ scope , string $ action , ?RuleError $ error = null ): ?RuleError
60
- {
61
- if ($ expr instanceof Node \Expr \Variable && is_string ($ expr ->name )) {
62
-
63
- $ hasVariable = $ scope ->hasVariableType ($ expr ->name );
64
-
65
- if ($ hasVariable ->no ()) {
66
- return $ error ?? RuleErrorBuilder::message (
67
- sprintf ('Variable $%s on left side of %s is never defined. ' , $ expr ->name , $ action )
68
- )->build ();
69
- }
70
-
71
- $ variableType = $ scope ->getType ($ expr );
72
-
73
- if ($ hasVariable ->maybe ()) {
74
- return null ;
75
- }
76
-
77
- if ($ hasVariable ->yes ()) {
78
- return $ error ?? $ this ->generateError (
79
- $ variableType ,
80
- sprintf ('Variable $%s on left side of %s always exists and ' , $ expr ->name , $ action )
81
- );
82
- }
83
-
84
- } elseif ($ expr instanceof Node \Expr \ArrayDimFetch && $ expr ->dim !== null ) {
85
-
86
- $ type = $ scope ->getType ($ expr ->var );
87
- $ dimType = $ scope ->getType ($ expr ->dim );
88
- $ hasOffsetValue = $ type ->hasOffsetValueType ($ dimType );
89
- if (!$ type ->isOffsetAccessible ()->yes ()) {
90
- return $ error ;
91
- }
92
-
93
- if ($ hasOffsetValue ->no ()) {
94
- return $ error ?? RuleErrorBuilder::message (
95
- sprintf (
96
- 'Offset %s on %s on left side of %s does not exist. ' ,
97
- $ dimType ->describe (VerbosityLevel::value ()),
98
- $ type ->describe (VerbosityLevel::value ()),
99
- $ action
100
- )
101
- )->build ();
102
- }
103
-
104
- if ($ hasOffsetValue ->maybe ()) {
105
- return null ;
106
- }
107
-
108
- // If offset is cannot be null, store this error message and see if one of the earlier offsets is.
109
- // E.g. $array['a']['b']['c'] ?? null; is a valid coalesce if a OR b or C might be null.
110
- if ($ hasOffsetValue ->yes ()) {
111
-
112
- $ error = $ error ?? $ this ->generateError ($ type ->getOffsetValueType ($ dimType ), sprintf (
113
- 'Offset %s on %s on left side of %s always exists and ' ,
114
- $ dimType ->describe (VerbosityLevel::value ()),
115
- $ type ->describe (VerbosityLevel::value ()),
116
- $ action
117
- ));
118
-
119
- if ($ error !== null ) {
120
- return $ this ->canBeCoalesced ($ expr ->var , $ scope , $ action , $ error );
121
- }
122
- }
123
-
124
- // Has offset, it is nullable
125
- return null ;
126
-
127
- } elseif ($ expr instanceof Node \Expr \PropertyFetch || $ expr instanceof Node \Expr \StaticPropertyFetch) {
128
-
129
- $ propertyReflection = $ this ->propertyReflectionFinder ->findPropertyReflectionFromNode ($ expr , $ scope );
130
-
131
- if ($ propertyReflection === null ) {
132
- return null ;
133
- }
134
-
135
- $ propertyDescription = $ this ->propertyDescriptor ->describeProperty ($ propertyReflection , $ expr );
136
- $ propertyType = $ propertyReflection ->getWritableType ();
137
-
138
- $ error = $ error ?? $ this ->generateError (
139
- $ propertyReflection ->getWritableType (),
140
- sprintf ('%s (%s) on left side of %s ' , $ propertyDescription , $ propertyType ->describe (VerbosityLevel::typeOnly ()), $ action )
141
- );
142
-
143
- if ($ error !== null ) {
144
- if ($ expr instanceof Node \Expr \PropertyFetch) {
145
- return $ this ->canBeCoalesced ($ expr ->var , $ scope , $ action , $ error );
146
- }
147
-
148
- if ($ expr ->class instanceof Expr) {
149
- return $ this ->canBeCoalesced ($ expr ->class , $ scope , $ action , $ error );
150
- }
151
- }
152
-
153
- return $ error ;
154
- }
155
-
156
- return $ error ?? $ this ->generateError ($ scope ->getType ($ expr ), sprintf ('Left side of %s ' , $ action ));
157
- }
158
-
159
- private function generateError (Type $ type , string $ message ): ?RuleError
160
- {
161
- $ nullType = new NullType ();
162
-
163
- if ($ type ->equals ($ nullType )) {
164
- return RuleErrorBuilder::message (
165
- sprintf ('%s is always null. ' , $ message )
166
- )->build ();
167
- }
168
-
169
- if ($ type ->isSuperTypeOf ($ nullType )->no ()) {
170
- return RuleErrorBuilder::message (
171
- sprintf ('%s is not nullable. ' , $ message )
172
- )->build ();
173
- }
174
-
175
- return null ;
176
- }
177
-
178
45
}
0 commit comments