44
55use PhpParser \Node ;
66use PhpParser \Node \Expr ;
7+ use PHPStan \Analyser \NameScope ;
78use PHPStan \Analyser \Scope ;
89use PHPStan \Node \Expr \GetOffsetValueTypeExpr ;
10+ use PHPStan \PhpDoc \NameScopeAlreadyBeingCreatedException ;
911use PHPStan \PhpDoc \Tag \VarTag ;
12+ use PHPStan \PhpDoc \TypeNodeResolver ;
1013use PHPStan \Rules \IdentifierRuleError ;
1114use PHPStan \Rules \RuleErrorBuilder ;
1215use PHPStan \Type \ArrayType ;
16+ use PHPStan \Type \FileTypeMapper ;
1317use PHPStan \Type \Generic \GenericObjectType ;
1418use PHPStan \Type \MixedType ;
1519use PHPStan \Type \ObjectType ;
2428final class VarTagTypeRuleHelper
2529{
2630
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+ )
2837 {
2938 }
3039
@@ -76,7 +85,7 @@ public function checkExprType(Scope $scope, Node\Expr $expr, Type $varTagType):
7685 $ errors = [];
7786 $ exprNativeType = $ scope ->getNativeType ($ expr );
7887 $ containsPhpStanType = $ this ->containsPhpStanType ($ varTagType );
79- if ($ this ->shouldVarTagTypeBeReported ($ expr , $ exprNativeType , $ varTagType )) {
88+ if ($ this ->shouldVarTagTypeBeReported ($ scope , $ expr , $ exprNativeType , $ varTagType )) {
8089 $ verbosity = VerbosityLevel::getRecommendedLevelByType ($ exprNativeType , $ varTagType );
8190 $ errors [] = RuleErrorBuilder::message (sprintf (
8291 '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):
8695 } else {
8796 $ exprType = $ scope ->getType ($ expr );
8897 if (
89- $ this ->shouldVarTagTypeBeReported ($ expr , $ exprType , $ varTagType )
98+ $ this ->shouldVarTagTypeBeReported ($ scope , $ expr , $ exprType , $ varTagType )
9099 && ($ this ->checkTypeAgainstPhpDocType || $ containsPhpStanType )
91100 ) {
92101 $ verbosity = VerbosityLevel::getRecommendedLevelByType ($ exprType , $ varTagType );
@@ -127,22 +136,22 @@ private function containsPhpStanType(Type $type): bool
127136 return false ;
128137 }
129138
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
131140 {
132141 if ($ expr instanceof Expr \Array_) {
133142 if ($ expr ->items === []) {
134143 $ type = new ArrayType (new MixedType (), new MixedType ());
135144 }
136145
137- return $ type -> isSuperTypeOf ( $ varTagType)-> no ( );
146+ return ! $ this -> isAtLeastMaybeSuperTypeOfVarType ( $ scope , $ type , $ varTagType );
138147 }
139148
140149 if ($ expr instanceof Expr \ConstFetch) {
141- return $ type -> isSuperTypeOf ( $ varTagType)-> no ( );
150+ return ! $ this -> isAtLeastMaybeSuperTypeOfVarType ( $ scope , $ type , $ varTagType );
142151 }
143152
144153 if ($ expr instanceof Node \Scalar) {
145- return $ type -> isSuperTypeOf ( $ varTagType)-> no ( );
154+ return ! $ this -> isAtLeastMaybeSuperTypeOfVarType ( $ scope , $ type , $ varTagType );
146155 }
147156
148157 if ($ expr instanceof Expr \New_) {
@@ -151,42 +160,87 @@ private function shouldVarTagTypeBeReported(Node\Expr $expr, Type $type, Type $v
151160 }
152161 }
153162
154- return $ this ->checkType ($ type , $ varTagType );
163+ return $ this ->checkType ($ scope , $ type , $ varTagType );
155164 }
156165
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
158167 {
159168 if ($ this ->strictWideningCheck ) {
160- return !$ type -> isSuperTypeOf ( $ varTagType)-> yes ( );
169+ return !$ this -> isSuperTypeOfVarType ( $ scope , $ type , $ varTagType );
161170 }
162171
163172 if ($ type ->isConstantArray ()->yes ()) {
164173 if ($ type ->isIterableAtLeastOnce ()->no ()) {
165174 $ type = new ArrayType (new MixedType (), new MixedType ());
166- return $ type -> isSuperTypeOf ( $ varTagType)-> no ( );
175+ return ! $ this -> isAtLeastMaybeSuperTypeOfVarType ( $ scope , $ type , $ varTagType );
167176 }
168177 }
169178
170179 if ($ type ->isIterable ()->yes () && $ varTagType ->isIterable ()->yes ()) {
171- if ($ type -> isSuperTypeOf ( $ varTagType)-> no ( )) {
180+ if (! $ this -> isAtLeastMaybeSuperTypeOfVarType ( $ scope , $ type , $ varTagType )) {
172181 return true ;
173182 }
174183
175184 $ innerType = $ type ->getIterableValueType ();
176185 $ innerVarTagType = $ varTagType ->getIterableValueType ();
177186
178187 if ($ type ->equals ($ innerType ) || $ varTagType ->equals ($ innerVarTagType )) {
179- return !$ innerType -> isSuperTypeOf ( $ innerVarTagType)-> yes ( );
188+ return !$ this -> isSuperTypeOfVarType ( $ scope , $ innerType , $ innerVarTagType );
180189 }
181190
182- return $ this ->checkType ($ innerType , $ innerVarTagType , $ depth + 1 );
191+ return $ this ->checkType ($ scope , $ innerType , $ innerVarTagType , $ depth + 1 );
183192 }
184193
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 );
187196 }
188197
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+ );
190244 }
191245
192246}
0 commit comments