88use PhpParser \Node \Name \FullyQualified ;
99use PhpParser \Node \Stmt \ClassMethod ;
1010use PHPStan \PhpDocParser \Ast \PhpDoc \ReturnTagValueNode ;
11- use PHPStan \PhpDocParser \Ast \Type \GenericTypeNode ;
1211use PHPStan \PhpDocParser \Ast \Type \IdentifierTypeNode ;
1312use PHPStan \Reflection \ClassReflection ;
13+ use PHPStan \Reflection \ReflectionProvider ;
1414use PHPStan \Type \Generic \GenericObjectType ;
1515use PHPStan \Type \ObjectType ;
1616use Rector \BetterPhpDocParser \PhpDocInfo \PhpDocInfo ;
@@ -60,7 +60,11 @@ final class NarrowObjectReturnTypeRector extends AbstractRector
6060 * @readonly
6161 */
6262 private DocBlockUpdater $ docBlockUpdater ;
63- public function __construct (BetterNodeFinder $ betterNodeFinder , ReflectionResolver $ reflectionResolver , AstResolver $ astResolver , StaticTypeMapper $ staticTypeMapper , TypeComparator $ typeComparator , PhpDocInfoFactory $ phpDocInfoFactory , DocBlockUpdater $ docBlockUpdater )
63+ /**
64+ * @readonly
65+ */
66+ private ReflectionProvider $ reflectionProvider ;
67+ public function __construct (BetterNodeFinder $ betterNodeFinder , ReflectionResolver $ reflectionResolver , AstResolver $ astResolver , StaticTypeMapper $ staticTypeMapper , TypeComparator $ typeComparator , PhpDocInfoFactory $ phpDocInfoFactory , DocBlockUpdater $ docBlockUpdater , ReflectionProvider $ reflectionProvider )
6468 {
6569 $ this ->betterNodeFinder = $ betterNodeFinder ;
6670 $ this ->reflectionResolver = $ reflectionResolver ;
@@ -69,10 +73,11 @@ public function __construct(BetterNodeFinder $betterNodeFinder, ReflectionResolv
6973 $ this ->typeComparator = $ typeComparator ;
7074 $ this ->phpDocInfoFactory = $ phpDocInfoFactory ;
7175 $ this ->docBlockUpdater = $ docBlockUpdater ;
76+ $ this ->reflectionProvider = $ reflectionProvider ;
7277 }
7378 public function getRuleDefinition (): RuleDefinition
7479 {
75- return new RuleDefinition ('Narrows return type from generic object or parent class to specific class in final classes/methods ' , [new CodeSample (<<<'CODE_SAMPLE'
80+ return new RuleDefinition ('Narrows return type from generic ` object` or parent class to specific class in final classes/methods ' , [new CodeSample (<<<'CODE_SAMPLE'
7681final class TalkFactory extends AbstractFactory
7782{
7883 protected function build(): object
@@ -133,19 +138,29 @@ public function refactor(Node $node): ?Node
133138 if (!$ classReflection ->isFinalByKeyword () && !$ node ->isFinal ()) {
134139 return null ;
135140 }
136- $ actualReturnClass = $ this ->getActualReturnClass ($ node );
141+ $ actualReturnClass = $ this ->getActualReturnedClass ($ node );
137142 if ($ actualReturnClass === null ) {
138143 return null ;
139144 }
140145 $ declaredType = $ returnType ->toString ();
146+ // already most narrow type
141147 if ($ declaredType === $ actualReturnClass ) {
142148 return null ;
143149 }
144- if ($ this ->isDeclaredTypeFinal ($ declaredType )) {
145- return null ;
146- }
147- if ($ this ->isActualTypeAnonymous ($ actualReturnClass )) {
148- return null ;
150+ // non-existing class
151+ if ($ declaredType !== 'object ' ) {
152+ if (!$ this ->reflectionProvider ->hasClass ($ declaredType )) {
153+ return null ;
154+ }
155+ $ declaredTypeClassReflection = $ this ->reflectionProvider ->getClass ($ declaredType );
156+ // already last final object
157+ if ($ declaredTypeClassReflection ->isFinalByKeyword ()) {
158+ return null ;
159+ }
160+ // this rule narrows only object or class types, not interfaces
161+ if (!$ declaredTypeClassReflection ->isClass ()) {
162+ return null ;
163+ }
149164 }
150165 if (!$ this ->isNarrowingValid ($ node , $ declaredType , $ actualReturnClass )) {
151166 return null ;
@@ -169,8 +184,6 @@ private function updateDocblock(ClassMethod $classMethod, string $actualReturnCl
169184 }
170185 if ($ returnTagValueNode ->type instanceof IdentifierTypeNode) {
171186 $ oldType = $ this ->staticTypeMapper ->mapPHPStanPhpDocTypeNodeToPHPStanType ($ returnTagValueNode ->type , $ classMethod );
172- } elseif ($ returnTagValueNode ->type instanceof GenericTypeNode) {
173- $ oldType = $ this ->staticTypeMapper ->mapPHPStanPhpDocTypeNodeToPHPStanType ($ returnTagValueNode ->type ->type , $ classMethod );
174187 } else {
175188 return ;
176189 }
@@ -180,34 +193,9 @@ private function updateDocblock(ClassMethod $classMethod, string $actualReturnCl
180193 return ;
181194 }
182195 }
183- if ($ returnTagValueNode ->type instanceof IdentifierTypeNode) {
184- $ returnTagValueNode ->type = new FullyQualifiedIdentifierTypeNode ($ actualReturnClass );
185- } else {
186- $ returnTagValueNode ->type ->type = new FullyQualifiedIdentifierTypeNode ($ actualReturnClass );
187- }
196+ $ returnTagValueNode ->type = new FullyQualifiedIdentifierTypeNode ($ actualReturnClass );
188197 $ this ->docBlockUpdater ->updateRefactoredNodeWithPhpDocInfo ($ classMethod );
189198 }
190- private function isDeclaredTypeFinal (string $ declaredType ): bool
191- {
192- if ($ declaredType === 'object ' ) {
193- return \false;
194- }
195- $ declaredObjectType = new ObjectType ($ declaredType );
196- $ classReflection = $ declaredObjectType ->getClassReflection ();
197- if (!$ classReflection instanceof ClassReflection) {
198- return \false;
199- }
200- return $ classReflection ->isFinalByKeyword ();
201- }
202- private function isActualTypeAnonymous (string $ actualType ): bool
203- {
204- $ actualObjectType = new ObjectType ($ actualType );
205- $ classReflection = $ actualObjectType ->getClassReflection ();
206- if (!$ classReflection instanceof ClassReflection) {
207- return \false;
208- }
209- return $ classReflection ->isAnonymous ();
210- }
211199 private function isNarrowingValid (ClassMethod $ classMethod , string $ declaredType , string $ actualType ): bool
212200 {
213201 if ($ declaredType === 'object ' ) {
@@ -258,7 +246,7 @@ private function isNarrowingValidFromParent(ClassMethod $classMethod, string $ac
258246 }
259247 return \true;
260248 }
261- private function getActualReturnClass (ClassMethod $ classMethod ): ?string
249+ private function getActualReturnedClass (ClassMethod $ classMethod ): ?string
262250 {
263251 $ returnStatements = $ this ->betterNodeFinder ->findReturnsScoped ($ classMethod );
264252 if ($ returnStatements === []) {
0 commit comments