1313use PhpParser \Node \Stmt \Expression ;
1414use PhpParser \NodeVisitor ;
1515use PHPStan \Reflection \ClassReflection ;
16+ use PHPStan \Reflection \ExtendedMethodReflection ;
1617use Rector \Enum \ObjectReference ;
1718use Rector \PHPStan \ScopeFetcher ;
19+ use Rector \PHPStanStaticTypeMapper \Enum \TypeKind ;
1820use Rector \Rector \AbstractRector ;
21+ use Rector \StaticTypeMapper \StaticTypeMapper ;
1922use Rector \ValueObject \MethodName ;
2023use Symplify \RuleDocGenerator \ValueObject \CodeSample \CodeSample ;
2124use Symplify \RuleDocGenerator \ValueObject \RuleDefinition ;
2528 */
2629final class RemoveParentDelegatingConstructorRector extends AbstractRector
2730{
31+ public function __construct (
32+ private readonly StaticTypeMapper $ staticTypeMapper ,
33+ ) {
34+ }
35+
2836 public function getRuleDefinition (): RuleDefinition
2937 {
3038 return new RuleDefinition (
@@ -86,7 +94,8 @@ public function refactor(Node $node): ?int
8694 return null ;
8795 }
8896
89- if (! $ this ->hasParentClassWithConstructor ($ node )) {
97+ $ parentMethodReflection = $ this ->matchParentConstructorReflection ($ node );
98+ if (! $ parentMethodReflection instanceof ExtendedMethodReflection) {
9099 return null ;
91100 }
92101
@@ -96,49 +105,38 @@ public function refactor(Node $node): ?int
96105 return null ;
97106 }
98107
99- $ constructorParams = $ node -> getParams ();
100- if (count ( $ constructorParams ) !== count ( $ parentCallArgs )) {
108+ // match count and order
109+ if (! $ this -> isParameterAndArgCountAndOrderIdentical ( $ node )) {
101110 return null ;
102111 }
103112
104- // match passed names in the same order
105- $ paramNames = [];
106- foreach ($ constructorParams as $ constructorParam ) {
107- $ paramNames [] = $ this ->getName ($ constructorParam ->var );
108- }
109-
110- $ argNames = [];
111- foreach ($ parentCallArgs as $ parentCallArg ) {
112- $ argValue = $ parentCallArg ->value ;
113- if (! $ argValue instanceof Variable) {
114- return null ;
115- }
116-
117- $ argNames [] = $ this ->getName ($ argValue );
118- }
119-
120- if ($ paramNames !== $ argNames ) {
113+ // match parameter types and parent constructor types
114+ if (! $ this ->areConstructorAndParentParameterTypesMatching ($ node , $ parentMethodReflection )) {
121115 return null ;
122116 }
123117
124118 return NodeVisitor::REMOVE_NODE ;
125119 }
126120
127- private function hasParentClassWithConstructor (ClassMethod $ classMethod ): bool
121+ private function matchParentConstructorReflection (ClassMethod $ classMethod ): ? ExtendedMethodReflection
128122 {
129123 $ scope = ScopeFetcher::fetch ($ classMethod );
130124
131125 $ classReflection = $ scope ->getClassReflection ();
132126 if (! $ classReflection instanceof ClassReflection) {
133- return false ;
127+ return null ;
134128 }
135129
136130 $ parentClassReflection = $ classReflection ->getParentClass ();
137131 if (! $ parentClassReflection instanceof ClassReflection) {
138- return false ;
132+ return null ;
133+ }
134+
135+ if (! $ parentClassReflection ->hasConstructor ()) {
136+ return null ;
139137 }
140138
141- return $ parentClassReflection ->hasConstructor ();
139+ return $ parentClassReflection ->getConstructor ();
142140 }
143141
144142 /**
@@ -167,4 +165,70 @@ private function matchParentConstructorCallArgs(Stmt $stmt): ?array
167165
168166 return $ staticCall ->getArgs ();
169167 }
168+
169+ private function isParameterAndArgCountAndOrderIdentical (ClassMethod $ classMethod ): bool
170+ {
171+ $ soleStmt = $ classMethod ->stmts [0 ];
172+
173+ $ parentCallArgs = $ this ->matchParentConstructorCallArgs ($ soleStmt );
174+ if ($ parentCallArgs === null ) {
175+ return false ;
176+ }
177+
178+ $ constructorParams = $ classMethod ->getParams ();
179+ if (count ($ constructorParams ) !== count ($ parentCallArgs )) {
180+ return false ;
181+ }
182+
183+ // match passed names in the same order
184+ $ paramNames = [];
185+ foreach ($ constructorParams as $ constructorParam ) {
186+ $ paramNames [] = $ this ->getName ($ constructorParam ->var );
187+ }
188+
189+ $ argNames = [];
190+ foreach ($ parentCallArgs as $ parentCallArg ) {
191+ $ argValue = $ parentCallArg ->value ;
192+ if (! $ argValue instanceof Variable) {
193+ return false ;
194+ }
195+
196+ $ argNames [] = $ this ->getName ($ argValue );
197+ }
198+
199+ return $ paramNames === $ argNames ;
200+ }
201+
202+ private function areConstructorAndParentParameterTypesMatching (
203+ ClassMethod $ classMethod ,
204+ ExtendedMethodReflection $ parentMethodReflection
205+ ): bool {
206+ foreach ($ classMethod ->getParams () as $ position => $ param ) {
207+ $ parameterType = $ param ->type ;
208+
209+ // no type override
210+ if ($ parameterType === null ) {
211+ continue ;
212+ }
213+
214+ $ parametersSelector = $ parentMethodReflection ->getOnlyVariant ();
215+
216+ foreach ($ parametersSelector ->getParameters () as $ index => $ parameterReflection ) {
217+ if ($ index !== $ position ) {
218+ continue ;
219+ }
220+
221+ $ parentParameterType = $ this ->staticTypeMapper ->mapPHPStanTypeToPhpParserNode (
222+ $ parameterReflection ->getType (),
223+ TypeKind::PARAM
224+ );
225+
226+ if (! $ this ->nodeComparator ->areNodesEqual ($ parameterType , $ parentParameterType )) {
227+ return false ;
228+ }
229+ }
230+ }
231+
232+ return true ;
233+ }
170234}
0 commit comments