diff --git a/conf/config.level0.neon b/conf/config.level0.neon index f4992cc7bd..0d05146fff 100644 --- a/conf/config.level0.neon +++ b/conf/config.level0.neon @@ -75,6 +75,7 @@ rules: - PHPStan\Rules\Methods\AbstractMethodInNonAbstractClassRule - PHPStan\Rules\Methods\CallMethodsRule - PHPStan\Rules\Methods\CallStaticMethodsRule + - PHPStan\Rules\Methods\ConstructorReturnTypeRule - PHPStan\Rules\Methods\ExistingClassesInTypehintsRule - PHPStan\Rules\Methods\FinalPrivateMethodRule - PHPStan\Rules\Methods\MethodCallableRule diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 11f30df85c..05c4bcb042 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -4369,6 +4369,7 @@ private function processNodesForTraitUse($node, ClassReflection $traitReflection continue; } + $methodAst->setAttribute('originalTraitMethodName', $methodAst->name->toLowerString()); $methodAst->name = $methodNames[$methodName]; } $this->processStmtNodes($node, $stmts, $scope->enterTrait($traitReflection), $nodeCallback, StatementContext::createTopLevel()); diff --git a/src/Rules/Methods/ConstructorReturnTypeRule.php b/src/Rules/Methods/ConstructorReturnTypeRule.php new file mode 100644 index 0000000000..c93634b94e --- /dev/null +++ b/src/Rules/Methods/ConstructorReturnTypeRule.php @@ -0,0 +1,63 @@ + + */ +class ConstructorReturnTypeRule implements Rule +{ + + public function getNodeType(): string + { + return InClassMethodNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $classReflection = $node->getClassReflection(); + $methodNode = $node->getOriginalNode(); + if ($scope->isInTrait()) { + $originalMethodName = $methodNode->getAttribute('originalTraitMethodName'); + if ( + $originalMethodName === '__construct' + && $methodNode->returnType !== null + ) { + return [ + RuleErrorBuilder::message(sprintf('Original constructor of trait %s has a return type.', $scope->getTraitReflection()->getDisplayName())) + ->identifier('constructor.returnType') + ->nonIgnorable() + ->build(), + ]; + } + } + if (!$classReflection->hasConstructor()) { + return []; + } + + $constructorReflection = $classReflection->getConstructor(); + $methodReflection = $node->getMethodReflection(); + if ($methodReflection->getName() !== $constructorReflection->getName()) { + return []; + } + + if ($methodNode->returnType === null) { + return []; + } + + return [ + RuleErrorBuilder::message(sprintf('Constructor of class %s has a return type.', $classReflection->getDisplayName())) + ->identifier('constructor.returnType') + ->nonIgnorable() + ->build(), + ]; + } + +} diff --git a/tests/PHPStan/Rules/Methods/ConstructorReturnTypeRuleTest.php b/tests/PHPStan/Rules/Methods/ConstructorReturnTypeRuleTest.php new file mode 100644 index 0000000000..335972e370 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/ConstructorReturnTypeRuleTest.php @@ -0,0 +1,37 @@ + + */ +class ConstructorReturnTypeRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + return new ConstructorReturnTypeRule(); + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/constructor-return-type.php'], [ + [ + 'Constructor of class ConstructorReturnType\Bar has a return type.', + 17, + ], + [ + 'Constructor of class ConstructorReturnType\UsesFooTrait has a return type.', + 26, + ], + [ + 'Original constructor of trait ConstructorReturnType\BarTrait has a return type.', + 35, + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/Methods/data/constructor-return-type.php b/tests/PHPStan/Rules/Methods/data/constructor-return-type.php new file mode 100644 index 0000000000..5f6f456ed9 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/constructor-return-type.php @@ -0,0 +1,51 @@ +