4
4
5
5
use PhpParser \Node ;
6
6
use PhpParser \Node \Expr ;
7
+ use PHPStan \Analyser \NameScope ;
7
8
use PHPStan \Analyser \Scope ;
8
9
use PHPStan \Node \Expr \GetOffsetValueTypeExpr ;
10
+ use PHPStan \PhpDoc \NameScopeAlreadyBeingCreatedException ;
9
11
use PHPStan \PhpDoc \Tag \VarTag ;
12
+ use PHPStan \PhpDoc \TypeNodeResolver ;
10
13
use PHPStan \Rules \IdentifierRuleError ;
11
14
use PHPStan \Rules \RuleErrorBuilder ;
12
15
use PHPStan \Type \ArrayType ;
16
+ use PHPStan \Type \FileTypeMapper ;
13
17
use PHPStan \Type \Generic \GenericObjectType ;
14
18
use PHPStan \Type \MixedType ;
15
19
use PHPStan \Type \ObjectType ;
24
28
final class VarTagTypeRuleHelper
25
29
{
26
30
27
- public function __construct (private bool $ checkTypeAgainstPhpDocType , private bool $ strictWideningCheck )
31
+ public function __construct (
32
+ private TypeNodeResolver $ typeNodeResolver ,
33
+ private FileTypeMapper $ fileTypeMapper ,
34
+ private bool $ checkTypeAgainstPhpDocType ,
35
+ private bool $ strictWideningCheck ,
36
+ )
28
37
{
29
38
}
30
39
@@ -76,7 +85,7 @@ public function checkExprType(Scope $scope, Node\Expr $expr, Type $varTagType):
76
85
$ errors = [];
77
86
$ exprNativeType = $ scope ->getNativeType ($ expr );
78
87
$ containsPhpStanType = $ this ->containsPhpStanType ($ varTagType );
79
- if ($ this ->shouldVarTagTypeBeReported ($ expr , $ exprNativeType , $ varTagType )) {
88
+ if ($ this ->shouldVarTagTypeBeReported ($ scope , $ expr , $ exprNativeType , $ varTagType )) {
80
89
$ verbosity = VerbosityLevel::getRecommendedLevelByType ($ exprNativeType , $ varTagType );
81
90
$ errors [] = RuleErrorBuilder::message (sprintf (
82
91
'PHPDoc tag @var with type %s is not subtype of native type %s. ' ,
@@ -86,7 +95,7 @@ public function checkExprType(Scope $scope, Node\Expr $expr, Type $varTagType):
86
95
} else {
87
96
$ exprType = $ scope ->getType ($ expr );
88
97
if (
89
- $ this ->shouldVarTagTypeBeReported ($ expr , $ exprType , $ varTagType )
98
+ $ this ->shouldVarTagTypeBeReported ($ scope , $ expr , $ exprType , $ varTagType )
90
99
&& ($ this ->checkTypeAgainstPhpDocType || $ containsPhpStanType )
91
100
) {
92
101
$ verbosity = VerbosityLevel::getRecommendedLevelByType ($ exprType , $ varTagType );
@@ -127,22 +136,22 @@ private function containsPhpStanType(Type $type): bool
127
136
return false ;
128
137
}
129
138
130
- private function shouldVarTagTypeBeReported (Node \Expr $ expr , Type $ type , Type $ varTagType ): bool
139
+ private function shouldVarTagTypeBeReported (Scope $ scope , Node \Expr $ expr , Type $ type , Type $ varTagType ): bool
131
140
{
132
141
if ($ expr instanceof Expr \Array_) {
133
142
if ($ expr ->items === []) {
134
143
$ type = new ArrayType (new MixedType (), new MixedType ());
135
144
}
136
145
137
- return $ type -> isSuperTypeOf ( $ varTagType)-> no ( );
146
+ return ! $ this -> isAtLeastMaybeSuperTypeOfVarType ( $ scope , $ type , $ varTagType );
138
147
}
139
148
140
149
if ($ expr instanceof Expr \ConstFetch) {
141
- return $ type -> isSuperTypeOf ( $ varTagType)-> no ( );
150
+ return ! $ this -> isAtLeastMaybeSuperTypeOfVarType ( $ scope , $ type , $ varTagType );
142
151
}
143
152
144
153
if ($ expr instanceof Node \Scalar) {
145
- return $ type -> isSuperTypeOf ( $ varTagType)-> no ( );
154
+ return ! $ this -> isAtLeastMaybeSuperTypeOfVarType ( $ scope , $ type , $ varTagType );
146
155
}
147
156
148
157
if ($ expr instanceof Expr \New_) {
@@ -151,42 +160,87 @@ private function shouldVarTagTypeBeReported(Node\Expr $expr, Type $type, Type $v
151
160
}
152
161
}
153
162
154
- return $ this ->checkType ($ type , $ varTagType );
163
+ return $ this ->checkType ($ scope , $ type , $ varTagType );
155
164
}
156
165
157
- private function checkType (Type $ type , Type $ varTagType , int $ depth = 0 ): bool
166
+ private function checkType (Scope $ scope , Type $ type , Type $ varTagType , int $ depth = 0 ): bool
158
167
{
159
168
if ($ this ->strictWideningCheck ) {
160
- return !$ type -> isSuperTypeOf ( $ varTagType)-> yes ( );
169
+ return !$ this -> isSuperTypeOfVarType ( $ scope , $ type , $ varTagType );
161
170
}
162
171
163
172
if ($ type ->isConstantArray ()->yes ()) {
164
173
if ($ type ->isIterableAtLeastOnce ()->no ()) {
165
174
$ type = new ArrayType (new MixedType (), new MixedType ());
166
- return $ type -> isSuperTypeOf ( $ varTagType)-> no ( );
175
+ return ! $ this -> isAtLeastMaybeSuperTypeOfVarType ( $ scope , $ type , $ varTagType );
167
176
}
168
177
}
169
178
170
179
if ($ type ->isIterable ()->yes () && $ varTagType ->isIterable ()->yes ()) {
171
- if ($ type -> isSuperTypeOf ( $ varTagType)-> no ( )) {
180
+ if (! $ this -> isAtLeastMaybeSuperTypeOfVarType ( $ scope , $ type , $ varTagType )) {
172
181
return true ;
173
182
}
174
183
175
184
$ innerType = $ type ->getIterableValueType ();
176
185
$ innerVarTagType = $ varTagType ->getIterableValueType ();
177
186
178
187
if ($ type ->equals ($ innerType ) || $ varTagType ->equals ($ innerVarTagType )) {
179
- return !$ innerType -> isSuperTypeOf ( $ innerVarTagType)-> yes ( );
188
+ return !$ this -> isSuperTypeOfVarType ( $ scope , $ innerType , $ innerVarTagType );
180
189
}
181
190
182
- return $ this ->checkType ($ innerType , $ innerVarTagType , $ depth + 1 );
191
+ return $ this ->checkType ($ scope , $ innerType , $ innerVarTagType , $ depth + 1 );
183
192
}
184
193
185
- if ($ type ->isConstantValue ()->yes () && $ depth === 0 ) {
186
- return $ type -> isSuperTypeOf ( $ varTagType)-> no ( );
194
+ if ($ depth === 0 && $ type ->isConstantValue ()->yes ()) {
195
+ return ! $ this -> isAtLeastMaybeSuperTypeOfVarType ( $ scope , $ type , $ varTagType );
187
196
}
188
197
189
- return !$ type ->isSuperTypeOf ($ varTagType )->yes ();
198
+ return !$ this ->isSuperTypeOfVarType ($ scope , $ type , $ varTagType );
199
+ }
200
+
201
+ private function isSuperTypeOfVarType (Scope $ scope , Type $ type , Type $ varTagType ): bool
202
+ {
203
+ if ($ type ->isSuperTypeOf ($ varTagType )->yes ()) {
204
+ return true ;
205
+ }
206
+
207
+ try {
208
+ $ type = $ this ->typeNodeResolver ->resolve ($ type ->toPhpDocNode (), $ this ->createNameScope ($ scope ));
209
+ } catch (NameScopeAlreadyBeingCreatedException ) {
210
+ return true ;
211
+ }
212
+
213
+ return $ type ->isSuperTypeOf ($ varTagType )->yes ();
214
+ }
215
+
216
+ private function isAtLeastMaybeSuperTypeOfVarType (Scope $ scope , Type $ type , Type $ varTagType ): bool
217
+ {
218
+ if (!$ type ->isSuperTypeOf ($ varTagType )->no ()) {
219
+ return true ;
220
+ }
221
+
222
+ try {
223
+ $ type = $ this ->typeNodeResolver ->resolve ($ type ->toPhpDocNode (), $ this ->createNameScope ($ scope ));
224
+ } catch (NameScopeAlreadyBeingCreatedException ) {
225
+ return true ;
226
+ }
227
+
228
+ return !$ type ->isSuperTypeOf ($ varTagType )->no ();
229
+ }
230
+
231
+ /**
232
+ * @throws NameScopeAlreadyBeingCreatedException
233
+ */
234
+ private function createNameScope (Scope $ scope ): NameScope
235
+ {
236
+ $ function = $ scope ->getFunction ();
237
+
238
+ return $ this ->fileTypeMapper ->getNameScope (
239
+ $ scope ->getFile (),
240
+ $ scope ->isInClass () ? $ scope ->getClassReflection ()->getName () : null ,
241
+ $ scope ->isInTrait () ? $ scope ->getTraitReflection ()->getName () : null ,
242
+ $ function !== null ? $ function ->getName () : null ,
243
+ );
190
244
}
191
245
192
246
}
0 commit comments