From 80c11097e31c4873b2326ce559311d6db6bc4b85 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sat, 19 Jul 2025 01:21:47 +0200 Subject: [PATCH 1/2] Fix 9654 --- src/Rules/Classes/NewStaticRule.php | 21 ++++++++++++ .../Rules/Classes/NewStaticRuleTest.php | 25 ++++++++++++++- tests/PHPStan/Rules/Classes/data/bug-9654.php | 32 +++++++++++++++++++ 3 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Rules/Classes/data/bug-9654.php diff --git a/src/Rules/Classes/NewStaticRule.php b/src/Rules/Classes/NewStaticRule.php index 62a69feefe..e414ce4e0c 100644 --- a/src/Rules/Classes/NewStaticRule.php +++ b/src/Rules/Classes/NewStaticRule.php @@ -5,6 +5,7 @@ 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; @@ -17,6 +18,12 @@ final class NewStaticRule implements Rule { + public function __construct( + private PhpVersion $phpVersion, + ) + { + } + public function getNodeType(): string { return Node\Expr\New_::class; @@ -77,6 +84,20 @@ public function processNode(Node $node, Scope $scope): array } } + if ( + $this->phpVersion->supportsAbstractTraitMethods() + && $scope->isInTrait() + ) { + $traitReflection = $scope->getTraitReflection(); + if ($traitReflection->hasConstructor()) { + $traitConstructor = $traitReflection->getConstructor(); + + if ($traitConstructor instanceof PhpMethodReflection && $traitConstructor->isAbstract()) { + 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; + } +} From 34d0c278adaaaf45cdd770f178a75718363f571f Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Mon, 21 Jul 2025 14:32:40 +0200 Subject: [PATCH 2/2] Try --- src/Rules/Classes/NewStaticRule.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Rules/Classes/NewStaticRule.php b/src/Rules/Classes/NewStaticRule.php index e414ce4e0c..71f682c616 100644 --- a/src/Rules/Classes/NewStaticRule.php +++ b/src/Rules/Classes/NewStaticRule.php @@ -9,6 +9,7 @@ use PHPStan\Reflection\Php\PhpMethodReflection; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; +use PHPStan\TrinaryLogic; use function strtolower; /** @@ -90,9 +91,8 @@ public function processNode(Node $node, Scope $scope): array ) { $traitReflection = $scope->getTraitReflection(); if ($traitReflection->hasConstructor()) { - $traitConstructor = $traitReflection->getConstructor(); - - if ($traitConstructor instanceof PhpMethodReflection && $traitConstructor->isAbstract()) { + $isAbstract = $traitReflection->getConstructor()->isAbstract(); + if ($isAbstract === true || ($isAbstract instanceof TrinaryLogic && $isAbstract->yes())) { return []; } }