77use PHPStan \Analyser \NameScope ;
88use PHPStan \Analyser \Scope ;
99use PHPStan \Node \Expr \GetOffsetValueTypeExpr ;
10+ use PHPStan \PhpDoc \NameScopeAlreadyBeingCreatedException ;
1011use PHPStan \PhpDoc \Tag \VarTag ;
1112use PHPStan \PhpDoc \TypeNodeResolver ;
1213use PHPStan \Rules \IdentifierRuleError ;
1314use PHPStan \Rules \RuleErrorBuilder ;
1415use PHPStan \Type \ArrayType ;
16+ use PHPStan \Type \FileTypeMapper ;
1517use PHPStan \Type \Generic \GenericObjectType ;
1618use PHPStan \Type \MixedType ;
1719use PHPStan \Type \ObjectType ;
@@ -28,6 +30,7 @@ final class VarTagTypeRuleHelper
2830
2931 public function __construct (
3032 private TypeNodeResolver $ typeNodeResolver ,
33+ private FileTypeMapper $ fileTypeMapper ,
3134 private bool $ checkTypeAgainstPhpDocType ,
3235 private bool $ strictWideningCheck ,
3336 )
@@ -82,7 +85,7 @@ public function checkExprType(Scope $scope, Node\Expr $expr, Type $varTagType):
8285 $ errors = [];
8386 $ exprNativeType = $ scope ->getNativeType ($ expr );
8487 $ containsPhpStanType = $ this ->containsPhpStanType ($ varTagType );
85- if ($ this ->shouldVarTagTypeBeReported ($ expr , $ exprNativeType , $ varTagType )) {
88+ if ($ this ->shouldVarTagTypeBeReported ($ scope , $ expr , $ exprNativeType , $ varTagType )) {
8689 $ verbosity = VerbosityLevel::getRecommendedLevelByType ($ exprNativeType , $ varTagType );
8790 $ errors [] = RuleErrorBuilder::message (sprintf (
8891 '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):
9295 } else {
9396 $ exprType = $ scope ->getType ($ expr );
9497 if (
95- $ this ->shouldVarTagTypeBeReported ($ expr , $ exprType , $ varTagType )
98+ $ this ->shouldVarTagTypeBeReported ($ scope , $ expr , $ exprType , $ varTagType )
9699 && ($ this ->checkTypeAgainstPhpDocType || $ containsPhpStanType )
97100 ) {
98101 $ verbosity = VerbosityLevel::getRecommendedLevelByType ($ exprType , $ varTagType );
@@ -133,22 +136,22 @@ private function containsPhpStanType(Type $type): bool
133136 return false ;
134137 }
135138
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
137140 {
138141 if ($ expr instanceof Expr \Array_) {
139142 if ($ expr ->items === []) {
140143 $ type = new ArrayType (new MixedType (), new MixedType ());
141144 }
142145
143- return !$ this ->isAtLeastMaybeSuperTypeOfVarType ($ type , $ varTagType );
146+ return !$ this ->isAtLeastMaybeSuperTypeOfVarType ($ scope , $ type , $ varTagType );
144147 }
145148
146149 if ($ expr instanceof Expr \ConstFetch) {
147- return !$ this ->isAtLeastMaybeSuperTypeOfVarType ($ type , $ varTagType );
150+ return !$ this ->isAtLeastMaybeSuperTypeOfVarType ($ scope , $ type , $ varTagType );
148151 }
149152
150153 if ($ expr instanceof Node \Scalar) {
151- return !$ this ->isAtLeastMaybeSuperTypeOfVarType ($ type , $ varTagType );
154+ return !$ this ->isAtLeastMaybeSuperTypeOfVarType ($ scope , $ type , $ varTagType );
152155 }
153156
154157 if ($ expr instanceof Expr \New_) {
@@ -157,64 +160,87 @@ private function shouldVarTagTypeBeReported(Node\Expr $expr, Type $type, Type $v
157160 }
158161 }
159162
160- return $ this ->checkType ($ type , $ varTagType );
163+ return $ this ->checkType ($ scope , $ type , $ varTagType );
161164 }
162165
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
164167 {
165168 if ($ this ->strictWideningCheck ) {
166- return !$ this ->isSuperTypeOfVarType ($ type , $ varTagType );
169+ return !$ this ->isSuperTypeOfVarType ($ scope , $ type , $ varTagType );
167170 }
168171
169172 if ($ type ->isConstantArray ()->yes ()) {
170173 if ($ type ->isIterableAtLeastOnce ()->no ()) {
171174 $ type = new ArrayType (new MixedType (), new MixedType ());
172- return !$ this ->isAtLeastMaybeSuperTypeOfVarType ($ type , $ varTagType );
175+ return !$ this ->isAtLeastMaybeSuperTypeOfVarType ($ scope , $ type , $ varTagType );
173176 }
174177 }
175178
176179 if ($ type ->isIterable ()->yes () && $ varTagType ->isIterable ()->yes ()) {
177- if (!$ this ->isAtLeastMaybeSuperTypeOfVarType ($ type , $ varTagType )) {
180+ if (!$ this ->isAtLeastMaybeSuperTypeOfVarType ($ scope , $ type , $ varTagType )) {
178181 return true ;
179182 }
180183
181184 $ innerType = $ type ->getIterableValueType ();
182185 $ innerVarTagType = $ varTagType ->getIterableValueType ();
183186
184187 if ($ type ->equals ($ innerType ) || $ varTagType ->equals ($ innerVarTagType )) {
185- return !$ this ->isSuperTypeOfVarType ($ innerType , $ innerVarTagType );
188+ return !$ this ->isSuperTypeOfVarType ($ scope , $ innerType , $ innerVarTagType );
186189 }
187190
188- return $ this ->checkType ($ innerType , $ innerVarTagType , $ depth + 1 );
191+ return $ this ->checkType ($ scope , $ innerType , $ innerVarTagType , $ depth + 1 );
189192 }
190193
191194 if ($ depth === 0 && $ type ->isConstantValue ()->yes ()) {
192- return !$ this ->isAtLeastMaybeSuperTypeOfVarType ($ type , $ varTagType );
195+ return !$ this ->isAtLeastMaybeSuperTypeOfVarType ($ scope , $ type , $ varTagType );
193196 }
194197
195- return !$ this ->isSuperTypeOfVarType ($ type , $ varTagType );
198+ return !$ this ->isSuperTypeOfVarType ($ scope , $ type , $ varTagType );
196199 }
197200
198- private function isSuperTypeOfVarType (Type $ type , Type $ varTagType ): bool
201+ private function isSuperTypeOfVarType (Scope $ scope , Type $ type , Type $ varTagType ): bool
199202 {
200203 if ($ type ->isSuperTypeOf ($ varTagType )->yes ()) {
201204 return true ;
202205 }
203206
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+ }
205212
206213 return $ type ->isSuperTypeOf ($ varTagType )->yes ();
207214 }
208215
209- private function isAtLeastMaybeSuperTypeOfVarType (Type $ type , Type $ varTagType ): bool
216+ private function isAtLeastMaybeSuperTypeOfVarType (Scope $ scope , Type $ type , Type $ varTagType ): bool
210217 {
211218 if (!$ type ->isSuperTypeOf ($ varTagType )->no ()) {
212219 return true ;
213220 }
214221
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+ }
216227
217228 return !$ type ->isSuperTypeOf ($ varTagType )->no ();
218229 }
219230
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+
220246}
0 commit comments