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