diff --git a/src/Rules/Classes/NewStaticRule.php b/src/Rules/Classes/NewStaticRule.php index 62a69feefe..71f682c616 100644 --- a/src/Rules/Classes/NewStaticRule.php +++ b/src/Rules/Classes/NewStaticRule.php @@ -5,9 +5,11 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; use PHPStan\DependencyInjection\RegisteredRule; +use PHPStan\Php\PhpVersion; use PHPStan\Reflection\Php\PhpMethodReflection; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; +use PHPStan\TrinaryLogic; use function strtolower; /** @@ -17,6 +19,12 @@ final class NewStaticRule implements Rule { + public function __construct( + private PhpVersion $phpVersion, + ) + { + } + public function getNodeType(): string { return Node\Expr\New_::class; @@ -77,6 +85,19 @@ public function processNode(Node $node, Scope $scope): array } } + if ( + $this->phpVersion->supportsAbstractTraitMethods() + && $scope->isInTrait() + ) { + $traitReflection = $scope->getTraitReflection(); + if ($traitReflection->hasConstructor()) { + $isAbstract = $traitReflection->getConstructor()->isAbstract(); + if ($isAbstract === true || ($isAbstract instanceof TrinaryLogic && $isAbstract->yes())) { + return []; + } + } + } + return $messages; } diff --git a/tests/PHPStan/Rules/Classes/NewStaticRuleTest.php b/tests/PHPStan/Rules/Classes/NewStaticRuleTest.php index 23501ec856..e6efd12d79 100644 --- a/tests/PHPStan/Rules/Classes/NewStaticRuleTest.php +++ b/tests/PHPStan/Rules/Classes/NewStaticRuleTest.php @@ -2,8 +2,10 @@ namespace PHPStan\Rules\Classes; +use PHPStan\Php\PhpVersion; use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; +use const PHP_VERSION_ID; /** * @extends RuleTestCase @@ -13,7 +15,9 @@ class NewStaticRuleTest extends RuleTestCase protected function getRule(): Rule { - return new NewStaticRule(); + return new NewStaticRule( + new PhpVersion(PHP_VERSION_ID), + ); } public function testRule(): void @@ -39,4 +43,23 @@ public function testRuleWithConsistentConstructor(): void $this->analyse([__DIR__ . '/data/new-static-consistent-constructor.php'], []); } + public function testBug9654(): void + { + $errors = []; + if (PHP_VERSION_ID < 80000) { + $errors[] = [ + 'Unsafe usage of new static()', + 11, + 'See: https://phpstan.org/blog/solving-phpstan-error-unsafe-usage-of-new-static', + ]; + $errors[] = [ + 'Unsafe usage of new static()', + 11, + 'See: https://phpstan.org/blog/solving-phpstan-error-unsafe-usage-of-new-static', + ]; + } + + $this->analyse([__DIR__ . '/data/bug-9654.php'], $errors); + } + } diff --git a/tests/PHPStan/Rules/Classes/data/bug-9654.php b/tests/PHPStan/Rules/Classes/data/bug-9654.php new file mode 100644 index 0000000000..3b78a8e1b3 --- /dev/null +++ b/tests/PHPStan/Rules/Classes/data/bug-9654.php @@ -0,0 +1,32 @@ += 8.0 + +namespace Bug9654; + +trait Abc +{ + abstract public function __construct(); + + public static function create(): static + { + return new static(); // this is safe as the constructor is defined abstract in this trait + } +} + +class Foo +{ + use Abc; + + public function __construct() { + + } +} + +class Bar +{ + use Abc; + + public function __construct(int $i = 0) + { + $i = $i*2; + } +}