From 44e67ad068ff384611aef8c1ff6c31f26ddd0e56 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 4 May 2026 11:34:47 +0200 Subject: [PATCH 1/9] Revert "Fix phpstan/phpstan#14138: errors for argument array template types no longer reported (argument.type) (#5300)" This reverts commit c381cc004ac38cc605bbd4996bb5c3e2fe858678. --- .../Php/PhpClassReflectionExtension.php | 50 +++++++++---------- .../Rules/Classes/InstantiationRuleTest.php | 12 ----- .../PHPStan/Rules/Classes/data/bug-14138.php | 37 -------------- .../PHPStan/Rules/Pure/PureMethodRuleTest.php | 7 --- .../Rules/Pure/data/bug-14138-pure.php | 27 ---------- 5 files changed, 24 insertions(+), 109 deletions(-) delete mode 100644 tests/PHPStan/Rules/Classes/data/bug-14138.php delete mode 100644 tests/PHPStan/Rules/Pure/data/bug-14138-pure.php diff --git a/src/Reflection/Php/PhpClassReflectionExtension.php b/src/Reflection/Php/PhpClassReflectionExtension.php index a31e70433e3..c97de268afa 100644 --- a/src/Reflection/Php/PhpClassReflectionExtension.php +++ b/src/Reflection/Php/PhpClassReflectionExtension.php @@ -902,6 +902,14 @@ public function createUserlandMethodReflection(ClassReflection $fileDeclaringCla } $phpDocParameterTypes[$paramName] = $paramTag->getType(); } + foreach ($phpDocParameterTypes as $paramName => $paramType) { + $phpDocParameterTypes[$paramName] = TemplateTypeHelper::resolveTemplateTypes( + $paramType, + $phpDocBlockClassReflection->getActiveTemplateTypeMap(), + $phpDocBlockClassReflection->getCallSiteVarianceMap(), + TemplateTypeVariance::createContravariant(), + ); + } foreach ($resolvedPhpDoc->getParamOutTags() as $paramName => $paramOutTag) { $phpDocParameterOutTypes[$paramName] = TemplateTypeHelper::resolveTemplateTypes( $paramOutTag->getType(), @@ -917,6 +925,22 @@ public function createUserlandMethodReflection(ClassReflection $fileDeclaringCla $isInternal = $resolvedPhpDoc->isInternal(); $isFinal = $resolvedPhpDoc->isFinal(); $isPure ??= $resolvedPhpDoc->isPure(); + if ($isPure === null) { + $classResolvedPhpDoc = $phpDocBlockClassReflection->getResolvedPhpDoc(); + if ($classResolvedPhpDoc !== null && $classResolvedPhpDoc->areAllMethodsPure()) { + if ( + strtolower($methodReflection->getName()) === '__construct' + || ( + ($phpDocReturnType === null || !$phpDocReturnType->isVoid()->yes()) + && !$nativeReturnType->isVoid()->yes() + ) + ) { + $isPure = true; + } + } elseif ($classResolvedPhpDoc !== null && $classResolvedPhpDoc->areAllMethodsImpure()) { + $isPure = false; + } + } $asserts = Assertions::createFromResolvedPhpDocBlock($resolvedPhpDoc); $acceptsNamedArguments = $resolvedPhpDoc->acceptsNamedArguments(); $selfOutType = $resolvedPhpDoc->getSelfOutTag() !== null ? $resolvedPhpDoc->getSelfOutTag()->getType() : null; @@ -925,32 +949,6 @@ public function createUserlandMethodReflection(ClassReflection $fileDeclaringCla } } - if ($isPure === null) { - $classResolvedPhpDoc = $phpDocBlockClassReflection->getResolvedPhpDoc(); - if ($classResolvedPhpDoc !== null && $classResolvedPhpDoc->areAllMethodsPure()) { - if ( - strtolower($methodReflection->getName()) === '__construct' - || ( - ($phpDocReturnType === null || !$phpDocReturnType->isVoid()->yes()) - && !$nativeReturnType->isVoid()->yes() - ) - ) { - $isPure = true; - } - } elseif ($classResolvedPhpDoc !== null && $classResolvedPhpDoc->areAllMethodsImpure()) { - $isPure = false; - } - } - - foreach ($phpDocParameterTypes as $paramName => $paramType) { - $phpDocParameterTypes[$paramName] = TemplateTypeHelper::resolveTemplateTypes( - $paramType, - $phpDocBlockClassReflection->getActiveTemplateTypeMap(), - $phpDocBlockClassReflection->getCallSiteVarianceMap(), - TemplateTypeVariance::createContravariant(), - ); - } - return $this->methodReflectionFactory->create( $actualDeclaringClass, $declaringTrait, diff --git a/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php b/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php index 5f83c21b6e9..606d37ad40a 100644 --- a/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php +++ b/tests/PHPStan/Rules/Classes/InstantiationRuleTest.php @@ -634,18 +634,6 @@ public function testBug11006(): void $this->analyse([__DIR__ . '/data/bug-11006.php'], []); } - #[RequiresPhp('>= 8.0.0')] - public function testBug14138(): void - { - $this->analyse([__DIR__ . '/data/bug-14138.php'], [ - [ - 'Parameter #1 $data of class Bug14138\Foo constructor expects array{foo: int, bar: int}, array{foo: 1} given.', - 36, - "Array does not have offset 'bar'.", - ], - ]); - } - public function testBug14499(): void { $this->analyse([__DIR__ . '/data/bug-14499.php'], []); diff --git a/tests/PHPStan/Rules/Classes/data/bug-14138.php b/tests/PHPStan/Rules/Classes/data/bug-14138.php deleted file mode 100644 index 185e16b309a..00000000000 --- a/tests/PHPStan/Rules/Classes/data/bug-14138.php +++ /dev/null @@ -1,37 +0,0 @@ -= 8.0 - -declare(strict_types = 1); - -namespace Bug14138; - -/** - * @template T of array - */ -abstract class AbstractApiData -{ - public function __construct( - /** @var T */ - protected array $data - ) {} - - /** - * @return T - */ - public function getData(): array - { - return $this->data; - } -} - - -/** - * @extends AbstractApiData - */ -class Foo extends AbstractApiData {} - -function testing(): void { - $a = new Foo(["foo" => 1]); -} diff --git a/tests/PHPStan/Rules/Pure/PureMethodRuleTest.php b/tests/PHPStan/Rules/Pure/PureMethodRuleTest.php index 8287809407a..8e1d6689769 100644 --- a/tests/PHPStan/Rules/Pure/PureMethodRuleTest.php +++ b/tests/PHPStan/Rules/Pure/PureMethodRuleTest.php @@ -347,13 +347,6 @@ public function testAllMethodsArePure(): void ]); } - #[RequiresPhp('>= 8.0.0')] - public function testBug14138Pure(): void - { - $this->treatPhpDocTypesAsCertain = true; - $this->analyse([__DIR__ . '/data/bug-14138-pure.php'], []); - } - public function testBug12382(): void { $this->treatPhpDocTypesAsCertain = true; diff --git a/tests/PHPStan/Rules/Pure/data/bug-14138-pure.php b/tests/PHPStan/Rules/Pure/data/bug-14138-pure.php deleted file mode 100644 index 0c4f6668194..00000000000 --- a/tests/PHPStan/Rules/Pure/data/bug-14138-pure.php +++ /dev/null @@ -1,27 +0,0 @@ -= 8.0 - -namespace Bug14138Pure; - -/** - * @phpstan-all-methods-pure - */ -class PureClassWithPromotedProps -{ - public function __construct( - protected int $value - ) {} - - public function getValue(): int - { - return $this->value; - } -} - -class TestCaller -{ - /** @phpstan-pure */ - public function callPureConstructor(): PureClassWithPromotedProps - { - return new PureClassWithPromotedProps(1); - } -} From 03adc4dafd24a6f58e15e5999a26e66d8c188651 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 4 May 2026 11:34:53 +0200 Subject: [PATCH 2/9] Revert "Remove obsolete todo" This reverts commit 1734058bef811c84970b19806bbd2daee38786ab. --- src/Reflection/Php/PhpClassReflectionExtension.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Reflection/Php/PhpClassReflectionExtension.php b/src/Reflection/Php/PhpClassReflectionExtension.php index c97de268afa..7e940b54bd6 100644 --- a/src/Reflection/Php/PhpClassReflectionExtension.php +++ b/src/Reflection/Php/PhpClassReflectionExtension.php @@ -292,6 +292,7 @@ private function createProperty( $currentResolvedPhpDoc, ); } elseif ($docComment !== null) { + // todo could call phpDocInheritanceResolver too $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( $declaringClassReflection->getFileName(), $declaringClassName, From 8c5a39bf73a3bbc73a25bb96d47e2446e5938f3b Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 4 May 2026 11:34:58 +0200 Subject: [PATCH 3/9] Revert "Do not require filename for reading PHPDocs" This reverts commit 1eaa1e6cbbba6319bbd25162dbd76f905e47ee26. --- src/Reflection/Php/PhpClassReflectionExtension.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Reflection/Php/PhpClassReflectionExtension.php b/src/Reflection/Php/PhpClassReflectionExtension.php index 7e940b54bd6..ba91e7567fb 100644 --- a/src/Reflection/Php/PhpClassReflectionExtension.php +++ b/src/Reflection/Php/PhpClassReflectionExtension.php @@ -654,12 +654,13 @@ private function createMethod( if ( $currentResolvedPhpDoc === null && $methodReflection->getDocComment() !== false + && $methodReflection->getFileName() !== false ) { $currentResolvedPhpDoc = $this->phpDocInheritanceResolver->resolvePhpDocForMethod( $declaringClass, $methodReflection->getName(), $this->fileTypeMapper->getResolvedPhpDoc( - $methodReflection->getFileName() === false ? null : $methodReflection->getFileName(), + $methodReflection->getFileName(), $declaringClassName, null, $methodReflection->getName(), @@ -798,7 +799,7 @@ public function createUserlandMethodReflection(ClassReflection $fileDeclaringCla [$currentResolvedPhpDoc, $phpDocBlockClassReflection] = $stubPhpDocPair; } - if ($currentResolvedPhpDoc === null && $methodReflection->getDocComment() !== false) { + if ($currentResolvedPhpDoc === null && $methodReflection->getDocComment() !== false && $actualDeclaringClass->getFileName() !== null) { $currentResolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( $actualDeclaringClass->getFileName(), $actualDeclaringClass->getName(), From 5895e5fcdc32feefa524c9e6ca9bc4ff7ad7bb00 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 4 May 2026 11:35:03 +0200 Subject: [PATCH 4/9] Revert "No need to have generics in class constant-related messages" This reverts commit d584c6e2592c2aaae5be193860c0f683fc6b3f5f. --- .../Constants/OverridingConstantRule.php | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Rules/Constants/OverridingConstantRule.php b/src/Rules/Constants/OverridingConstantRule.php index ad538c34688..78cb6faff7a 100644 --- a/src/Rules/Constants/OverridingConstantRule.php +++ b/src/Rules/Constants/OverridingConstantRule.php @@ -65,9 +65,9 @@ private function processSingleConstant(ClassReflection $classReflection, string if ($prototype->isFinal()) { $errors[] = RuleErrorBuilder::message(sprintf( 'Constant %s::%s overrides final constant %s::%s.', - $classReflection->getDisplayName(false), + $classReflection->getDisplayName(), $constantReflection->getName(), - $prototype->getDeclaringClass()->getDisplayName(false), + $prototype->getDeclaringClass()->getDisplayName(), $prototype->getName(), ))->identifier('classConstant.final')->nonIgnorable()->build(); } @@ -77,18 +77,18 @@ private function processSingleConstant(ClassReflection $classReflection, string $errors[] = RuleErrorBuilder::message(sprintf( '%s constant %s::%s overriding public constant %s::%s should also be public.', $constantReflection->isPrivate() ? 'Private' : 'Protected', - $constantReflection->getDeclaringClass()->getDisplayName(false), + $constantReflection->getDeclaringClass()->getDisplayName(), $constantReflection->getName(), - $prototype->getDeclaringClass()->getDisplayName(false), + $prototype->getDeclaringClass()->getDisplayName(), $prototype->getName(), ))->identifier('classConstant.visibility')->nonIgnorable()->build(); } } elseif ($constantReflection->isPrivate()) { $errors[] = RuleErrorBuilder::message(sprintf( 'Private constant %s::%s overriding protected constant %s::%s should be protected or public.', - $constantReflection->getDeclaringClass()->getDisplayName(false), + $constantReflection->getDeclaringClass()->getDisplayName(), $constantReflection->getName(), - $prototype->getDeclaringClass()->getDisplayName(false), + $prototype->getDeclaringClass()->getDisplayName(), $prototype->getName(), ))->identifier('classConstant.visibility')->nonIgnorable()->build(); } @@ -105,19 +105,19 @@ private function processSingleConstant(ClassReflection $classReflection, string $errors[] = RuleErrorBuilder::message(sprintf( 'Native type %s of constant %s::%s is not covariant with native type %s of constant %s::%s.', $constantNativeType->describe(VerbosityLevel::typeOnly()), - $constantReflection->getDeclaringClass()->getDisplayName(false), + $constantReflection->getDeclaringClass()->getDisplayName(), $constantReflection->getName(), $prototypeNativeType->describe(VerbosityLevel::typeOnly()), - $prototype->getDeclaringClass()->getDisplayName(false), + $prototype->getDeclaringClass()->getDisplayName(), $prototype->getName(), ))->identifier('classConstant.nativeType')->nonIgnorable()->build(); } } else { $errors[] = RuleErrorBuilder::message(sprintf( 'Constant %s::%s overriding constant %s::%s (%s) should also have native type %s.', - $constantReflection->getDeclaringClass()->getDisplayName(false), + $constantReflection->getDeclaringClass()->getDisplayName(), $constantReflection->getName(), - $prototype->getDeclaringClass()->getDisplayName(false), + $prototype->getDeclaringClass()->getDisplayName(), $prototype->getName(), $prototypeNativeType->describe(VerbosityLevel::typeOnly()), $prototypeNativeType->describe(VerbosityLevel::typeOnly()), @@ -137,10 +137,10 @@ private function processSingleConstant(ClassReflection $classReflection, string $errors[] = RuleErrorBuilder::message(sprintf( 'Type %s of constant %s::%s is not covariant with type %s of constant %s::%s.', $constantReflection->getValueType()->describe(VerbosityLevel::value()), - $constantReflection->getDeclaringClass()->getDisplayName(false), + $constantReflection->getDeclaringClass()->getDisplayName(), $constantReflection->getName(), $prototype->getValueType()->describe(VerbosityLevel::value()), - $prototype->getDeclaringClass()->getDisplayName(false), + $prototype->getDeclaringClass()->getDisplayName(), $prototype->getName(), ))->identifier('classConstant.type')->build(); } From eeecd8d2c78d48c1a0f78069c7c644bca41dfbcc Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 4 May 2026 11:35:33 +0200 Subject: [PATCH 5/9] Revert "Regression tests" This reverts commit ca0e92c0eae121156645fb61183222fcda736cef. --- .../Rules/Methods/ReturnTypeRuleTest.php | 5 -- .../PHPStan/Rules/Methods/data/bug-10771.php | 58 ------------------- 2 files changed, 63 deletions(-) delete mode 100644 tests/PHPStan/Rules/Methods/data/bug-10771.php diff --git a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php index a60a6a704a6..130ac6f11cf 100644 --- a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php @@ -1302,11 +1302,6 @@ public function testBug8438(): void $this->analyse([__DIR__ . '/data/bug-8438.php'], []); } - public function testBug10771(): void - { - $this->analyse([__DIR__ . '/data/bug-10771.php'], []); - } - public function testBug5946(): void { $this->analyse([__DIR__ . '/data/bug-5946.php'], [ diff --git a/tests/PHPStan/Rules/Methods/data/bug-10771.php b/tests/PHPStan/Rules/Methods/data/bug-10771.php deleted file mode 100644 index 052da8049b6..00000000000 --- a/tests/PHPStan/Rules/Methods/data/bug-10771.php +++ /dev/null @@ -1,58 +0,0 @@ - - */ -class A extends B -{ - /** - * @return class-string - */ - public function getClassString(): string - { - return static::ENTITY; - } -} - -/** - * @template T of Template - */ -class B -{ - /** @var class-string */ - public const ENTITY = Template::class; -} - -class Template -{ - -} - -/** - * @template TA of Template - * - * @extends Bb - */ -class Aa extends Bb -{ - /** - * @return class-string - */ - public function getClassString(): string - { - return static::ENTITY; - } -} - -/** - * @template TB of Template - */ -class Bb -{ - /** @var class-string */ - public const ENTITY = Template::class; -} From 314f8765e273b85137329f483490ade4559762fa Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 4 May 2026 11:39:23 +0200 Subject: [PATCH 6/9] Revert "Introduce `@phpstan-all-methods-(im)pure`" This reverts commit ba8fae8027fbd88fc323e5bfc1ebe08ed7a5bee1. --- bin/functionMetadata_original.php | 162 ++++++++++++++++ conf/config.neon | 1 - resources/functionMetadata.php | 161 ++++++++++++++++ src/Analyser/NodeScopeResolver.php | 17 -- src/PhpDoc/PhpDocNodeResolver.php | 10 - src/PhpDoc/ResolvedPhpDocBlock.php | 22 --- .../Php/PhpClassReflectionExtension.php | 16 -- src/Rules/PhpDoc/InvalidPHPStanDocTagRule.php | 2 - stubs/Redis.stub | 11 -- ...rictComparisonOfDifferentTypesRuleTest.php | 5 - .../Rules/Comparison/data/bug-10215.php | 23 --- .../PHPStan/Rules/Pure/PureMethodRuleTest.php | 63 ------ .../Rules/Pure/data/all-methods-are-pure.php | 180 ------------------ 13 files changed, 323 insertions(+), 350 deletions(-) delete mode 100644 stubs/Redis.stub delete mode 100644 tests/PHPStan/Rules/Comparison/data/bug-10215.php delete mode 100644 tests/PHPStan/Rules/Pure/data/all-methods-are-pure.php diff --git a/bin/functionMetadata_original.php b/bin/functionMetadata_original.php index 78ac65db296..1f386e0760d 100644 --- a/bin/functionMetadata_original.php +++ b/bin/functionMetadata_original.php @@ -299,6 +299,168 @@ 'DateTimeImmutable::getTimestamp' => ['hasSideEffects' => false], 'DateTimeImmutable::getTimezone' => ['hasSideEffects' => false], + 'Redis::append' => ['hasSideEffects' => true], + 'Redis::bitcount' => ['hasSideEffects' => true], + 'Redis::bitop' => ['hasSideEffects' => true], + 'Redis::bitpos' => ['hasSideEffects' => true], + 'Redis::blPop' => ['hasSideEffects' => true], + 'Redis::blmove' => ['hasSideEffects' => true], + 'Redis::blmpop' => ['hasSideEffects' => true], + 'Redis::brPop' => ['hasSideEffects' => true], + 'Redis::brpoplpush' => ['hasSideEffects' => true], + 'Redis::bzmpop' => ['hasSideEffects' => true], + 'Redis::bzPopMax' => ['hasSideEffects' => true], + 'Redis::bzPopMin' => ['hasSideEffects' => true], + 'Redis::connect' => ['hasSideEffects' => true], + 'Redis::dbSize' => ['hasSideEffects' => true], + 'Redis::decr' => ['hasSideEffects' => true], + 'Redis::decrBy' => ['hasSideEffects' => true], + 'Redis::del' => ['hasSideEffects' => true], + 'Redis::delete' => ['hasSideEffects' => true], + 'Redis::exists' => ['hasSideEffects' => true], + 'Redis::expire' => ['hasSideEffects' => true], + 'Redis::expireAt' => ['hasSideEffects' => true], + 'Redis::expiretime' => ['hasSideEffects' => true], + 'Redis::flushAll' => ['hasSideEffects' => true], + 'Redis::flushDB' => ['hasSideEffects' => true], + 'Redis::function' => ['hasSideEffects' => true], + 'Redis::geoadd' => ['hasSideEffects' => true], + 'Redis::geodist' => ['hasSideEffects' => true], + 'Redis::geohash' => ['hasSideEffects' => true], + 'Redis::geopos' => ['hasSideEffects' => true], + 'Redis::georadius' => ['hasSideEffects' => true], + 'Redis::georadiusbymember' => ['hasSideEffects' => true], + 'Redis::georadiusbymember_ro' => ['hasSideEffects' => true], + 'Redis::geosearch' => ['hasSideEffects' => true], + 'Redis::geosearchstore' => ['hasSideEffects' => true], + 'Redis::get' => ['hasSideEffects' => true], + 'Redis::getBit' => ['hasSideEffects' => true], + 'Redis::getEx' => ['hasSideEffects' => true], + 'Redis::getDBNum' => ['hasSideEffects' => true], + 'Redis::getDel' => ['hasSideEffects' => true], + 'Redis::getLastError' => ['hasSideEffects' => true], + 'Redis::getMode' => ['hasSideEffects' => true], + 'Redis::getOption' => ['hasSideEffects' => true], + 'Redis::getPersistentID' => ['hasSideEffects' => true], + 'Redis::getRange' => ['hasSideEffects' => true], + 'Redis::lcs' => ['hasSideEffects' => true], + 'Redis::lmpop' => ['hasSideEffects' => true], + 'Redis::getReadTimeout' => ['hasSideEffects' => true], + 'Redis::getset' => ['hasSideEffects' => true], + 'Redis::getTimeout' => ['hasSideEffects' => true], + 'Redis::getTransferredBytes' => ['hasSideEffects' => true], + 'Redis::hDel' => ['hasSideEffects' => true], + 'Redis::hExists' => ['hasSideEffects' => true], + 'Redis::hGet' => ['hasSideEffects' => true], + 'Redis::hGetAll' => ['hasSideEffects' => true], + 'Redis::hIncrBy' => ['hasSideEffects' => true], + 'Redis::hIncrByFloat' => ['hasSideEffects' => true], + 'Redis::hKeys' => ['hasSideEffects' => true], + 'Redis::hLen' => ['hasSideEffects' => true], + 'Redis::hMget' => ['hasSideEffects' => true], + 'Redis::hMset' => ['hasSideEffects' => true], + 'Redis::hRandField' => ['hasSideEffects' => true], + 'Redis::hscan' => ['hasSideEffects' => true], + 'Redis::hSet' => ['hasSideEffects' => true], + 'Redis::hSetNx' => ['hasSideEffects' => true], + 'Redis::hStrLen' => ['hasSideEffects' => true], + 'Redis::hVals' => ['hasSideEffects' => true], + 'Redis::incr' => ['hasSideEffects' => true], + 'Redis::incrBy' => ['hasSideEffects' => true], + 'Redis::incrByFloat' => ['hasSideEffects' => true], + 'Redis::isConnected' => ['hasSideEffects' => true], + 'Redis::keys' => ['hasSideEffects' => true], + 'Redis::lastSave' => ['hasSideEffects' => true], + 'Redis::lInsert' => ['hasSideEffects' => true], + 'Redis::lLen' => ['hasSideEffects' => true], + 'Redis::lMove' => ['hasSideEffects' => true], + 'Redis::lPop' => ['hasSideEffects' => true], + 'Redis::lPos' => ['hasSideEffects' => true], + 'Redis::lPush' => ['hasSideEffects' => true], + 'Redis::lPushx' => ['hasSideEffects' => true], + 'Redis::lSet' => ['hasSideEffects' => true], + 'Redis::lindex' => ['hasSideEffects' => true], + 'Redis::lrange' => ['hasSideEffects' => true], + 'Redis::lrem' => ['hasSideEffects' => true], + 'Redis::ltrim' => ['hasSideEffects' => true], + 'Redis::mget' => ['hasSideEffects' => true], + 'Redis::move' => ['hasSideEffects' => true], + 'Redis::mset' => ['hasSideEffects' => true], + 'Redis::msetnx' => ['hasSideEffects' => true], + 'Redis::pconnect' => ['hasSideEffects' => true], + 'Redis::persist' => ['hasSideEffects' => true], + 'Redis::pexpire' => ['hasSideEffects' => true], + 'Redis::pexpireAt' => ['hasSideEffects' => true], + 'Redis::pexpiretime' => ['hasSideEffects' => true], + 'Redis::rpoplpush' => ['hasSideEffects' => true], + 'Redis::rPush' => ['hasSideEffects' => true], + 'Redis::rPushx' => ['hasSideEffects' => true], + 'Redis::sAdd' => ['hasSideEffects' => true], + 'Redis::sAddArray' => ['hasSideEffects' => true], + 'Redis::scan' => ['hasSideEffects' => true], + 'Redis::scard' => ['hasSideEffects' => true], + 'Redis::script' => ['hasSideEffects' => true], + 'Redis::sDiff' => ['hasSideEffects' => true], + 'Redis::sDiffStore' => ['hasSideEffects' => true], + 'Redis::set' => ['hasSideEffects' => true], + 'Redis::setBit' => ['hasSideEffects' => true], + 'Redis::setRange' => ['hasSideEffects' => true], + 'Redis::setOption' => ['hasSideEffects' => true], + 'Redis::setex' => ['hasSideEffects' => true], + 'Redis::setnx' => ['hasSideEffects' => true], + 'Redis::sInter' => ['hasSideEffects' => true], + 'Redis::sintercard' => ['hasSideEffects' => true], + 'Redis::sInterStore' => ['hasSideEffects' => true], + 'Redis::sismember' => ['hasSideEffects' => true], + 'Redis::sMembers' => ['hasSideEffects' => true], + 'Redis::sMisMember' => ['hasSideEffects' => true], + 'Redis::sMove' => ['hasSideEffects' => true], + 'Redis::sPop' => ['hasSideEffects' => true], + 'Redis::sort' => ['hasSideEffects' => true], + 'Redis::sort_ro' => ['hasSideEffects' => true], + 'Redis::sRandMember' => ['hasSideEffects' => true], + 'Redis::srem' => ['hasSideEffects' => true], + 'Redis::sscan' => ['hasSideEffects' => true], + 'Redis::sUnion' => ['hasSideEffects' => true], + 'Redis::sUnionStore' => ['hasSideEffects' => true], + 'Redis::time' => ['hasSideEffects' => true], + 'Redis::touch' => ['hasSideEffects' => true], + 'Redis::ttl' => ['hasSideEffects' => true], + 'Redis::type' => ['hasSideEffects' => true], + 'Redis::unlink' => ['hasSideEffects' => true], + 'Redis::zAdd' => ['hasSideEffects' => true], + 'Redis::zCard' => ['hasSideEffects' => true], + 'Redis::zCount' => ['hasSideEffects' => true], + 'Redis::zdiff' => ['hasSideEffects' => true], + 'Redis::zdiffstore' => ['hasSideEffects' => true], + 'Redis::zIncrBy' => ['hasSideEffects' => true], + 'Redis::zinter' => ['hasSideEffects' => true], + 'Redis::zintercard' => ['hasSideEffects' => true], + 'Redis::zinterstore' => ['hasSideEffects' => true], + 'Redis::zLexCount' => ['hasSideEffects' => true], + 'Redis::zmpop' => ['hasSideEffects' => true], + 'Redis::zMscore' => ['hasSideEffects' => true], + 'Redis::zPopMax' => ['hasSideEffects' => true], + 'Redis::zPopMin' => ['hasSideEffects' => true], + 'Redis::zRange' => ['hasSideEffects' => true], + 'Redis::zRangeByLex' => ['hasSideEffects' => true], + 'Redis::zRangeByScore' => ['hasSideEffects' => true], + 'Redis::zrangestore' => ['hasSideEffects' => true], + 'Redis::zRandMember' => ['hasSideEffects' => true], + 'Redis::zRank' => ['hasSideEffects' => true], + 'Redis::zRem' => ['hasSideEffects' => true], + 'Redis::zRemRangeByLex' => ['hasSideEffects' => true], + 'Redis::zRemRangeByRank' => ['hasSideEffects' => true], + 'Redis::zRemRangeByScore' => ['hasSideEffects' => true], + 'Redis::zRevRange' => ['hasSideEffects' => true], + 'Redis::zRevRangeByLex' => ['hasSideEffects' => true], + 'Redis::zRevRangeByScore' => ['hasSideEffects' => true], + 'Redis::zRevRank' => ['hasSideEffects' => true], + 'Redis::zscan' => ['hasSideEffects' => true], + 'Redis::zScore' => ['hasSideEffects' => true], + 'Redis::zunion' => ['hasSideEffects' => true], + 'Redis::zunionstore' => ['hasSideEffects' => true], + 'SplDoublyLinkedList::pop' => ['hasSideEffects' => true], 'SplDoublyLinkedList::shift' => ['hasSideEffects' => true], diff --git a/conf/config.neon b/conf/config.neon index 6c71dae9225..d9f02ebcc36 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -126,7 +126,6 @@ parameters: - stdClass stubFiles: - ../stubs/Memcached.stub - - ../stubs/Redis.stub - ../stubs/ReflectionAttribute.stub - ../stubs/ReflectionClassConstant.stub - ../stubs/ReflectionFunctionAbstract.stub diff --git a/resources/functionMetadata.php b/resources/functionMetadata.php index 4e50a9f80f3..51ff3ef411a 100644 --- a/resources/functionMetadata.php +++ b/resources/functionMetadata.php @@ -452,6 +452,167 @@ 'NumberFormatter::getPattern' => ['hasSideEffects' => false], 'NumberFormatter::getSymbol' => ['hasSideEffects' => false], 'NumberFormatter::getTextAttribute' => ['hasSideEffects' => false], + 'Redis::append' => ['hasSideEffects' => true], + 'Redis::bitcount' => ['hasSideEffects' => true], + 'Redis::bitop' => ['hasSideEffects' => true], + 'Redis::bitpos' => ['hasSideEffects' => true], + 'Redis::blPop' => ['hasSideEffects' => true], + 'Redis::blmove' => ['hasSideEffects' => true], + 'Redis::blmpop' => ['hasSideEffects' => true], + 'Redis::brPop' => ['hasSideEffects' => true], + 'Redis::brpoplpush' => ['hasSideEffects' => true], + 'Redis::bzPopMax' => ['hasSideEffects' => true], + 'Redis::bzPopMin' => ['hasSideEffects' => true], + 'Redis::bzmpop' => ['hasSideEffects' => true], + 'Redis::connect' => ['hasSideEffects' => true], + 'Redis::dbSize' => ['hasSideEffects' => true], + 'Redis::decr' => ['hasSideEffects' => true], + 'Redis::decrBy' => ['hasSideEffects' => true], + 'Redis::del' => ['hasSideEffects' => true], + 'Redis::delete' => ['hasSideEffects' => true], + 'Redis::exists' => ['hasSideEffects' => true], + 'Redis::expire' => ['hasSideEffects' => true], + 'Redis::expireAt' => ['hasSideEffects' => true], + 'Redis::expiretime' => ['hasSideEffects' => true], + 'Redis::flushAll' => ['hasSideEffects' => true], + 'Redis::flushDB' => ['hasSideEffects' => true], + 'Redis::function' => ['hasSideEffects' => true], + 'Redis::geoadd' => ['hasSideEffects' => true], + 'Redis::geodist' => ['hasSideEffects' => true], + 'Redis::geohash' => ['hasSideEffects' => true], + 'Redis::geopos' => ['hasSideEffects' => true], + 'Redis::georadius' => ['hasSideEffects' => true], + 'Redis::georadiusbymember' => ['hasSideEffects' => true], + 'Redis::georadiusbymember_ro' => ['hasSideEffects' => true], + 'Redis::geosearch' => ['hasSideEffects' => true], + 'Redis::geosearchstore' => ['hasSideEffects' => true], + 'Redis::get' => ['hasSideEffects' => true], + 'Redis::getBit' => ['hasSideEffects' => true], + 'Redis::getDBNum' => ['hasSideEffects' => true], + 'Redis::getDel' => ['hasSideEffects' => true], + 'Redis::getEx' => ['hasSideEffects' => true], + 'Redis::getLastError' => ['hasSideEffects' => true], + 'Redis::getMode' => ['hasSideEffects' => true], + 'Redis::getOption' => ['hasSideEffects' => true], + 'Redis::getPersistentID' => ['hasSideEffects' => true], + 'Redis::getRange' => ['hasSideEffects' => true], + 'Redis::getReadTimeout' => ['hasSideEffects' => true], + 'Redis::getTimeout' => ['hasSideEffects' => true], + 'Redis::getTransferredBytes' => ['hasSideEffects' => true], + 'Redis::getset' => ['hasSideEffects' => true], + 'Redis::hDel' => ['hasSideEffects' => true], + 'Redis::hExists' => ['hasSideEffects' => true], + 'Redis::hGet' => ['hasSideEffects' => true], + 'Redis::hGetAll' => ['hasSideEffects' => true], + 'Redis::hIncrBy' => ['hasSideEffects' => true], + 'Redis::hIncrByFloat' => ['hasSideEffects' => true], + 'Redis::hKeys' => ['hasSideEffects' => true], + 'Redis::hLen' => ['hasSideEffects' => true], + 'Redis::hMget' => ['hasSideEffects' => true], + 'Redis::hMset' => ['hasSideEffects' => true], + 'Redis::hRandField' => ['hasSideEffects' => true], + 'Redis::hSet' => ['hasSideEffects' => true], + 'Redis::hSetNx' => ['hasSideEffects' => true], + 'Redis::hStrLen' => ['hasSideEffects' => true], + 'Redis::hVals' => ['hasSideEffects' => true], + 'Redis::hscan' => ['hasSideEffects' => true], + 'Redis::incr' => ['hasSideEffects' => true], + 'Redis::incrBy' => ['hasSideEffects' => true], + 'Redis::incrByFloat' => ['hasSideEffects' => true], + 'Redis::isConnected' => ['hasSideEffects' => true], + 'Redis::keys' => ['hasSideEffects' => true], + 'Redis::lInsert' => ['hasSideEffects' => true], + 'Redis::lLen' => ['hasSideEffects' => true], + 'Redis::lMove' => ['hasSideEffects' => true], + 'Redis::lPop' => ['hasSideEffects' => true], + 'Redis::lPos' => ['hasSideEffects' => true], + 'Redis::lPush' => ['hasSideEffects' => true], + 'Redis::lPushx' => ['hasSideEffects' => true], + 'Redis::lSet' => ['hasSideEffects' => true], + 'Redis::lastSave' => ['hasSideEffects' => true], + 'Redis::lcs' => ['hasSideEffects' => true], + 'Redis::lindex' => ['hasSideEffects' => true], + 'Redis::lmpop' => ['hasSideEffects' => true], + 'Redis::lrange' => ['hasSideEffects' => true], + 'Redis::lrem' => ['hasSideEffects' => true], + 'Redis::ltrim' => ['hasSideEffects' => true], + 'Redis::mget' => ['hasSideEffects' => true], + 'Redis::move' => ['hasSideEffects' => true], + 'Redis::mset' => ['hasSideEffects' => true], + 'Redis::msetnx' => ['hasSideEffects' => true], + 'Redis::pconnect' => ['hasSideEffects' => true], + 'Redis::persist' => ['hasSideEffects' => true], + 'Redis::pexpire' => ['hasSideEffects' => true], + 'Redis::pexpireAt' => ['hasSideEffects' => true], + 'Redis::pexpiretime' => ['hasSideEffects' => true], + 'Redis::rPush' => ['hasSideEffects' => true], + 'Redis::rPushx' => ['hasSideEffects' => true], + 'Redis::rpoplpush' => ['hasSideEffects' => true], + 'Redis::sAdd' => ['hasSideEffects' => true], + 'Redis::sAddArray' => ['hasSideEffects' => true], + 'Redis::sDiff' => ['hasSideEffects' => true], + 'Redis::sDiffStore' => ['hasSideEffects' => true], + 'Redis::sInter' => ['hasSideEffects' => true], + 'Redis::sInterStore' => ['hasSideEffects' => true], + 'Redis::sMembers' => ['hasSideEffects' => true], + 'Redis::sMisMember' => ['hasSideEffects' => true], + 'Redis::sMove' => ['hasSideEffects' => true], + 'Redis::sPop' => ['hasSideEffects' => true], + 'Redis::sRandMember' => ['hasSideEffects' => true], + 'Redis::sUnion' => ['hasSideEffects' => true], + 'Redis::sUnionStore' => ['hasSideEffects' => true], + 'Redis::scan' => ['hasSideEffects' => true], + 'Redis::scard' => ['hasSideEffects' => true], + 'Redis::script' => ['hasSideEffects' => true], + 'Redis::set' => ['hasSideEffects' => true], + 'Redis::setBit' => ['hasSideEffects' => true], + 'Redis::setOption' => ['hasSideEffects' => true], + 'Redis::setRange' => ['hasSideEffects' => true], + 'Redis::setex' => ['hasSideEffects' => true], + 'Redis::setnx' => ['hasSideEffects' => true], + 'Redis::sintercard' => ['hasSideEffects' => true], + 'Redis::sismember' => ['hasSideEffects' => true], + 'Redis::sort' => ['hasSideEffects' => true], + 'Redis::sort_ro' => ['hasSideEffects' => true], + 'Redis::srem' => ['hasSideEffects' => true], + 'Redis::sscan' => ['hasSideEffects' => true], + 'Redis::time' => ['hasSideEffects' => true], + 'Redis::touch' => ['hasSideEffects' => true], + 'Redis::ttl' => ['hasSideEffects' => true], + 'Redis::type' => ['hasSideEffects' => true], + 'Redis::unlink' => ['hasSideEffects' => true], + 'Redis::zAdd' => ['hasSideEffects' => true], + 'Redis::zCard' => ['hasSideEffects' => true], + 'Redis::zCount' => ['hasSideEffects' => true], + 'Redis::zIncrBy' => ['hasSideEffects' => true], + 'Redis::zLexCount' => ['hasSideEffects' => true], + 'Redis::zMscore' => ['hasSideEffects' => true], + 'Redis::zPopMax' => ['hasSideEffects' => true], + 'Redis::zPopMin' => ['hasSideEffects' => true], + 'Redis::zRandMember' => ['hasSideEffects' => true], + 'Redis::zRange' => ['hasSideEffects' => true], + 'Redis::zRangeByLex' => ['hasSideEffects' => true], + 'Redis::zRangeByScore' => ['hasSideEffects' => true], + 'Redis::zRank' => ['hasSideEffects' => true], + 'Redis::zRem' => ['hasSideEffects' => true], + 'Redis::zRemRangeByLex' => ['hasSideEffects' => true], + 'Redis::zRemRangeByRank' => ['hasSideEffects' => true], + 'Redis::zRemRangeByScore' => ['hasSideEffects' => true], + 'Redis::zRevRange' => ['hasSideEffects' => true], + 'Redis::zRevRangeByLex' => ['hasSideEffects' => true], + 'Redis::zRevRangeByScore' => ['hasSideEffects' => true], + 'Redis::zRevRank' => ['hasSideEffects' => true], + 'Redis::zScore' => ['hasSideEffects' => true], + 'Redis::zdiff' => ['hasSideEffects' => true], + 'Redis::zdiffstore' => ['hasSideEffects' => true], + 'Redis::zinter' => ['hasSideEffects' => true], + 'Redis::zintercard' => ['hasSideEffects' => true], + 'Redis::zinterstore' => ['hasSideEffects' => true], + 'Redis::zmpop' => ['hasSideEffects' => true], + 'Redis::zrangestore' => ['hasSideEffects' => true], + 'Redis::zscan' => ['hasSideEffects' => true], + 'Redis::zunion' => ['hasSideEffects' => true], + 'Redis::zunionstore' => ['hasSideEffects' => true], 'ReflectionAttribute::getArguments' => ['hasSideEffects' => false], 'ReflectionAttribute::getName' => ['hasSideEffects' => false], 'ReflectionAttribute::getTarget' => ['hasSideEffects' => false], diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index cbed5105aeb..382f31c00c8 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -4694,23 +4694,6 @@ public function getPhpDocs(Scope $scope, Node\FunctionLike|Node\Stmt\Property $n $acceptsNamedArguments = $scope->getClassReflection()->acceptsNamedArguments(); } - if ($isPure === null && $node instanceof Node\FunctionLike && $scope->isInClass()) { - $classResolvedPhpDoc = $scope->getClassReflection()->getResolvedPhpDoc(); - if ($classResolvedPhpDoc !== null && $classResolvedPhpDoc->areAllMethodsPure()) { - if ( - strtolower($functionName ?? '') === '__construct' - || ( - ($phpDocReturnType === null || !$phpDocReturnType->isVoid()->yes()) - && !$scope->getFunctionType($node->getReturnType(), false, false)->isVoid()->yes() - ) - ) { - $isPure = true; - } - } elseif ($classResolvedPhpDoc !== null && $classResolvedPhpDoc->areAllMethodsImpure()) { - $isPure = false; - } - } - return [$templateTypeMap, $phpDocParameterTypes, $phpDocImmediatelyInvokedCallableParameters, $phpDocClosureThisTypeParameters, $phpDocReturnType, $phpDocThrowType, $deprecatedDescription, $isDeprecated, $isInternal, $isFinal, $isPure, $acceptsNamedArguments, $isReadOnly, $docComment, $asserts, $selfOutType, $phpDocParameterOutTypes, $varTags, $isAllowedPrivateMutation, $resolvedPhpDoc]; } diff --git a/src/PhpDoc/PhpDocNodeResolver.php b/src/PhpDoc/PhpDocNodeResolver.php index 1e88dce58aa..af34edd5052 100644 --- a/src/PhpDoc/PhpDocNodeResolver.php +++ b/src/PhpDoc/PhpDocNodeResolver.php @@ -673,16 +673,6 @@ public function resolveIsImpure(PhpDocNode $phpDocNode): bool return false; } - public function resolveAllMethodsPure(PhpDocNode $phpDocNode): bool - { - return count($phpDocNode->getTagsByName('@phpstan-all-methods-pure')) > 0; - } - - public function resolveAllMethodsImpure(PhpDocNode $phpDocNode): bool - { - return count($phpDocNode->getTagsByName('@phpstan-all-methods-impure')) > 0; - } - public function resolveIsReadOnly(PhpDocNode $phpDocNode): bool { foreach (['@readonly', '@phan-read-only', '@psalm-readonly', '@phpstan-readonly', '@phpstan-readonly-allow-private-mutation', '@psalm-readonly-allow-private-mutation'] as $tagName) { diff --git a/src/PhpDoc/ResolvedPhpDocBlock.php b/src/PhpDoc/ResolvedPhpDocBlock.php index be6da4fc46d..b2533223b67 100644 --- a/src/PhpDoc/ResolvedPhpDocBlock.php +++ b/src/PhpDoc/ResolvedPhpDocBlock.php @@ -139,10 +139,6 @@ final class ResolvedPhpDocBlock /** @var bool|'notLoaded'|null */ private bool|string|null $isPure = 'notLoaded'; - private ?bool $areAllMethodsPure = null; - - private ?bool $areAllMethodsImpure = null; - private ?bool $isReadOnly = null; private ?bool $isImmutable = null; @@ -237,8 +233,6 @@ public static function createEmpty(): self $self->isInternal = false; $self->isFinal = false; $self->isPure = null; - $self->areAllMethodsPure = false; - $self->areAllMethodsImpure = false; $self->isReadOnly = false; $self->isImmutable = false; $self->isAllowedPrivateMutation = false; @@ -293,8 +287,6 @@ public function merge(ResolvedPhpDocBlock $parent, InheritedPhpDocParameterMappi $result->isInternal = $this->isInternal(); $result->isFinal = $this->isFinal(); $result->isPure = self::mergePureTags($this->isPure(), $parent); - $result->areAllMethodsPure = $this->areAllMethodsPure(); - $result->areAllMethodsImpure = $this->areAllMethodsImpure(); $result->isReadOnly = $this->isReadOnly(); $result->isImmutable = $this->isImmutable(); $result->isAllowedPrivateMutation = $this->isAllowedPrivateMutation(); @@ -824,20 +816,6 @@ public function isPure(): ?bool return $this->isPure; } - public function areAllMethodsPure(): bool - { - return $this->areAllMethodsPure ??= $this->phpDocNodeResolver->resolveAllMethodsPure( - $this->phpDocNode, - ); - } - - public function areAllMethodsImpure(): bool - { - return $this->areAllMethodsImpure ??= $this->phpDocNodeResolver->resolveAllMethodsImpure( - $this->phpDocNode, - ); - } - public function isReadOnly(): bool { return $this->isReadOnly ??= $this->phpDocNodeResolver->resolveIsReadOnly( diff --git a/src/Reflection/Php/PhpClassReflectionExtension.php b/src/Reflection/Php/PhpClassReflectionExtension.php index ba91e7567fb..22517d447f6 100644 --- a/src/Reflection/Php/PhpClassReflectionExtension.php +++ b/src/Reflection/Php/PhpClassReflectionExtension.php @@ -927,22 +927,6 @@ public function createUserlandMethodReflection(ClassReflection $fileDeclaringCla $isInternal = $resolvedPhpDoc->isInternal(); $isFinal = $resolvedPhpDoc->isFinal(); $isPure ??= $resolvedPhpDoc->isPure(); - if ($isPure === null) { - $classResolvedPhpDoc = $phpDocBlockClassReflection->getResolvedPhpDoc(); - if ($classResolvedPhpDoc !== null && $classResolvedPhpDoc->areAllMethodsPure()) { - if ( - strtolower($methodReflection->getName()) === '__construct' - || ( - ($phpDocReturnType === null || !$phpDocReturnType->isVoid()->yes()) - && !$nativeReturnType->isVoid()->yes() - ) - ) { - $isPure = true; - } - } elseif ($classResolvedPhpDoc !== null && $classResolvedPhpDoc->areAllMethodsImpure()) { - $isPure = false; - } - } $asserts = Assertions::createFromResolvedPhpDocBlock($resolvedPhpDoc); $acceptsNamedArguments = $resolvedPhpDoc->acceptsNamedArguments(); $selfOutType = $resolvedPhpDoc->getSelfOutTag() !== null ? $resolvedPhpDoc->getSelfOutTag()->getType() : null; diff --git a/src/Rules/PhpDoc/InvalidPHPStanDocTagRule.php b/src/Rules/PhpDoc/InvalidPHPStanDocTagRule.php index fcd5702fafe..b7687c6678e 100644 --- a/src/Rules/PhpDoc/InvalidPHPStanDocTagRule.php +++ b/src/Rules/PhpDoc/InvalidPHPStanDocTagRule.php @@ -64,8 +64,6 @@ final class InvalidPHPStanDocTagRule implements Rule '@phpstan-param-immediately-invoked-callable', '@phpstan-param-later-invoked-callable', '@phpstan-param-closure-this', - '@phpstan-all-methods-pure', - '@phpstan-all-methods-impure', ]; public function __construct( diff --git a/stubs/Redis.stub b/stubs/Redis.stub deleted file mode 100644 index 072475482fc..00000000000 --- a/stubs/Redis.stub +++ /dev/null @@ -1,11 +0,0 @@ -analyse([__DIR__ . '/data/bug-11485.php'], []); } - public function testBug10215(): void - { - $this->analyse([__DIR__ . '/data/bug-10215.php'], []); - } - public function testBug12946(): void { $this->analyse([__DIR__ . '/data/bug-12946.php'], []); diff --git a/tests/PHPStan/Rules/Comparison/data/bug-10215.php b/tests/PHPStan/Rules/Comparison/data/bug-10215.php deleted file mode 100644 index 50accb912c5..00000000000 --- a/tests/PHPStan/Rules/Comparison/data/bug-10215.php +++ /dev/null @@ -1,23 +0,0 @@ -= 8.1 - -namespace Bug10215; - -class CacheManager -{ - public function __construct(private readonly \Redis $redis) - { - } - - public function getCachedValue(string $key, callable $callback): int - { - if (false !== ($value = $this->redis->get($key))) { - return (int) $value; - } - $callback(); - if (false !== ($value = $this->redis->get($key))) { - return (int) $value; - } - - throw new \LogicException('Cache was not filled by callback'); - } -} diff --git a/tests/PHPStan/Rules/Pure/PureMethodRuleTest.php b/tests/PHPStan/Rules/Pure/PureMethodRuleTest.php index 8e1d6689769..905117917f2 100644 --- a/tests/PHPStan/Rules/Pure/PureMethodRuleTest.php +++ b/tests/PHPStan/Rules/Pure/PureMethodRuleTest.php @@ -284,69 +284,6 @@ public function testBug12224(): void ]); } - public function testAllMethodsArePure(): void - { - $this->treatPhpDocTypesAsCertain = true; - $this->analyse([__DIR__ . '/data/all-methods-are-pure.php'], [ - [ - 'Method AllMethodsArePure\Foo::pureVoid() is marked as pure but returns void.', - 30, - ], - [ - 'Method AllMethodsArePure\Foo::impure() is marked as impure but does not have any side effects.', - 37, - ], - [ - 'Method AllMethodsArePure\Foo::impureVoid() is marked as impure but does not have any side effects.', - 45, - ], - [ - 'Method AllMethodsArePure\Bar::test() is marked as impure but does not have any side effects.', - 55, - ], - [ - 'Method AllMethodsArePure\Bar::testVoid() is marked as impure but does not have any side effects.', - 60, - ], - [ - 'Method AllMethodsArePure\Bar::pureVoid() is marked as pure but returns void.', - 75, - ], - [ - 'Method AllMethodsArePure\Bar::impure() is marked as impure but does not have any side effects.', - 82, - ], - [ - 'Method AllMethodsArePure\Bar::impureVoid() is marked as impure but does not have any side effects.', - 90, - ], - [ - 'Impure call to method AllMethodsArePure\Test::impure() in pure method AllMethodsArePure\SideEffectPure::nothingWithImpure().', - 120, - ], - [ - 'Method AllMethodsArePure\SideEffectPure::impureWithPure() is marked as impure but does not have any side effects.', - 130, - ], - [ - 'Method AllMethodsArePure\SideEffectImpure::nothingWithPure() is marked as impure but does not have any side effects.', - 143, - ], - [ - 'Impure call to method AllMethodsArePure\Test::impure() in pure method AllMethodsArePure\SideEffectImpure::pureWithImpure().', - 148, - ], - [ - 'Impure call to method AllMethodsArePure\FooWithMixed::impureMethod() in pure method AllMethodsArePure\FooWithMixed::testMixed().', - 163, - ], - [ - 'Impure call to method AllMethodsArePure\FooWithMixed::impureMethod() in pure method AllMethodsArePure\FooWithMixed::testMixed2().', - 171, - ], - ]); - } - public function testBug12382(): void { $this->treatPhpDocTypesAsCertain = true; diff --git a/tests/PHPStan/Rules/Pure/data/all-methods-are-pure.php b/tests/PHPStan/Rules/Pure/data/all-methods-are-pure.php deleted file mode 100644 index 03ee1cf0888..00000000000 --- a/tests/PHPStan/Rules/Pure/data/all-methods-are-pure.php +++ /dev/null @@ -1,180 +0,0 @@ -impureMethod(); - } - - /** - * @return mixed - */ - public function testMixed2() - { - return $this->impureMethod(); - } - - /** - * @phpstan-impure - */ - public function impureMethod(): mixed { - return time(); - } -} From e7ea21c095ecd604ca96473a2662db3277c310dc Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 4 May 2026 11:42:52 +0200 Subject: [PATCH 7/9] Revert "Rework phpDoc inheritance to resolve through reflection instead of re-walking the hierarchy" This reverts commit 617a3a8a53746276bfc51661cfae3df6da208fab. --- src/Analyser/MutatingScope.php | 5 - src/Analyser/NodeScopeResolver.php | 31 +- .../InheritedPhpDocParameterMapping.php | 67 --- src/PhpDoc/PhpDocBlock.php | 391 ++++++++++++++++++ src/PhpDoc/PhpDocInheritanceResolver.php | 328 +++++---------- src/PhpDoc/ResolvedPhpDocBlock.php | 277 +++++++++---- .../AnnotationMethodReflection.php | 6 - src/Reflection/ClassConstantReflection.php | 3 - src/Reflection/ClassReflection.php | 50 ++- .../Dummy/ChangedTypeMethodReflection.php | 6 - .../Dummy/DummyClassConstantReflection.php | 6 - .../Dummy/DummyConstructorReflection.php | 6 - .../Dummy/DummyMethodReflection.php | 6 - src/Reflection/ExtendedMethodReflection.php | 3 - .../Native/NativeMethodReflection.php | 7 - .../Php/ClosureCallMethodReflection.php | 6 - .../Php/EnumCasesMethodReflection.php | 6 - .../Php/PhpClassReflectionExtension.php | 369 +++++++++-------- .../Php/PhpMethodFromParserNodeReflection.php | 7 - src/Reflection/Php/PhpMethodReflection.php | 9 - .../Php/PhpMethodReflectionFactory.php | 2 - src/Reflection/Php/PhpPropertyReflection.php | 7 - .../RealClassClassConstantReflection.php | 7 - src/Reflection/ResolvedMethodReflection.php | 16 +- .../Type/IntersectionTypeMethodReflection.php | 6 - .../Type/UnionTypeMethodReflection.php | 6 - .../WrappedExtendedMethodReflection.php | 6 - ...nDeclaringClassClassConstantReflection.php | 6 - ...ewrittenDeclaringClassMethodReflection.php | 6 - tests/PHPStan/Analyser/AnalyserTest.php | 3 +- .../data/overriding-constant-native-types.php | 19 - 31 files changed, 929 insertions(+), 744 deletions(-) delete mode 100644 src/PhpDoc/InheritedPhpDocParameterMapping.php create mode 100644 src/PhpDoc/PhpDocBlock.php diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 310c280c2a5..8d431cb312e 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -41,7 +41,6 @@ use PHPStan\Php\PhpVersion; use PHPStan\Php\PhpVersionFactory; use PHPStan\Php\PhpVersions; -use PHPStan\PhpDoc\ResolvedPhpDocBlock; use PHPStan\Reflection\Assertions; use PHPStan\Reflection\AttributeReflection; use PHPStan\Reflection\AttributeReflectionFactory; @@ -1492,7 +1491,6 @@ public function enterClassMethod( array $immediatelyInvokedCallableParameters = [], array $phpDocClosureThisTypeParameters = [], bool $isConstructor = false, - ?ResolvedPhpDocBlock $resolvedPhpDocBlock = null, ): self { if (!$this->isInClass()) { @@ -1522,7 +1520,6 @@ public function enterClassMethod( $asserts ?? Assertions::createEmpty(), $selfOutType, $phpDocComment, - $resolvedPhpDocBlock, array_map(fn (Type $type): Type => $this->transformStaticType(TemplateTypeHelper::toArgument($type)), $parameterOutTypes), $immediatelyInvokedCallableParameters, array_map(fn (Type $type): Type => $this->transformStaticType(TemplateTypeHelper::toArgument($type)), $phpDocClosureThisTypeParameters), @@ -1546,7 +1543,6 @@ public function enterPropertyHook( ?string $deprecatedDescription, bool $isDeprecated, ?string $phpDocComment, - ?ResolvedPhpDocBlock $resolvedPhpDocBlock = null, ): self { if (!$this->isInClass()) { @@ -1611,7 +1607,6 @@ public function enterPropertyHook( Assertions::createEmpty(), null, $phpDocComment, - $resolvedPhpDocBlock, [], [], [], diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 382f31c00c8..0b3b4cee7ef 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -3129,7 +3129,7 @@ private function processPropertyHooks( $this->callNodeCallback($nodeCallback, $hook, $scope, $storage); $this->processAttributeGroups($stmt, $hook->attrGroups, $scope, $storage, $nodeCallback); - [, $phpDocParameterTypes,,,, $phpDocThrowType,,,,,,,, $phpDocComment,,,,,, $resolvedPhpDoc] = $this->getPhpDocs($scope, $hook); + [, $phpDocParameterTypes,,,, $phpDocThrowType,,,,,,,, $phpDocComment] = $this->getPhpDocs($scope, $hook); foreach ($hook->params as $param) { $this->processParamNode($stmt, $param, $scope, $storage, $nodeCallback); @@ -3147,7 +3147,6 @@ private function processPropertyHooks( $deprecatedDescription, $isDeprecated, $phpDocComment, - $resolvedPhpDoc, ); $hookReflection = $hookScope->getFunction(); if (!$hookReflection instanceof PhpMethodFromParserNodeReflection) { @@ -4525,7 +4524,7 @@ private function processNodesForCalledMethod($node, ExpressionResultStorage $sto } /** - * @return array{TemplateTypeMap, array, array, array, ?Type, ?Type, ?string, bool, bool, bool, bool|null, bool, bool, string|null, Assertions, ?Type, array, array<(string|int), VarTag>, bool, ?ResolvedPhpDocBlock} + * @return array{TemplateTypeMap, array, array, array, ?Type, ?Type, ?string, bool, bool, bool, bool|null, bool, bool, string|null, Assertions, ?Type, array, array<(string|int), VarTag>, bool} */ public function getPhpDocs(Scope $scope, Node\FunctionLike|Node\Stmt\Property $node): array { @@ -4568,21 +4567,12 @@ public function getPhpDocs(Scope $scope, Node\FunctionLike|Node\Stmt\Property $n return $param->var->name; }, $node->getParams()); - $currentResolvedPhpDoc = null; - if ($docComment !== null) { - $currentResolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( - $file, - $class, - $trait, - $node->name->name, - $docComment, - ); - } - $methodNameForInheritance = $node->getAttribute('originalTraitMethodName') ?? $node->name->name; $resolvedPhpDoc = $this->phpDocInheritanceResolver->resolvePhpDocForMethod( + $docComment, + $file, $scope->getClassReflection(), - $methodNameForInheritance, - $currentResolvedPhpDoc, + $trait, + $node->name->name, $positionalParameterNames, ); @@ -4684,17 +4674,16 @@ public function getPhpDocs(Scope $scope, Node\FunctionLike|Node\Stmt\Property $n $isPure = $resolvedPhpDoc->isPure(); $isAllowedPrivateMutation = $resolvedPhpDoc->isAllowedPrivateMutation(); $acceptsNamedArguments = $resolvedPhpDoc->acceptsNamedArguments(); + if ($acceptsNamedArguments && $scope->isInClass()) { + $acceptsNamedArguments = $scope->getClassReflection()->acceptsNamedArguments(); + } $isReadOnly = $isReadOnly || $resolvedPhpDoc->isReadOnly(); $asserts = Assertions::createFromResolvedPhpDocBlock($resolvedPhpDoc); $selfOutType = $resolvedPhpDoc->getSelfOutTag() !== null ? $resolvedPhpDoc->getSelfOutTag()->getType() : null; $varTags = $resolvedPhpDoc->getVarTags(); } - if ($acceptsNamedArguments && $scope->isInClass()) { - $acceptsNamedArguments = $scope->getClassReflection()->acceptsNamedArguments(); - } - - return [$templateTypeMap, $phpDocParameterTypes, $phpDocImmediatelyInvokedCallableParameters, $phpDocClosureThisTypeParameters, $phpDocReturnType, $phpDocThrowType, $deprecatedDescription, $isDeprecated, $isInternal, $isFinal, $isPure, $acceptsNamedArguments, $isReadOnly, $docComment, $asserts, $selfOutType, $phpDocParameterOutTypes, $varTags, $isAllowedPrivateMutation, $resolvedPhpDoc]; + return [$templateTypeMap, $phpDocParameterTypes, $phpDocImmediatelyInvokedCallableParameters, $phpDocClosureThisTypeParameters, $phpDocReturnType, $phpDocThrowType, $deprecatedDescription, $isDeprecated, $isInternal, $isFinal, $isPure, $acceptsNamedArguments, $isReadOnly, $docComment, $asserts, $selfOutType, $phpDocParameterOutTypes, $varTags, $isAllowedPrivateMutation]; } private function transformStaticType(ClassReflection $declaringClass, Type $type): Type diff --git a/src/PhpDoc/InheritedPhpDocParameterMapping.php b/src/PhpDoc/InheritedPhpDocParameterMapping.php deleted file mode 100644 index c0b80d3e256..00000000000 --- a/src/PhpDoc/InheritedPhpDocParameterMapping.php +++ /dev/null @@ -1,67 +0,0 @@ - $parameterNameMapping - */ - public function __construct( - private array $parameterNameMapping, - ) - { - } - - /** - * @template T - * @param array $array - * @return array - */ - public function transformArrayKeysWithParameterNameMapping(array $array): array - { - $newArray = []; - foreach ($array as $key => $value) { - if (!array_key_exists($key, $this->parameterNameMapping)) { - continue; - } - $newArray[$this->parameterNameMapping[$key]] = $value; - } - - return $newArray; - } - - public function transformConditionalReturnTypeWithParameterNameMapping(Type $type): Type - { - $nameMapping = $this->parameterNameMapping; - return TypeTraverser::map($type, static function (Type $type, callable $traverse) use ($nameMapping): Type { - if ($type instanceof ConditionalTypeForParameter) { - $parameterName = substr($type->getParameterName(), 1); - if (array_key_exists($parameterName, $nameMapping)) { - $type = $type->changeParameterName('$' . $nameMapping[$parameterName]); - } - } - - return $traverse($type); - }); - } - - public function transformAssertTagParameterWithParameterNameMapping(AssertTagParameter $parameter): AssertTagParameter - { - $parameterName = substr($parameter->getParameterName(), 1); - if (array_key_exists($parameterName, $this->parameterNameMapping)) { - $parameter = $parameter->changeParameterName('$' . $this->parameterNameMapping[$parameterName]); - } - - return $parameter; - } - -} diff --git a/src/PhpDoc/PhpDocBlock.php b/src/PhpDoc/PhpDocBlock.php new file mode 100644 index 00000000000..6c602701f47 --- /dev/null +++ b/src/PhpDoc/PhpDocBlock.php @@ -0,0 +1,391 @@ + $parameterNameMapping + * @param array $parents + */ + private function __construct( + private string $docComment, + private ?string $file, + private ClassReflection $classReflection, + private ?string $trait, + private array $parameterNameMapping, + private array $parents, + ) + { + } + + public function getDocComment(): string + { + return $this->docComment; + } + + public function getFile(): ?string + { + return $this->file; + } + + public function getClassReflection(): ClassReflection + { + return $this->classReflection; + } + + public function getTrait(): ?string + { + return $this->trait; + } + + /** + * @return array + */ + public function getParents(): array + { + return $this->parents; + } + + /** + * @template T + * @param array $array + * @return array + */ + public function transformArrayKeysWithParameterNameMapping(array $array): array + { + $newArray = []; + foreach ($array as $key => $value) { + if (!array_key_exists($key, $this->parameterNameMapping)) { + continue; + } + $newArray[$this->parameterNameMapping[$key]] = $value; + } + + return $newArray; + } + + public function transformConditionalReturnTypeWithParameterNameMapping(Type $type): Type + { + $nameMapping = $this->parameterNameMapping; + return TypeTraverser::map($type, static function (Type $type, callable $traverse) use ($nameMapping): Type { + if ($type instanceof ConditionalTypeForParameter) { + $parameterName = substr($type->getParameterName(), 1); + if (array_key_exists($parameterName, $nameMapping)) { + $type = $type->changeParameterName('$' . $nameMapping[$parameterName]); + } + } + + return $traverse($type); + }); + } + + public function transformAssertTagParameterWithParameterNameMapping(AssertTagParameter $parameter): AssertTagParameter + { + $parameterName = substr($parameter->getParameterName(), 1); + if (array_key_exists($parameterName, $this->parameterNameMapping)) { + $parameter = $parameter->changeParameterName('$' . $this->parameterNameMapping[$parameterName]); + } + + return $parameter; + } + + public static function resolvePhpDocBlockForProperty( + ?string $docComment, + ClassReflection $classReflection, + ?string $trait, + string $propertyName, + ?string $file, + ): self + { + $docBlocksFromParents = []; + foreach (self::getParentReflections($classReflection) as $parentReflection) { + $oneResult = self::resolvePropertyPhpDocBlockFromClass( + $parentReflection, + $propertyName, + ); + + if ($oneResult === null) { // Null if it is private or from a wrong trait. + continue; + } + + $docBlocksFromParents[] = $oneResult; + } + + return new self( + $docComment ?? ResolvedPhpDocBlock::EMPTY_DOC_STRING, + $file, + $classReflection, + $trait, + [], + $docBlocksFromParents, + ); + } + + public static function resolvePhpDocBlockForConstant( + ?string $docComment, + ClassReflection $classReflection, + string $constantName, + ?string $file, + ): self + { + $docBlocksFromParents = []; + foreach (self::getParentReflections($classReflection) as $parentReflection) { + $oneResult = self::resolveConstantPhpDocBlockFromClass( + $parentReflection, + $constantName, + ); + + if ($oneResult === null) { // Null if it is private or from a wrong trait. + continue; + } + + $docBlocksFromParents[] = $oneResult; + } + + return new self( + $docComment ?? ResolvedPhpDocBlock::EMPTY_DOC_STRING, + $file, + $classReflection, + null, + [], + $docBlocksFromParents, + ); + } + + /** + * @param array $originalPositionalParameterNames + * @param array $newPositionalParameterNames + */ + public static function resolvePhpDocBlockForMethod( + ?string $docComment, + ClassReflection $classReflection, + ?string $trait, + string $methodName, + ?string $file, + array $originalPositionalParameterNames, + array $newPositionalParameterNames, + ): self + { + $docBlocksFromParents = []; + foreach (self::getParentReflections($classReflection) as $parentReflection) { + $oneResult = self::resolveMethodPhpDocBlockFromClass( + $parentReflection, + $methodName, + $newPositionalParameterNames, + ); + + if ($oneResult === null) { // Null if it is private or from a wrong trait. + continue; + } + + $docBlocksFromParents[] = $oneResult; + } + + foreach ($classReflection->getTraits() as $traitReflection) { + if (!$traitReflection->hasNativeMethod($methodName)) { + continue; + } + $traitMethod = $traitReflection->getNativeMethod($methodName); + $abstract = $traitMethod->isAbstract(); + if (is_bool($abstract)) { + if (!$abstract) { + continue; + } + } elseif (!$abstract->yes()) { + continue; + } + + if ($traitMethod->getDocComment() === null) { + continue; + } + + $methodVariant = $traitMethod->getOnlyVariant(); + $positionalMethodParameterNames = []; + foreach ($methodVariant->getParameters() as $methodParameter) { + $positionalMethodParameterNames[] = $methodParameter->getName(); + } + + $docBlocksFromParents[] = new self( + $traitMethod->getDocComment(), + $classReflection->getFileName(), + $classReflection, + $traitReflection->getName(), + self::remapParameterNames($newPositionalParameterNames, $positionalMethodParameterNames), + [], + ); + } + + return new self( + $docComment ?? ResolvedPhpDocBlock::EMPTY_DOC_STRING, + $file, + $classReflection, + $trait, + self::remapParameterNames($originalPositionalParameterNames, $newPositionalParameterNames), + $docBlocksFromParents, + ); + } + + /** + * @param array $originalPositionalParameterNames + * @param array $newPositionalParameterNames + * @return array + */ + private static function remapParameterNames( + array $originalPositionalParameterNames, + array $newPositionalParameterNames, + ): array + { + $parameterNameMapping = []; + foreach ($originalPositionalParameterNames as $i => $parameterName) { + if (!array_key_exists($i, $newPositionalParameterNames)) { + continue; + } + $parameterNameMapping[$newPositionalParameterNames[$i]] = $parameterName; + } + + return $parameterNameMapping; + } + + /** + * @return array + */ + private static function getParentReflections(ClassReflection $classReflection): array + { + $result = []; + + $parent = $classReflection->getParentClass(); + if ($parent !== null) { + $result[] = $parent; + } + + foreach ($classReflection->getImmediateInterfaces() as $interface) { + $result[] = $interface; + } + + return $result; + } + + private static function resolveConstantPhpDocBlockFromClass( + ClassReflection $classReflection, + string $name, + ): ?self + { + if ($classReflection->hasConstant($name)) { + $parentReflection = $classReflection->getConstant($name); + if ($parentReflection->isPrivate()) { + return null; + } + + $classReflection = $parentReflection->getDeclaringClass(); + + return self::resolvePhpDocBlockForConstant( + $parentReflection->getDocComment() ?? ResolvedPhpDocBlock::EMPTY_DOC_STRING, + $classReflection, + $name, + $classReflection->getFileName(), + ); + } + + return null; + } + + private static function resolvePropertyPhpDocBlockFromClass( + ClassReflection $classReflection, + string $name, + ): ?self + { + if ($classReflection->hasNativeProperty($name)) { + $parentReflection = $classReflection->getNativeProperty($name); + if ($parentReflection->isPrivate()) { + return null; + } + + $classReflection = $parentReflection->getDeclaringClass(); + $traitReflection = $parentReflection->getDeclaringTrait(); + + $trait = $traitReflection !== null + ? $traitReflection->getName() + : null; + + return self::resolvePhpDocBlockForProperty( + $parentReflection->getDocComment() ?? ResolvedPhpDocBlock::EMPTY_DOC_STRING, + $classReflection, + $trait, + $name, + $classReflection->getFileName(), + ); + } + + return null; + } + + /** + * @param array $positionalParameterNames + */ + private static function resolveMethodPhpDocBlockFromClass( + ClassReflection $classReflection, + string $name, + array $positionalParameterNames, + ): ?self + { + if ($classReflection->hasNativeMethod($name)) { + $parentReflection = $classReflection->getNativeMethod($name); + if ($parentReflection->isPrivate()) { + return null; + } + + $classReflection = $parentReflection->getDeclaringClass(); + $traitReflection = null; + if ($parentReflection instanceof PhpMethodReflection || $parentReflection instanceof ResolvedMethodReflection) { + $traitReflection = $parentReflection->getDeclaringTrait(); + } + $methodVariants = $parentReflection->getVariants(); + $positionalMethodParameterNames = []; + $lowercaseMethodName = strtolower($parentReflection->getName()); + if ( + count($methodVariants) === 1 + && $lowercaseMethodName !== '__construct' + && $lowercaseMethodName !== strtolower($parentReflection->getDeclaringClass()->getName()) + ) { + $methodParameters = $methodVariants[0]->getParameters(); + foreach ($methodParameters as $methodParameter) { + $positionalMethodParameterNames[] = $methodParameter->getName(); + } + } else { + $positionalMethodParameterNames = $positionalParameterNames; + } + + $trait = $traitReflection !== null + ? $traitReflection->getName() + : null; + + return self::resolvePhpDocBlockForMethod( + $parentReflection->getDocComment() ?? ResolvedPhpDocBlock::EMPTY_DOC_STRING, + $classReflection, + $trait, + $name, + $classReflection->getFileName(), + $positionalParameterNames, + $positionalMethodParameterNames, + ); + } + + return null; + } + +} diff --git a/src/PhpDoc/PhpDocInheritanceResolver.php b/src/PhpDoc/PhpDocInheritanceResolver.php index 628b3e62dfc..b72e7fe36b2 100644 --- a/src/PhpDoc/PhpDocInheritanceResolver.php +++ b/src/PhpDoc/PhpDocInheritanceResolver.php @@ -2,284 +2,154 @@ namespace PHPStan\PhpDoc; +use PHPStan\BetterReflection\Reflection\Adapter\ReflectionParameter; use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\ClassReflection; -use PHPStan\Reflection\ExtendedMethodReflection; use PHPStan\Type\FileTypeMapper; -use function array_key_exists; -use function count; -use function is_bool; +use function array_map; use function strtolower; #[AutowiredService] final class PhpDocInheritanceResolver { - public function __construct(private FileTypeMapper $fileTypeMapper) + public function __construct( + private FileTypeMapper $fileTypeMapper, + private StubPhpDocProvider $stubPhpDocProvider, + ) { } public function resolvePhpDocForProperty( - ClassReflection $declaringClass, + ?string $docComment, + ClassReflection $classReflection, + ?string $classReflectionFileName, + ?string $declaringTraitName, string $propertyName, - ?ResolvedPhpDocBlock $currentResolvedPhpDoc, - ): ?ResolvedPhpDocBlock + ): ResolvedPhpDocBlock { - $parent = $declaringClass->getParentClass(); - if ($parent !== null) { - $parentMethod = $this->resolvePropertyPhpDocFromParentClass($declaringClass, $parent, $propertyName, $currentResolvedPhpDoc); - if ($parentMethod !== null) { - return $parentMethod; - } - } - - foreach ($declaringClass->getImmediateInterfaces() as $interface) { - $interfaceMethod = $this->resolvePropertyPhpDocFromParentClass($declaringClass, $interface, $propertyName, $currentResolvedPhpDoc); - if ($interfaceMethod === null) { - continue; - } - - return $interfaceMethod; - } - - return $currentResolvedPhpDoc; + $phpDocBlock = PhpDocBlock::resolvePhpDocBlockForProperty( + $docComment, + $classReflection, + null, + $propertyName, + $classReflectionFileName, + ); + + return $this->docBlockTreeToResolvedDocBlock($phpDocBlock, $declaringTraitName, null, $propertyName, null); } public function resolvePhpDocForConstant( - ClassReflection $declaringClass, + ?string $docComment, + ClassReflection $classReflection, + ?string $classReflectionFileName, string $constantName, - ?ResolvedPhpDocBlock $currentResolvedPhpDoc, - ): ?ResolvedPhpDocBlock + ): ResolvedPhpDocBlock { - $parent = $declaringClass->getParentClass(); - if ($parent !== null) { - $parentMethod = $this->resolveConstantPhpDocFromParentClass($declaringClass, $parent, $constantName, $currentResolvedPhpDoc); - if ($parentMethod !== null) { - return $parentMethod; - } - } - - foreach ($declaringClass->getImmediateInterfaces() as $interface) { - $interfaceMethod = $this->resolveConstantPhpDocFromParentClass($declaringClass, $interface, $constantName, $currentResolvedPhpDoc); - if ($interfaceMethod === null) { - continue; - } - - return $interfaceMethod; - } - - return $currentResolvedPhpDoc; + $phpDocBlock = PhpDocBlock::resolvePhpDocBlockForConstant( + $docComment, + $classReflection, + $constantName, + $classReflectionFileName, + ); + + return $this->docBlockTreeToResolvedDocBlock($phpDocBlock, null, null, null, $constantName); } /** - * @param array $currentPositionalParameterNames + * @param array $positionalParameterNames */ public function resolvePhpDocForMethod( - ClassReflection $declaringClass, + ?string $docComment, + ?string $fileName, + ClassReflection $classReflection, + ?string $declaringTraitName, string $methodName, - ?ResolvedPhpDocBlock $currentResolvedPhpDoc, - array $currentPositionalParameterNames, - ): ?ResolvedPhpDocBlock - { - $parent = $declaringClass->getParentClass(); - if ($parent !== null) { - if ($parent->hasNativeMethod($methodName)) { - $parentMethod = $parent->getNativeMethod($methodName); - if (!$parentMethod->isPrivate() && $parentMethod->getResolvedPhpDoc() !== null) { - if ($parentMethod->getName() !== '__construct' || !$parentMethod->getDeclaringClass()->isBuiltin()) { - return $this->resolveMethodPhpDocFromParentClass($parentMethod, $parentMethod->getResolvedPhpDoc(), $declaringClass, $parent, $currentResolvedPhpDoc, $currentPositionalParameterNames); - } - } - } - } - - foreach ($declaringClass->getImmediateInterfaces() as $interface) { - if (!$interface->hasNativeMethod($methodName)) { - continue; - } - - $interfaceMethod = $interface->getNativeMethod($methodName); - if ($interfaceMethod->isPrivate()) { - continue; - } - if ($interfaceMethod->getResolvedPhpDoc() === null) { - continue; - } - return $this->resolveMethodPhpDocFromParentClass($interfaceMethod, $interfaceMethod->getResolvedPhpDoc(), $declaringClass, $interface, $currentResolvedPhpDoc, $currentPositionalParameterNames); - - } - - foreach ($declaringClass->getTraits() as $trait) { - if (!$trait->hasNativeMethod($methodName)) { - continue; - } - - $traitMethod = $trait->getNativeMethod($methodName); - if ($traitMethod->getDocComment() === null) { - continue; - } - if ($declaringClass->getFileName() === null) { - continue; - } - - $abstract = $traitMethod->isAbstract(); - if (is_bool($abstract)) { - if (!$abstract) { - continue; - } - } elseif (!$abstract->yes()) { - continue; - } - - $resolvedPhpDocBlock = $this->fileTypeMapper->getResolvedPhpDoc( - $declaringClass->getFileName(), - $declaringClass->getName(), - $trait->getName(), - $methodName, - $traitMethod->getDocComment(), - ); - - return $this->resolveMethodPhpDocFromParentClass($traitMethod, $resolvedPhpDocBlock, $declaringClass, $trait, $currentResolvedPhpDoc, $currentPositionalParameterNames); - } - - return $currentResolvedPhpDoc; - } - - /** - * @param array $currentPositionalParameterNames - */ - private function resolveMethodPhpDocFromParentClass( - ExtendedMethodReflection $parentMethod, - ResolvedPhpDocBlock $resolvedPhpDocBlock, - ClassReflection $declaringClass, - ClassReflection $parent, - ?ResolvedPhpDocBlock $currentResolvedPhpDoc, - array $currentPositionalParameterNames, + array $positionalParameterNames, ): ResolvedPhpDocBlock { - if ($currentResolvedPhpDoc === null) { - $currentResolvedPhpDoc = ResolvedPhpDocBlock::createEmpty(); - } - - $methodVariants = $parentMethod->getVariants(); - $positionalMethodParameterNames = []; - $lowercaseMethodName = strtolower($parentMethod->getName()); - if ( - count($methodVariants) === 1 - && $lowercaseMethodName !== '__construct' - && $lowercaseMethodName !== strtolower($parentMethod->getDeclaringClass()->getName()) - ) { - $methodParameters = $methodVariants[0]->getParameters(); - foreach ($methodParameters as $methodParameter) { - $positionalMethodParameterNames[] = $methodParameter->getName(); - } - } else { - $positionalMethodParameterNames = $currentPositionalParameterNames; - } - - $parentClassForMerge = $parent; - $phpDocDeclaringClass = $parentMethod->getDeclaringClass(); - if ($phpDocDeclaringClass->getName() !== $parent->getName()) { - $ancestor = $parent->getAncestorWithClassName($phpDocDeclaringClass->getName()); - if ($ancestor !== null) { - $parentClassForMerge = $ancestor; - } - } - - return $currentResolvedPhpDoc->merge($resolvedPhpDocBlock, new InheritedPhpDocParameterMapping(self::remapParameterNames($currentPositionalParameterNames, $positionalMethodParameterNames)), $declaringClass, $parentClassForMerge); + $phpDocBlock = PhpDocBlock::resolvePhpDocBlockForMethod( + $docComment, + $classReflection, + $declaringTraitName, + $methodName, + $fileName, + $positionalParameterNames, + $positionalParameterNames, + ); + + return $this->docBlockTreeToResolvedDocBlock($phpDocBlock, $phpDocBlock->getTrait(), $methodName, null, null); } - /** - * @param array $originalPositionalParameterNames - * @param array $newPositionalParameterNames - * @return array - */ - private static function remapParameterNames( - array $originalPositionalParameterNames, - array $newPositionalParameterNames, - ): array + private function docBlockTreeToResolvedDocBlock(PhpDocBlock $phpDocBlock, ?string $traitName, ?string $functionName, ?string $propertyName, ?string $constantName): ResolvedPhpDocBlock { - $parameterNameMapping = []; - foreach ($originalPositionalParameterNames as $i => $parameterName) { - if (!array_key_exists($i, $newPositionalParameterNames)) { + $parents = []; + $parentPhpDocBlocks = []; + + foreach ($phpDocBlock->getParents() as $parentPhpDocBlock) { + if ( + $functionName !== null + && strtolower($functionName) === '__construct' + && $parentPhpDocBlock->getClassReflection()->isBuiltin() + ) { continue; } - $parameterNameMapping[$newPositionalParameterNames[$i]] = $parameterName; + $parents[] = $this->docBlockTreeToResolvedDocBlock( + $parentPhpDocBlock, + $parentPhpDocBlock->getTrait(), + $functionName, + $propertyName, + $constantName, + ); + $parentPhpDocBlocks[] = $parentPhpDocBlock; } - return $parameterNameMapping; + $oneResolvedDockBlock = $this->docBlockToResolvedDocBlock($phpDocBlock, $traitName, $functionName, $propertyName, $constantName); + return $oneResolvedDockBlock->merge($parents, $parentPhpDocBlocks); } - private function resolveConstantPhpDocFromParentClass( - ClassReflection $declaringClass, - ClassReflection $parent, - string $constantName, - ?ResolvedPhpDocBlock $currentResolvedPhpDoc, - ): ?ResolvedPhpDocBlock + private function docBlockToResolvedDocBlock(PhpDocBlock $phpDocBlock, ?string $traitName, ?string $functionName, ?string $propertyName, ?string $constantName): ResolvedPhpDocBlock { - if (!$parent->hasConstant($constantName)) { - return null; - } - - $parentConstant = $parent->getConstant($constantName); - if ($parentConstant->isPrivate()) { - return null; - } - - if ($parentConstant->getResolvedPhpDoc() === null) { - return null; - } - - if ($currentResolvedPhpDoc === null) { - $currentResolvedPhpDoc = ResolvedPhpDocBlock::createEmpty(); - } - - $parentClassForMerge = $parent; - $phpDocDeclaringClass = $parentConstant->getDeclaringClass(); - if ($phpDocDeclaringClass->getName() !== $parent->getName()) { - $ancestor = $parent->getAncestorWithClassName($phpDocDeclaringClass->getName()); - if ($ancestor !== null) { - $parentClassForMerge = $ancestor; + $classReflection = $phpDocBlock->getClassReflection(); + if ($functionName !== null && $classReflection->getNativeReflection()->hasMethod($functionName)) { + $methodReflection = $classReflection->getNativeReflection()->getMethod($functionName); + $stub = $this->stubPhpDocProvider->findMethodPhpDoc($classReflection->getName(), $classReflection->getName(), $functionName, array_map(static fn (ReflectionParameter $parameter): string => $parameter->getName(), $methodReflection->getParameters())); + if ($stub !== null) { + return $stub; } } - return $currentResolvedPhpDoc->merge($parentConstant->getResolvedPhpDoc(), new InheritedPhpDocParameterMapping([]), $declaringClass, $parentClassForMerge); - } - - private function resolvePropertyPhpDocFromParentClass( - ClassReflection $declaringClass, - ClassReflection $parent, - string $propertyName, - ?ResolvedPhpDocBlock $currentResolvedPhpDoc, - ): ?ResolvedPhpDocBlock - { - if (!$parent->hasNativeProperty($propertyName)) { - return null; - } + if ($propertyName !== null && $classReflection->getNativeReflection()->hasProperty($propertyName)) { + $stub = $this->stubPhpDocProvider->findPropertyPhpDoc($classReflection->getName(), $propertyName); - $parentProperty = $parent->getNativeProperty($propertyName); - if ($parentProperty->isPrivate()) { - return null; - } + if ($stub === null) { + $propertyReflection = $classReflection->getNativeReflection()->getProperty($propertyName); - if ($parentProperty->getResolvedPhpDoc() === null) { - return null; - } + $propertyDeclaringClass = $propertyReflection->getBetterReflection()->getDeclaringClass(); - if ($currentResolvedPhpDoc === null) { - $currentResolvedPhpDoc = ResolvedPhpDocBlock::createEmpty(); + if ($propertyDeclaringClass->isTrait() && (! $propertyReflection->getDeclaringClass()->isTrait() || $propertyReflection->getDeclaringClass()->getName() !== $propertyDeclaringClass->getName())) { + $stub = $this->stubPhpDocProvider->findPropertyPhpDoc($propertyDeclaringClass->getName(), $propertyName); + } + } + if ($stub !== null) { + return $stub; + } } - $parentClassForMerge = $parent; - $phpDocDeclaringClass = $parentProperty->getDeclaringClass(); - if ($phpDocDeclaringClass->getName() !== $parent->getName()) { - $ancestor = $parent->getAncestorWithClassName($phpDocDeclaringClass->getName()); - if ($ancestor !== null) { - $parentClassForMerge = $ancestor; + if ($constantName !== null && $classReflection->getNativeReflection()->hasConstant($constantName)) { + $stub = $this->stubPhpDocProvider->findClassConstantPhpDoc($classReflection->getName(), $constantName); + if ($stub !== null) { + return $stub; } } - return $currentResolvedPhpDoc->merge($parentProperty->getResolvedPhpDoc(), new InheritedPhpDocParameterMapping([]), $declaringClass, $parentClassForMerge); + return $this->fileTypeMapper->getResolvedPhpDoc( + $phpDocBlock->getFile(), + $classReflection->getName(), + $traitName, + $functionName, + $phpDocBlock->getDocComment(), + ); } } diff --git a/src/PhpDoc/ResolvedPhpDocBlock.php b/src/PhpDoc/ResolvedPhpDocBlock.php index b2533223b67..dc43e9af5e7 100644 --- a/src/PhpDoc/ResolvedPhpDocBlock.php +++ b/src/PhpDoc/ResolvedPhpDocBlock.php @@ -242,17 +242,28 @@ public static function createEmpty(): self return $self; } - public function merge(ResolvedPhpDocBlock $parent, InheritedPhpDocParameterMapping $parameterMapping, ClassReflection $declaringClass, ClassReflection $parentClass): self + /** + * @param array $parents + * @param array $parentPhpDocBlocks + */ + public function merge(array $parents, array $parentPhpDocBlocks): self { + $className = $this->nameScope !== null ? $this->nameScope->getClassName() : null; + $classReflection = $className !== null && $this->reflectionProvider->hasClass($className) + ? $this->reflectionProvider->getClass($className) + : null; + // new property also needs to be added to createEmpty() $result = new self(); // we will resolve everything on $this here so these properties don't have to be populated // skip $result->phpDocNode $phpDocNodes = $this->phpDocNodes; $acceptsNamedArguments = $this->acceptsNamedArguments(); - foreach ($parent->phpDocNodes as $phpDocNode) { - $phpDocNodes[] = $phpDocNode; - $acceptsNamedArguments = $acceptsNamedArguments && $parent->acceptsNamedArguments(); + foreach ($parents as $parent) { + foreach ($parent->phpDocNodes as $phpDocNode) { + $phpDocNodes[] = $phpDocNode; + $acceptsNamedArguments = $acceptsNamedArguments && $parent->acceptsNamedArguments(); + } } $result->phpDocNodes = $phpDocNodes; $result->phpDocString = $this->phpDocString; @@ -261,32 +272,32 @@ public function merge(ResolvedPhpDocBlock $parent, InheritedPhpDocParameterMappi $result->templateTypeMap = $this->templateTypeMap; $result->templateTags = $this->templateTags; // skip $result->phpDocNodeResolver - $result->varTags = self::mergeVarTags($this->getVarTags(), $parent, $parentClass); + $result->varTags = self::mergeVarTags($this->getVarTags(), $parents, $parentPhpDocBlocks); $result->methodTags = $this->getMethodTags(); $result->propertyTags = $this->getPropertyTags(); $result->extendsTags = $this->getExtendsTags(); $result->implementsTags = $this->getImplementsTags(); $result->usesTags = $this->getUsesTags(); - $result->paramTags = self::mergeParamTags($this->getParamTags(), $parent, $parameterMapping, $parentClass); - $result->paramOutTags = self::mergeParamOutTags($this->getParamOutTags(), $parent, $parameterMapping, $parentClass); - $result->paramsImmediatelyInvokedCallable = self::mergeParamsImmediatelyInvokedCallable($this->getParamsImmediatelyInvokedCallable(), $parent, $parameterMapping); - $result->paramClosureThisTags = self::mergeParamClosureThisTags($this->getParamClosureThisTags(), $parent, $parameterMapping, $parentClass); - $result->returnTag = self::mergeReturnTags($this->getReturnTag(), $declaringClass, $parent, $parameterMapping, $parentClass); - $result->throwsTag = self::mergeThrowsTags($this->getThrowsTag(), $parent); + $result->paramTags = self::mergeParamTags($this->getParamTags(), $parents, $parentPhpDocBlocks); + $result->paramOutTags = self::mergeParamOutTags($this->getParamOutTags(), $parents, $parentPhpDocBlocks); + $result->paramsImmediatelyInvokedCallable = self::mergeParamsImmediatelyInvokedCallable($this->getParamsImmediatelyInvokedCallable(), $parents, $parentPhpDocBlocks); + $result->paramClosureThisTags = self::mergeParamClosureThisTags($this->getParamClosureThisTags(), $parents, $parentPhpDocBlocks); + $result->returnTag = self::mergeReturnTags($this->getReturnTag(), $classReflection, $parents, $parentPhpDocBlocks); + $result->throwsTag = self::mergeThrowsTags($this->getThrowsTag(), $parents); $result->mixinTags = $this->getMixinTags(); $result->requireExtendsTags = $this->getRequireExtendsTags(); $result->requireImplementsTags = $this->getRequireImplementsTags(); $result->sealedTypeTags = $this->getSealedTags(); $result->typeAliasTags = $this->getTypeAliasTags(); $result->typeAliasImportTags = $this->getTypeAliasImportTags(); - $result->assertTags = self::mergeAssertTags($this->getAssertTags(), $parent, $parameterMapping, $parentClass); - $result->selfOutTypeTag = self::mergeSelfOutTypeTags($this->getSelfOutTag(), $parent); - $result->deprecatedTag = self::mergeDeprecatedTags($this->getDeprecatedTag(), $this->isNotDeprecated(), $parent); + $result->assertTags = self::mergeAssertTags($this->getAssertTags(), $parents, $parentPhpDocBlocks); + $result->selfOutTypeTag = self::mergeSelfOutTypeTags($this->getSelfOutTag(), $parents); + $result->deprecatedTag = self::mergeDeprecatedTags($this->getDeprecatedTag(), $this->isNotDeprecated(), $parents); $result->isDeprecated = $result->deprecatedTag !== null; $result->isNotDeprecated = $this->isNotDeprecated(); $result->isInternal = $this->isInternal(); $result->isFinal = $this->isFinal(); - $result->isPure = self::mergePureTags($this->isPure(), $parent); + $result->isPure = self::mergePureTags($this->isPure(), $parents); $result->isReadOnly = $this->isReadOnly(); $result->isImmutable = $this->isImmutable(); $result->isAllowedPrivateMutation = $this->isAllowedPrivateMutation(); @@ -839,30 +850,36 @@ public function isAllowedPrivateMutation(): bool /** * @param array $varTags + * @param array $parents + * @param array $parentPhpDocBlocks * @return array */ - private static function mergeVarTags(array $varTags, self $parent, ClassReflection $parentClass): array + private static function mergeVarTags(array $varTags, array $parents, array $parentPhpDocBlocks): array { // Only allow one var tag per comment. Check the parent if child does not have this tag. if (count($varTags) > 0) { return $varTags; } - $result = self::mergeOneParentVarTags($parent, $parentClass); - if ($result === null) { - return []; + foreach ($parents as $i => $parent) { + $result = self::mergeOneParentVarTags($parent, $parentPhpDocBlocks[$i]); + if ($result === null) { + continue; + } + + return $result; } - return $result; + return []; } /** * @return array|null */ - private static function mergeOneParentVarTags(self $parent, ClassReflection $parentClass): ?array + private static function mergeOneParentVarTags(self $parent, PhpDocBlock $phpDocBlock): ?array { foreach ($parent->getVarTags() as $key => $parentVarTag) { - return [$key => self::resolveTemplateTypeInTag($parentVarTag->toImplicit(), $parentClass, TemplateTypeVariance::createInvariant())]; + return [$key => self::resolveTemplateTypeInTag($parentVarTag->toImplicit(), $phpDocBlock, TemplateTypeVariance::createInvariant())]; } return null; @@ -870,20 +887,26 @@ private static function mergeOneParentVarTags(self $parent, ClassReflection $par /** * @param array $paramTags + * @param array $parents + * @param array $parentPhpDocBlocks * @return array */ - private static function mergeParamTags(array $paramTags, self $parent, InheritedPhpDocParameterMapping $parameterMapping, ClassReflection $parentClass): array + private static function mergeParamTags(array $paramTags, array $parents, array $parentPhpDocBlocks): array { - return self::mergeOneParentParamTags($paramTags, $parent, $parameterMapping, $parentClass); + foreach ($parents as $i => $parent) { + $paramTags = self::mergeOneParentParamTags($paramTags, $parent, $parentPhpDocBlocks[$i]); + } + + return $paramTags; } /** * @param array $paramTags * @return array */ - private static function mergeOneParentParamTags(array $paramTags, self $parent, InheritedPhpDocParameterMapping $parameterMapping, ClassReflection $parentClass): array + private static function mergeOneParentParamTags(array $paramTags, self $parent, PhpDocBlock $phpDocBlock): array { - $parentParamTags = $parameterMapping->transformArrayKeysWithParameterNameMapping($parent->getParamTags()); + $parentParamTags = $phpDocBlock->transformArrayKeysWithParameterNameMapping($parent->getParamTags()); foreach ($parentParamTags as $name => $parentParamTag) { if (array_key_exists($name, $paramTags)) { @@ -891,8 +914,8 @@ private static function mergeOneParentParamTags(array $paramTags, self $parent, } $paramTags[$name] = self::resolveTemplateTypeInTag( - $parentParamTag->withType($parameterMapping->transformConditionalReturnTypeWithParameterNameMapping($parentParamTag->getType())), - $parentClass, + $parentParamTag->withType($phpDocBlock->transformConditionalReturnTypeWithParameterNameMapping($parentParamTag->getType())), + $phpDocBlock, TemplateTypeVariance::createContravariant(), ); } @@ -900,16 +923,30 @@ private static function mergeOneParentParamTags(array $paramTags, self $parent, return $paramTags; } - private static function mergeReturnTags(?ReturnTag $returnTag, ClassReflection $classReflection, self $parent, InheritedPhpDocParameterMapping $parameterMapping, ClassReflection $parentClass): ?ReturnTag + /** + * @param array $parents + * @param array $parentPhpDocBlocks + * @return ReturnTag|Null + */ + private static function mergeReturnTags(?ReturnTag $returnTag, ?ClassReflection $classReflection, array $parents, array $parentPhpDocBlocks): ?ReturnTag { if ($returnTag !== null) { return $returnTag; } - return self::mergeOneParentReturnTag($returnTag, $classReflection, $parent, $parameterMapping, $parentClass); + foreach ($parents as $i => $parent) { + $result = self::mergeOneParentReturnTag($returnTag, $classReflection, $parent, $parentPhpDocBlocks[$i]); + if ($result === null) { + continue; + } + + return $result; + } + + return null; } - private static function mergeOneParentReturnTag(?ReturnTag $returnTag, ClassReflection $classReflection, self $parent, InheritedPhpDocParameterMapping $parameterMapping, ClassReflection $parentClass): ?ReturnTag + private static function mergeOneParentReturnTag(?ReturnTag $returnTag, ?ClassReflection $classReflection, self $parent, PhpDocBlock $phpDocBlock): ?ReturnTag { $parentReturnTag = $parent->getReturnTag(); if ($parentReturnTag === null) { @@ -917,18 +954,21 @@ private static function mergeOneParentReturnTag(?ReturnTag $returnTag, ClassRefl } $parentType = $parentReturnTag->getType(); - $parentType = TypeTraverser::map( - $parentType, - static function (Type $type, callable $traverse) use ($classReflection): Type { - if ($type instanceof StaticType) { - return $type->changeBaseClass($classReflection); - } - return $traverse($type); - }, - ); + if ($classReflection !== null) { + $parentType = TypeTraverser::map( + $parentType, + static function (Type $type, callable $traverse) use ($classReflection): Type { + if ($type instanceof StaticType) { + return $type->changeBaseClass($classReflection); + } - $parentReturnTag = $parentReturnTag->withType($parentType); + return $traverse($type); + }, + ); + + $parentReturnTag = $parentReturnTag->withType($parentType); + } // Each parent would overwrite the previous one except if it returns a less specific type. // Do not care for incompatible types as there is a separate rule for that. @@ -938,45 +978,70 @@ static function (Type $type, callable $traverse) use ($classReflection): Type { return self::resolveTemplateTypeInTag( $parentReturnTag->withType( - $parameterMapping->transformConditionalReturnTypeWithParameterNameMapping($parentReturnTag->getType()), + $phpDocBlock->transformConditionalReturnTypeWithParameterNameMapping($parentReturnTag->getType()), )->toImplicit(), - $parentClass, + $phpDocBlock, TemplateTypeVariance::createCovariant(), ); } /** * @param array $assertTags + * @param array $parents + * @param array $parentPhpDocBlocks * @return array */ - private static function mergeAssertTags(array $assertTags, self $parent, InheritedPhpDocParameterMapping $parameterMapping, ClassReflection $parentClass): array + private static function mergeAssertTags(array $assertTags, array $parents, array $parentPhpDocBlocks): array { if (count($assertTags) > 0) { return $assertTags; } + foreach ($parents as $i => $parent) { + $result = $parent->getAssertTags(); + if (count($result) === 0) { + continue; + } - return array_map( - static fn (AssertTag $assertTag) => self::resolveTemplateTypeInTag( - $assertTag->withParameter( - $parameterMapping->transformAssertTagParameterWithParameterNameMapping($assertTag->getParameter()), - )->toImplicit(), - $parentClass, - TemplateTypeVariance::createCovariant(), - ), - $parent->getAssertTags(), - ); + $phpDocBlock = $parentPhpDocBlocks[$i]; + + return array_map( + static fn (AssertTag $assertTag) => self::resolveTemplateTypeInTag( + $assertTag->withParameter( + $phpDocBlock->transformAssertTagParameterWithParameterNameMapping($assertTag->getParameter()), + )->toImplicit(), + $phpDocBlock, + TemplateTypeVariance::createCovariant(), + ), + $result, + ); + } + + return $assertTags; } - private static function mergeSelfOutTypeTags(?SelfOutTypeTag $selfOutTypeTag, self $parent): ?SelfOutTypeTag + /** + * @param array $parents + */ + private static function mergeSelfOutTypeTags(?SelfOutTypeTag $selfOutTypeTag, array $parents): ?SelfOutTypeTag { if ($selfOutTypeTag !== null) { return $selfOutTypeTag; } + foreach ($parents as $parent) { + $result = $parent->getSelfOutTag(); + if ($result === null) { + continue; + } + return $result; + } - return $parent->getSelfOutTag(); + return null; } - private static function mergeDeprecatedTags(?DeprecatedTag $deprecatedTag, bool $hasNotDeprecatedTag, self $parent): ?DeprecatedTag + /** + * @param array $parents + */ + private static function mergeDeprecatedTags(?DeprecatedTag $deprecatedTag, bool $hasNotDeprecatedTag, array $parents): ?DeprecatedTag { if ($deprecatedTag !== null) { return $deprecatedTag; @@ -986,39 +1051,59 @@ private static function mergeDeprecatedTags(?DeprecatedTag $deprecatedTag, bool return null; } - $result = $parent->getDeprecatedTag(); - if ($result === null && !$parent->isNotDeprecated()) { - return null; + foreach ($parents as $parent) { + $result = $parent->getDeprecatedTag(); + if ($result === null && !$parent->isNotDeprecated()) { + continue; + } + return $result; } - return $result; + return null; } - private static function mergeThrowsTags(?ThrowsTag $throwsTag, self $parent): ?ThrowsTag + /** + * @param array $parents + */ + private static function mergeThrowsTags(?ThrowsTag $throwsTag, array $parents): ?ThrowsTag { if ($throwsTag !== null) { return $throwsTag; } + foreach ($parents as $parent) { + $result = $parent->getThrowsTag(); + if ($result === null) { + continue; + } + + return $result; + } - return $parent->getThrowsTag(); + return null; } /** * @param array $paramOutTags + * @param array $parents + * @param array $parentPhpDocBlocks * @return array */ - private static function mergeParamOutTags(array $paramOutTags, self $parent, InheritedPhpDocParameterMapping $parameterMapping, ClassReflection $parentClass): array + private static function mergeParamOutTags(array $paramOutTags, array $parents, array $parentPhpDocBlocks): array { - return self::mergeOneParentParamOutTags($paramOutTags, $parent, $parameterMapping, $parentClass); + foreach ($parents as $i => $parent) { + $paramOutTags = self::mergeOneParentParamOutTags($paramOutTags, $parent, $parentPhpDocBlocks[$i]); + } + + return $paramOutTags; } /** * @param array $paramOutTags * @return array */ - private static function mergeOneParentParamOutTags(array $paramOutTags, self $parent, InheritedPhpDocParameterMapping $parameterMapping, ClassReflection $parentClass): array + private static function mergeOneParentParamOutTags(array $paramOutTags, self $parent, PhpDocBlock $phpDocBlock): array { - $parentParamOutTags = $parameterMapping->transformArrayKeysWithParameterNameMapping($parent->getParamOutTags()); + $parentParamOutTags = $phpDocBlock->transformArrayKeysWithParameterNameMapping($parent->getParamOutTags()); foreach ($parentParamOutTags as $name => $parentParamTag) { if (array_key_exists($name, $paramOutTags)) { @@ -1026,8 +1111,8 @@ private static function mergeOneParentParamOutTags(array $paramOutTags, self $pa } $paramOutTags[$name] = self::resolveTemplateTypeInTag( - $parentParamTag->withType($parameterMapping->transformConditionalReturnTypeWithParameterNameMapping($parentParamTag->getType())), - $parentClass, + $parentParamTag->withType($phpDocBlock->transformConditionalReturnTypeWithParameterNameMapping($parentParamTag->getType())), + $phpDocBlock, TemplateTypeVariance::createCovariant(), ); } @@ -1037,20 +1122,26 @@ private static function mergeOneParentParamOutTags(array $paramOutTags, self $pa /** * @param array $paramsImmediatelyInvokedCallable + * @param array $parents + * @param array $parentPhpDocBlocks * @return array */ - private static function mergeParamsImmediatelyInvokedCallable(array $paramsImmediatelyInvokedCallable, self $parent, InheritedPhpDocParameterMapping $parameterMapping): array + private static function mergeParamsImmediatelyInvokedCallable(array $paramsImmediatelyInvokedCallable, array $parents, array $parentPhpDocBlocks): array { - return self::mergeOneParentParamImmediatelyInvokedCallable($paramsImmediatelyInvokedCallable, $parent, $parameterMapping); + foreach ($parents as $i => $parent) { + $paramsImmediatelyInvokedCallable = self::mergeOneParentParamImmediatelyInvokedCallable($paramsImmediatelyInvokedCallable, $parent, $parentPhpDocBlocks[$i]); + } + + return $paramsImmediatelyInvokedCallable; } /** * @param array $paramsImmediatelyInvokedCallable * @return array */ - private static function mergeOneParentParamImmediatelyInvokedCallable(array $paramsImmediatelyInvokedCallable, self $parent, InheritedPhpDocParameterMapping $parameterMapping): array + private static function mergeOneParentParamImmediatelyInvokedCallable(array $paramsImmediatelyInvokedCallable, self $parent, PhpDocBlock $phpDocBlock): array { - $parentImmediatelyInvokedCallable = $parameterMapping->transformArrayKeysWithParameterNameMapping($parent->getParamsImmediatelyInvokedCallable()); + $parentImmediatelyInvokedCallable = $phpDocBlock->transformArrayKeysWithParameterNameMapping($parent->getParamsImmediatelyInvokedCallable()); foreach ($parentImmediatelyInvokedCallable as $name => $parentIsImmediatelyInvokedCallable) { if (array_key_exists($name, $paramsImmediatelyInvokedCallable)) { @@ -1065,20 +1156,26 @@ private static function mergeOneParentParamImmediatelyInvokedCallable(array $par /** * @param array $paramsClosureThisTags + * @param array $parents + * @param array $parentPhpDocBlocks * @return array */ - private static function mergeParamClosureThisTags(array $paramsClosureThisTags, self $parent, InheritedPhpDocParameterMapping $parameterMapping, ClassReflection $parentClass): array + private static function mergeParamClosureThisTags(array $paramsClosureThisTags, array $parents, array $parentPhpDocBlocks): array { - return self::mergeOneParentParamClosureThisTag($paramsClosureThisTags, $parent, $parameterMapping, $parentClass); + foreach ($parents as $i => $parent) { + $paramsClosureThisTags = self::mergeOneParentParamClosureThisTag($paramsClosureThisTags, $parent, $parentPhpDocBlocks[$i]); + } + + return $paramsClosureThisTags; } /** * @param array $paramsClosureThisTags * @return array */ - private static function mergeOneParentParamClosureThisTag(array $paramsClosureThisTags, self $parent, InheritedPhpDocParameterMapping $parameterMapping, ClassReflection $parentClass): array + private static function mergeOneParentParamClosureThisTag(array $paramsClosureThisTags, self $parent, PhpDocBlock $phpDocBlock): array { - $parentClosureThisTags = $parameterMapping->transformArrayKeysWithParameterNameMapping($parent->getParamClosureThisTags()); + $parentClosureThisTags = $phpDocBlock->transformArrayKeysWithParameterNameMapping($parent->getParamClosureThisTags()); foreach ($parentClosureThisTags as $name => $parentParamClosureThisTag) { if (array_key_exists($name, $paramsClosureThisTags)) { @@ -1087,9 +1184,9 @@ private static function mergeOneParentParamClosureThisTag(array $paramsClosureTh $paramsClosureThisTags[$name] = self::resolveTemplateTypeInTag( $parentParamClosureThisTag->withType( - $parameterMapping->transformConditionalReturnTypeWithParameterNameMapping($parentParamClosureThisTag->getType()), + $phpDocBlock->transformConditionalReturnTypeWithParameterNameMapping($parentParamClosureThisTag->getType()), ), - $parentClass, + $phpDocBlock, TemplateTypeVariance::createContravariant(), ); } @@ -1097,13 +1194,25 @@ private static function mergeOneParentParamClosureThisTag(array $paramsClosureTh return $paramsClosureThisTags; } - private static function mergePureTags(?bool $isPure, self $parent): ?bool + /** + * @param array $parents + */ + private static function mergePureTags(?bool $isPure, array $parents): ?bool { if ($isPure !== null) { return $isPure; } - return $parent->isPure(); + foreach ($parents as $parent) { + $parentIsPure = $parent->isPure(); + if ($parentIsPure === null) { + continue; + } + + return $parentIsPure; + } + + return null; } /** @@ -1113,14 +1222,14 @@ private static function mergePureTags(?bool $isPure, self $parent): ?bool */ private static function resolveTemplateTypeInTag( TypedTag $tag, - ClassReflection $classReflection, + PhpDocBlock $phpDocBlock, TemplateTypeVariance $positionVariance, ): TypedTag { $type = TemplateTypeHelper::resolveTemplateTypes( $tag->getType(), - $classReflection->getActiveTemplateTypeMap(), - $classReflection->getCallSiteVarianceMap(), + $phpDocBlock->getClassReflection()->getActiveTemplateTypeMap(), + $phpDocBlock->getClassReflection()->getCallSiteVarianceMap(), $positionVariance, ); return $tag->withType($type); diff --git a/src/Reflection/Annotations/AnnotationMethodReflection.php b/src/Reflection/Annotations/AnnotationMethodReflection.php index 2f0b233122b..7fe7ccd14eb 100644 --- a/src/Reflection/Annotations/AnnotationMethodReflection.php +++ b/src/Reflection/Annotations/AnnotationMethodReflection.php @@ -2,7 +2,6 @@ namespace PHPStan\Reflection\Annotations; -use PHPStan\PhpDoc\ResolvedPhpDocBlock; use PHPStan\Reflection\Assertions; use PHPStan\Reflection\ClassMemberReflection; use PHPStan\Reflection\ClassReflection; @@ -189,9 +188,4 @@ public function mustUseReturnValue(): TrinaryLogic return TrinaryLogic::createNo(); } - public function getResolvedPhpDoc(): ?ResolvedPhpDocBlock - { - return null; - } - } diff --git a/src/Reflection/ClassConstantReflection.php b/src/Reflection/ClassConstantReflection.php index 097082b7618..e23db3b606a 100644 --- a/src/Reflection/ClassConstantReflection.php +++ b/src/Reflection/ClassConstantReflection.php @@ -3,7 +3,6 @@ namespace PHPStan\Reflection; use PhpParser\Node\Expr; -use PHPStan\PhpDoc\ResolvedPhpDocBlock; use PHPStan\Type\Type; /** @@ -38,6 +37,4 @@ public function hasNativeType(): bool; public function getNativeType(): ?Type; - public function getResolvedPhpDoc(): ?ResolvedPhpDocBlock; - } diff --git a/src/Reflection/ClassReflection.php b/src/Reflection/ClassReflection.php index 229df8daee6..df1ca5258d6 100644 --- a/src/Reflection/ClassReflection.php +++ b/src/Reflection/ClassReflection.php @@ -1267,13 +1267,32 @@ public function getConstant(string $name): ClassConstantReflection $deprecatedDescription = $deprecation === null ? null : $deprecation->getDescription(); $isDeprecated = $deprecation !== null; - $declaringClass = $this->getAncestorWithClassName($reflectionConstant->getDeclaringClass()->getName()); - if ($declaringClass === null) { - throw new ShouldNotHappenException(); - } + $declaringClass = $this->reflectionProvider->getClass($reflectionConstant->getDeclaringClass()->getName()); $fileName = $declaringClass->getFileName(); $phpDocType = null; - $currentResolvedPhpDoc = $this->findConstantResolvedPhpDoc($reflectionConstant); + $resolvedPhpDoc = $this->stubPhpDocProvider->findClassConstantPhpDoc( + $declaringClass->getName(), + $name, + ); + if ($resolvedPhpDoc === null) { + $docComment = null; + if ($reflectionConstant->getDocComment() !== false) { + $docComment = $reflectionConstant->getDocComment(); + } + $resolvedPhpDoc = $this->phpDocInheritanceResolver->resolvePhpDocForConstant( + $docComment, + $declaringClass, + $fileName, + $name, + ); + } + + if (!$isDeprecated) { + $deprecatedDescription = $resolvedPhpDoc->getDeprecatedTag() !== null ? $resolvedPhpDoc->getDeprecatedTag()->getMessage() : null; + $isDeprecated = $resolvedPhpDoc->isDeprecated(); + } + $isInternal = $resolvedPhpDoc->isInternal(); + $isFinal = $resolvedPhpDoc->isFinal(); $nativeType = null; if ($reflectionConstant->getType() !== null) { @@ -1282,22 +1301,12 @@ public function getConstant(string $name): ClassConstantReflection $nativeType = $this->signatureMapProvider->getClassConstantMetadata($declaringClass->getName(), $name)['nativeType']; } - $resolvedPhpDoc = $this->phpDocInheritanceResolver->resolvePhpDocForConstant( - $declaringClass, - $name, - $currentResolvedPhpDoc, - ); - - $isInternal = false; - $isFinal = false; - if ($resolvedPhpDoc !== null) { - if (!$isDeprecated) { - $deprecatedDescription = $resolvedPhpDoc->getDeprecatedTag() !== null ? $resolvedPhpDoc->getDeprecatedTag()->getMessage() : null; - $isDeprecated = $resolvedPhpDoc->isDeprecated(); + $varTags = $resolvedPhpDoc->getVarTags(); + if (isset($varTags[0]) && count($varTags) === 1) { + $varTag = $varTags[0]; + if ($varTag->isExplicit() || $nativeType === null || $nativeType->isSuperTypeOf($varTag->getType())->yes()) { + $phpDocType = $varTag->getType(); } - $isInternal = $resolvedPhpDoc->isInternal(); - $isFinal = $resolvedPhpDoc->isFinal(); - $phpDocType = self::resolveConstantVarPhpDocType($resolvedPhpDoc, $nativeType, $declaringClass); } $this->constants[$name] = new RealClassClassConstantReflection( @@ -1306,7 +1315,6 @@ public function getConstant(string $name): ClassConstantReflection $reflectionConstant, $nativeType, $phpDocType, - $resolvedPhpDoc, $deprecatedDescription, $isDeprecated, $isInternal, diff --git a/src/Reflection/Dummy/ChangedTypeMethodReflection.php b/src/Reflection/Dummy/ChangedTypeMethodReflection.php index d525f2661d9..b5749c7a292 100644 --- a/src/Reflection/Dummy/ChangedTypeMethodReflection.php +++ b/src/Reflection/Dummy/ChangedTypeMethodReflection.php @@ -2,7 +2,6 @@ namespace PHPStan\Reflection\Dummy; -use PHPStan\PhpDoc\ResolvedPhpDocBlock; use PHPStan\Reflection\Assertions; use PHPStan\Reflection\ClassMemberReflection; use PHPStan\Reflection\ClassReflection; @@ -178,9 +177,4 @@ public function mustUseReturnValue(): TrinaryLogic return $this->reflection->mustUseReturnValue(); } - public function getResolvedPhpDoc(): ?ResolvedPhpDocBlock - { - return $this->reflection->getResolvedPhpDoc(); - } - } diff --git a/src/Reflection/Dummy/DummyClassConstantReflection.php b/src/Reflection/Dummy/DummyClassConstantReflection.php index 768c5bdf275..1b349899418 100644 --- a/src/Reflection/Dummy/DummyClassConstantReflection.php +++ b/src/Reflection/Dummy/DummyClassConstantReflection.php @@ -4,7 +4,6 @@ use PhpParser\Node\Expr; use PHPStan\Node\Expr\TypeExpr; -use PHPStan\PhpDoc\ResolvedPhpDocBlock; use PHPStan\Reflection\ClassConstantReflection; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ReflectionProviderStaticAccessor; @@ -117,9 +116,4 @@ public function getAttributes(): array return []; } - public function getResolvedPhpDoc(): ?ResolvedPhpDocBlock - { - return null; - } - } diff --git a/src/Reflection/Dummy/DummyConstructorReflection.php b/src/Reflection/Dummy/DummyConstructorReflection.php index 844a5340e11..6652c87c475 100644 --- a/src/Reflection/Dummy/DummyConstructorReflection.php +++ b/src/Reflection/Dummy/DummyConstructorReflection.php @@ -2,7 +2,6 @@ namespace PHPStan\Reflection\Dummy; -use PHPStan\PhpDoc\ResolvedPhpDocBlock; use PHPStan\Reflection\Assertions; use PHPStan\Reflection\ClassMemberReflection; use PHPStan\Reflection\ClassReflection; @@ -164,9 +163,4 @@ public function mustUseReturnValue(): TrinaryLogic return TrinaryLogic::createNo(); } - public function getResolvedPhpDoc(): ?ResolvedPhpDocBlock - { - return null; - } - } diff --git a/src/Reflection/Dummy/DummyMethodReflection.php b/src/Reflection/Dummy/DummyMethodReflection.php index afe694a7c56..f81481d4491 100644 --- a/src/Reflection/Dummy/DummyMethodReflection.php +++ b/src/Reflection/Dummy/DummyMethodReflection.php @@ -2,7 +2,6 @@ namespace PHPStan\Reflection\Dummy; -use PHPStan\PhpDoc\ResolvedPhpDocBlock; use PHPStan\Reflection\Assertions; use PHPStan\Reflection\ClassMemberReflection; use PHPStan\Reflection\ClassReflection; @@ -156,9 +155,4 @@ public function mustUseReturnValue(): TrinaryLogic return TrinaryLogic::createNo(); } - public function getResolvedPhpDoc(): ?ResolvedPhpDocBlock - { - return null; - } - } diff --git a/src/Reflection/ExtendedMethodReflection.php b/src/Reflection/ExtendedMethodReflection.php index b9cf6acddf7..c4403de426b 100644 --- a/src/Reflection/ExtendedMethodReflection.php +++ b/src/Reflection/ExtendedMethodReflection.php @@ -2,7 +2,6 @@ namespace PHPStan\Reflection; -use PHPStan\PhpDoc\ResolvedPhpDocBlock; use PHPStan\TrinaryLogic; use PHPStan\Type\Type; @@ -78,6 +77,4 @@ public function getAttributes(): array; */ public function mustUseReturnValue(): TrinaryLogic; - public function getResolvedPhpDoc(): ?ResolvedPhpDocBlock; - } diff --git a/src/Reflection/Native/NativeMethodReflection.php b/src/Reflection/Native/NativeMethodReflection.php index 5cd7475e83e..9a167e98624 100644 --- a/src/Reflection/Native/NativeMethodReflection.php +++ b/src/Reflection/Native/NativeMethodReflection.php @@ -3,7 +3,6 @@ namespace PHPStan\Reflection\Native; use PHPStan\BetterReflection\Reflection\Adapter\ReflectionMethod; -use PHPStan\PhpDoc\ResolvedPhpDocBlock; use PHPStan\Reflection\Assertions; use PHPStan\Reflection\AttributeReflection; use PHPStan\Reflection\ClassMemberReflection; @@ -32,7 +31,6 @@ public function __construct( private ReflectionProvider $reflectionProvider, private ClassReflection $declaringClass, private ReflectionMethod $reflection, - private ?ResolvedPhpDocBlock $resolvedPhpDocBlock, private array $variants, private ?array $namedArgumentsVariants, private TrinaryLogic $hasSideEffects, @@ -238,9 +236,4 @@ public function mustUseReturnValue(): TrinaryLogic return TrinaryLogic::createNo(); } - public function getResolvedPhpDoc(): ?ResolvedPhpDocBlock - { - return $this->resolvedPhpDocBlock; - } - } diff --git a/src/Reflection/Php/ClosureCallMethodReflection.php b/src/Reflection/Php/ClosureCallMethodReflection.php index b15ec9401b9..773f37bb143 100644 --- a/src/Reflection/Php/ClosureCallMethodReflection.php +++ b/src/Reflection/Php/ClosureCallMethodReflection.php @@ -2,7 +2,6 @@ namespace PHPStan\Reflection\Php; -use PHPStan\PhpDoc\ResolvedPhpDocBlock; use PHPStan\Reflection\Assertions; use PHPStan\Reflection\ClassMemberReflection; use PHPStan\Reflection\ClassReflection; @@ -208,9 +207,4 @@ public function mustUseReturnValue(): TrinaryLogic return $this->nativeMethodReflection->mustUseReturnValue(); } - public function getResolvedPhpDoc(): ?ResolvedPhpDocBlock - { - return $this->nativeMethodReflection->getResolvedPhpDoc(); - } - } diff --git a/src/Reflection/Php/EnumCasesMethodReflection.php b/src/Reflection/Php/EnumCasesMethodReflection.php index d731fc29d90..91d795598bc 100644 --- a/src/Reflection/Php/EnumCasesMethodReflection.php +++ b/src/Reflection/Php/EnumCasesMethodReflection.php @@ -2,7 +2,6 @@ namespace PHPStan\Reflection\Php; -use PHPStan\PhpDoc\ResolvedPhpDocBlock; use PHPStan\Reflection\Assertions; use PHPStan\Reflection\ClassMemberReflection; use PHPStan\Reflection\ClassReflection; @@ -167,9 +166,4 @@ public function mustUseReturnValue(): TrinaryLogic return TrinaryLogic::createNo(); } - public function getResolvedPhpDoc(): ?ResolvedPhpDocBlock - { - return null; - } - } diff --git a/src/Reflection/Php/PhpClassReflectionExtension.php b/src/Reflection/Php/PhpClassReflectionExtension.php index 22517d447f6..34306232bf2 100644 --- a/src/Reflection/Php/PhpClassReflectionExtension.php +++ b/src/Reflection/Php/PhpClassReflectionExtension.php @@ -231,7 +231,6 @@ private function createProperty( $classReflection->getNativeReflection()->getProperty($propertyName), getHook: null, setHook: null, - resolvedPhpDocBlock: null, deprecatedDescription: null, isDeprecated: false, isInternal: false, @@ -270,29 +269,14 @@ private function createProperty( } if ($constructorName === null) { - $currentResolvedPhpDoc = $this->stubPhpDocProvider->findPropertyPhpDoc($declaringClassName, $propertyName); - if ( - $currentResolvedPhpDoc === null - && $declaringTraitName !== null - ) { - $currentResolvedPhpDoc = $this->stubPhpDocProvider->findPropertyPhpDoc($declaringTraitName, $propertyName); - } - if ($currentResolvedPhpDoc === null && $docComment !== null) { - $currentResolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( - $declaringClassReflection->getFileName(), - $declaringClassName, - $declaringTraitName, - null, - $docComment, - ); - } $resolvedPhpDoc = $this->phpDocInheritanceResolver->resolvePhpDocForProperty( + $docComment, $declaringClassReflection, + $declaringClassReflection->getFileName(), + $declaringTraitName, $propertyName, - $currentResolvedPhpDoc, ); } elseif ($docComment !== null) { - // todo could call phpDocInheritanceResolver too $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( $declaringClassReflection->getFileName(), $declaringClassName, @@ -301,6 +285,7 @@ private function createProperty( $docComment, ); } + $phpDocBlockClassReflection = $declaringClassReflection; if ($resolvedPhpDoc !== null) { $varTags = $resolvedPhpDoc->getVarTags(); @@ -312,8 +297,8 @@ private function createProperty( $phpDocType = $phpDocType !== null ? TemplateTypeHelper::resolveTemplateTypes( $phpDocType, - $declaringClassReflection->getActiveTemplateTypeMap(), - $declaringClassReflection->getCallSiteVarianceMap(), + $phpDocBlockClassReflection->getActiveTemplateTypeMap(), + $phpDocBlockClassReflection->getCallSiteVarianceMap(), TemplateTypeVariance::createInvariant(), ) : null; @@ -329,12 +314,23 @@ private function createProperty( if ($phpDocType === null) { if (isset($constructorName)) { - $resolvedConstructorPhpDoc = $declaringClassReflection->getConstructor()->getResolvedPhpDoc(); - if ($resolvedConstructorPhpDoc !== null) { - $paramTags = $resolvedConstructorPhpDoc->getParamTags(); - if (isset($paramTags[$propertyReflection->getName()])) { - $phpDocType = $paramTags[$propertyReflection->getName()]->getType(); - } + $constructorDocComment = $declaringClassReflection->getConstructor()->getDocComment(); + $nativeClassReflection = $declaringClassReflection->getNativeReflection(); + $positionalParameterNames = []; + if ($nativeClassReflection->getConstructor() !== null) { + $positionalParameterNames = array_map(static fn (ReflectionParameter $parameter): string => $parameter->getName(), $nativeClassReflection->getConstructor()->getParameters()); + } + $resolvedConstructorPhpDoc = $this->phpDocInheritanceResolver->resolvePhpDocForMethod( + $constructorDocComment, + $declaringClassReflection->getFileName(), + $declaringClassReflection, + $declaringTraitName, + $constructorName, + $positionalParameterNames, + ); + $paramTags = $resolvedConstructorPhpDoc->getParamTags(); + if (isset($paramTags[$propertyReflection->getName()])) { + $phpDocType = $paramTags[$propertyReflection->getName()]->getType(); } } } @@ -432,7 +428,6 @@ private function createProperty( $propertyReflection, $getHook, $setHook, - $resolvedPhpDoc, $deprecatedDescription, $isDeprecated, $isInternal, @@ -489,7 +484,6 @@ private function createProperty( $propertyReflection, $getHook, $setHook, - $nativeProperty->getResolvedPhpDoc(), $deprecatedDescription, $isDeprecated, $isInternal, @@ -636,92 +630,112 @@ private function createMethod( foreach ($methodSignature->getParameters() as $parameter) { $phpDocParameterNameMapping[$parameter->getName()] = $parameter->getName(); } + $stubPhpDocReturnType = null; + $stubPhpDocParameterTypes = []; + $stubPhpDocParameterVariadicity = []; $phpDocParameterTypes = []; $phpDocReturnType = null; + $stubPhpDocPair = null; + $stubPhpParameterOutTypes = []; $phpDocParameterOutTypes = []; $immediatelyInvokedCallableParameters = []; $closureThisParameters = []; - $currentResolvedPhpDoc = null; - $phpDocDeclaringClass = $declaringClass; - $phpDocFromStubs = false; + $stubImmediatelyInvokedCallableParameters = []; + $stubClosureThisParameters = []; if (count($methodSignatures) === 1) { $stubPhpDocPair = $this->findMethodPhpDocIncludingAncestors($declaringClass, $declaringClass, $methodReflection->getName(), array_map(static fn (ParameterSignature $parameterSignature): string => $parameterSignature->getName(), $methodSignature->getParameters())); if ($stubPhpDocPair !== null) { - [$currentResolvedPhpDoc, $phpDocDeclaringClass] = $stubPhpDocPair; - $phpDocFromStubs = true; + [$stubPhpDoc, $stubDeclaringClass] = $stubPhpDocPair; + $templateTypeMap = $stubDeclaringClass->getActiveTemplateTypeMap(); + $callSiteVarianceMap = $stubDeclaringClass->getCallSiteVarianceMap(); + $returnTag = $stubPhpDoc->getReturnTag(); + $stubImmediatelyInvokedCallableParameters = array_map(static fn (bool $immediate) => TrinaryLogic::createFromBoolean($immediate), $stubPhpDoc->getParamsImmediatelyInvokedCallable()); + if ($returnTag !== null) { + $stubPhpDocReturnType = TemplateTypeHelper::resolveTemplateTypes( + $returnTag->getType(), + $templateTypeMap, + $callSiteVarianceMap, + TemplateTypeVariance::createCovariant(), + ); + } + + $stubClosureThisParameters = array_map(static fn ($tag) => $tag->getType(), $stubPhpDoc->getParamClosureThisTags()); + foreach ($stubPhpDoc->getParamTags() as $name => $paramTag) { + $stubPhpDocParameterTypes[$name] = TemplateTypeHelper::resolveTemplateTypes( + $paramTag->getType(), + $templateTypeMap, + $callSiteVarianceMap, + TemplateTypeVariance::createContravariant(), + ); + $stubPhpDocParameterVariadicity[$name] = $paramTag->isVariadic(); + } + + $throwsTag = $stubPhpDoc->getThrowsTag(); + if ($throwsTag !== null) { + $throwType = $throwsTag->getType(); + } + + $asserts = Assertions::createFromResolvedPhpDocBlock($stubPhpDoc); + $acceptsNamedArguments = $stubPhpDoc->acceptsNamedArguments(); + + $selfOutTypeTag = $stubPhpDoc->getSelfOutTag(); + if ($selfOutTypeTag !== null) { + $selfOutType = $selfOutTypeTag->getType(); + } + + foreach ($stubPhpDoc->getParamOutTags() as $name => $paramOutTag) { + $stubPhpParameterOutTypes[$name] = TemplateTypeHelper::resolveTemplateTypes( + $paramOutTag->getType(), + $templateTypeMap, + $callSiteVarianceMap, + TemplateTypeVariance::createCovariant(), + ); + } + + if ($declaringClassName === $stubDeclaringClass->getName() && $stubPhpDoc->hasPhpDocString()) { + $phpDocComment = $stubPhpDoc->getPhpDocString(); + } } } - if ( - $currentResolvedPhpDoc === null - && $methodReflection->getDocComment() !== false - && $methodReflection->getFileName() !== false - ) { - $currentResolvedPhpDoc = $this->phpDocInheritanceResolver->resolvePhpDocForMethod( - $declaringClass, - $methodReflection->getName(), - $this->fileTypeMapper->getResolvedPhpDoc( - $methodReflection->getFileName(), + if ($stubPhpDocPair === null && $methodReflection->getDocComment() !== false) { + $filename = $methodReflection->getFileName(); + if ($filename !== false) { + $phpDocBlock = $this->fileTypeMapper->getResolvedPhpDoc( + $filename, $declaringClassName, null, $methodReflection->getName(), $methodReflection->getDocComment(), - ), - array_map(static fn (ReflectionParameter $parameter): string => $parameter->getName(), $methodReflection->getParameters()), - ); - } - - if ($currentResolvedPhpDoc !== null) { - $templateTypeMap = $phpDocDeclaringClass->getActiveTemplateTypeMap(); - $callSiteVarianceMap = $phpDocDeclaringClass->getCallSiteVarianceMap(); - $returnTag = $currentResolvedPhpDoc->getReturnTag(); - $immediatelyInvokedCallableParameters = array_map(static fn (bool $immediate) => TrinaryLogic::createFromBoolean($immediate), $currentResolvedPhpDoc->getParamsImmediatelyInvokedCallable()); - if ($returnTag !== null && count($methodSignatures) === 1) { - $phpDocReturnType = TemplateTypeHelper::resolveTemplateTypes( - $returnTag->getType(), - $templateTypeMap, - $callSiteVarianceMap, - TemplateTypeVariance::createCovariant(), ); - } - - $closureThisParameters = array_map(static fn ($tag) => $tag->getType(), $currentResolvedPhpDoc->getParamClosureThisTags()); - foreach ($currentResolvedPhpDoc->getParamTags() as $name => $paramTag) { - $phpDocParameterTypes[$name] = TemplateTypeHelper::resolveTemplateTypes( - $paramTag->getType(), - $templateTypeMap, - $callSiteVarianceMap, - TemplateTypeVariance::createContravariant(), - ); - } - - $throwsTag = $currentResolvedPhpDoc->getThrowsTag(); - if ($throwsTag !== null) { - $throwType = $throwsTag->getType(); - } - - $asserts = Assertions::createFromResolvedPhpDocBlock($currentResolvedPhpDoc); - $acceptsNamedArguments = $currentResolvedPhpDoc->acceptsNamedArguments(); - $isPure ??= $currentResolvedPhpDoc->isPure(); + $throwsTag = $phpDocBlock->getThrowsTag(); + if ($throwsTag !== null) { + $throwType = $throwsTag->getType(); + } + $returnTag = $phpDocBlock->getReturnTag(); + if ($returnTag !== null && count($methodSignatures) === 1) { + $phpDocReturnType = $returnTag->getType(); + } + $immediatelyInvokedCallableParameters = array_map(static fn ($immediate) => TrinaryLogic::createFromBoolean($immediate), $phpDocBlock->getParamsImmediatelyInvokedCallable()); + $closureThisParameters = array_map(static fn ($tag) => $tag->getType(), $phpDocBlock->getParamClosureThisTags()); + foreach ($phpDocBlock->getParamTags() as $name => $paramTag) { + $phpDocParameterTypes[$name] = $paramTag->getType(); + } + $asserts = Assertions::createFromResolvedPhpDocBlock($phpDocBlock); + $acceptsNamedArguments = $phpDocBlock->acceptsNamedArguments(); - $selfOutTypeTag = $currentResolvedPhpDoc->getSelfOutTag(); - if ($selfOutTypeTag !== null) { - $selfOutType = $selfOutTypeTag->getType(); - } + $selfOutTypeTag = $phpDocBlock->getSelfOutTag(); + if ($selfOutTypeTag !== null) { + $selfOutType = $selfOutTypeTag->getType(); + } - foreach ($currentResolvedPhpDoc->getParamOutTags() as $name => $paramOutTag) { - $phpDocParameterOutTypes[$name] = TemplateTypeHelper::resolveTemplateTypes( - $paramOutTag->getType(), - $templateTypeMap, - $callSiteVarianceMap, - TemplateTypeVariance::createCovariant(), - ); - } + if ($phpDocBlock->hasPhpDocString()) { + $phpDocComment = $phpDocBlock->getPhpDocString(); + } - if ($currentResolvedPhpDoc->hasPhpDocString()) { - $phpDocComment = $currentResolvedPhpDoc->getPhpDocString(); - } + foreach ($phpDocBlock->getParamOutTags() as $name => $paramOutTag) { + $phpDocParameterOutTypes[$name] = $paramOutTag->getType(); + } - if (!$phpDocFromStubs) { $signatureParameters = $methodSignature->getParameters(); foreach ($methodReflection->getParameters() as $paramI => $reflectionParameter) { if (!array_key_exists($paramI, $signatureParameters)) { @@ -732,7 +746,7 @@ private function createMethod( } } } - $variantsByType[$signatureType][] = $this->createNativeMethodVariant($methodSignature, $phpDocParameterTypes, $phpDocReturnType, $phpDocParameterNameMapping, $phpDocParameterOutTypes, $immediatelyInvokedCallableParameters, $closureThisParameters, $phpDocFromStubs, $signatureType !== 'named'); + $variantsByType[$signatureType][] = $this->createNativeMethodVariant($methodSignature, $stubPhpDocParameterTypes, $stubPhpDocParameterVariadicity, $stubPhpDocReturnType, $phpDocParameterTypes, $phpDocReturnType, $phpDocParameterNameMapping, $stubPhpParameterOutTypes, $phpDocParameterOutTypes, $stubImmediatelyInvokedCallableParameters, $immediatelyInvokedCallableParameters, $stubClosureThisParameters, $closureThisParameters, $signatureType !== 'named'); } } @@ -749,7 +763,6 @@ private function createMethod( $this->reflectionProviderProvider->getReflectionProvider(), $declaringClass, $methodReflection, - $currentResolvedPhpDoc ?? null, $variantsByType['positional'], $variantsByType['named'] ?? null, $isPure !== null ? TrinaryLogic::createFromBoolean(!$isPure) : TrinaryLogic::createMaybe(), @@ -775,7 +788,8 @@ public function createUserlandMethodReflection(ClassReflection $fileDeclaringCla $deprecation = $this->deprecationProvider->getMethodDeprecation($methodReflection); $deprecatedDescription = $deprecation === null ? null : $deprecation->getDescription(); $isDeprecated = $deprecation !== null; - $currentResolvedPhpDoc = null; + + $resolvedPhpDoc = null; $stubPhpDocPair = $this->findMethodPhpDocIncludingAncestors($fileDeclaringClass, $fileDeclaringClass, $methodReflection->getName(), array_map(static fn (ReflectionParameter $parameter): string => $parameter->getName(), $methodReflection->getParameters())); $phpDocBlockClassReflection = $fileDeclaringClass; @@ -796,26 +810,24 @@ public function createUserlandMethodReflection(ClassReflection $fileDeclaringCla } if ($stubPhpDocPair !== null) { - [$currentResolvedPhpDoc, $phpDocBlockClassReflection] = $stubPhpDocPair; + [$resolvedPhpDoc, $phpDocBlockClassReflection] = $stubPhpDocPair; } - if ($currentResolvedPhpDoc === null && $methodReflection->getDocComment() !== false && $actualDeclaringClass->getFileName() !== null) { - $currentResolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( + if ($resolvedPhpDoc === null) { + $docComment = $methodReflection->getDocComment() !== false ? $methodReflection->getDocComment() : null; + $positionalParameterNames = array_map(static fn (ReflectionParameter $parameter): string => $parameter->getName(), $methodReflection->getParameters()); + + $resolvedPhpDoc = $this->phpDocInheritanceResolver->resolvePhpDocForMethod( + $docComment, $actualDeclaringClass->getFileName(), - $actualDeclaringClass->getName(), + $actualDeclaringClass, $declaringTraitName, $methodReflection->getName(), - $methodReflection->getDocComment(), + $positionalParameterNames, ); + $phpDocBlockClassReflection = $fileDeclaringClass; } - $resolvedPhpDoc = $this->phpDocInheritanceResolver->resolvePhpDocForMethod( - $actualDeclaringClass, - $methodReflection->getName(), - $currentResolvedPhpDoc, - array_map(static fn (ReflectionParameter $parameter): string => $parameter->getName(), $methodReflection->getParameters()), - ); - $declaringTrait = null; $reflectionProvider = $this->reflectionProviderProvider->getReflectionProvider(); if ( @@ -863,11 +875,47 @@ public function createUserlandMethodReflection(ClassReflection $fileDeclaringCla } } + $templateTypeMap = $resolvedPhpDoc->getTemplateTypeMap(); + $immediatelyInvokedCallableParameters = array_map(static fn (bool $immediate) => TrinaryLogic::createFromBoolean($immediate), $resolvedPhpDoc->getParamsImmediatelyInvokedCallable()); + $closureThisParameters = array_map(static fn ($tag) => $tag->getType(), $resolvedPhpDoc->getParamClosureThisTags()); + + foreach ($resolvedPhpDoc->getParamTags() as $paramName => $paramTag) { + if (array_key_exists($paramName, $phpDocParameterTypes)) { + continue; + } + $phpDocParameterTypes[$paramName] = $paramTag->getType(); + } + foreach ($phpDocParameterTypes as $paramName => $paramType) { + $phpDocParameterTypes[$paramName] = TemplateTypeHelper::resolveTemplateTypes( + $paramType, + $phpDocBlockClassReflection->getActiveTemplateTypeMap(), + $phpDocBlockClassReflection->getCallSiteVarianceMap(), + TemplateTypeVariance::createContravariant(), + ); + } + + $phpDocParameterOutTypes = []; + foreach ($resolvedPhpDoc->getParamOutTags() as $paramName => $paramOutTag) { + $phpDocParameterOutTypes[$paramName] = TemplateTypeHelper::resolveTemplateTypes( + $paramOutTag->getType(), + $phpDocBlockClassReflection->getActiveTemplateTypeMap(), + $phpDocBlockClassReflection->getCallSiteVarianceMap(), + TemplateTypeVariance::createCovariant(), + ); + } + $nativeReturnType = TypehintHelper::decideTypeFromReflection( $methodReflection->getReturnType(), selfClass: $actualDeclaringClass, ); - + $phpDocReturnType = $this->getPhpDocReturnType($phpDocBlockClassReflection, $resolvedPhpDoc, $nativeReturnType); + $phpDocThrowType = $resolvedPhpDoc->getThrowsTag() !== null ? $resolvedPhpDoc->getThrowsTag()->getType() : null; + if (!$isDeprecated) { + $deprecatedDescription = $resolvedPhpDoc->getDeprecatedTag() !== null ? $resolvedPhpDoc->getDeprecatedTag()->getMessage() : null; + $isDeprecated = $resolvedPhpDoc->isDeprecated(); + } + $isInternal = $resolvedPhpDoc->isInternal(); + $isFinal = $resolvedPhpDoc->isFinal(); $isPure = null; if ($actualDeclaringClass->isBuiltin() || $actualDeclaringClass->isEnum()) { foreach (array_keys($actualDeclaringClass->getAncestors()) as $className) { @@ -880,59 +928,13 @@ public function createUserlandMethodReflection(ClassReflection $fileDeclaringCla } } - $phpDocParameterOutTypes = []; - $phpDocReturnType = null; - $templateTypeMap = TemplateTypeMap::createEmpty(); - $immediatelyInvokedCallableParameters = []; - $closureThisParameters = []; - $phpDocThrowType = null; - $isInternal = false; - $isFinal = false; - $asserts = Assertions::createEmpty(); - $acceptsNamedArguments = true; - $selfOutType = null; + $isPure ??= $resolvedPhpDoc->isPure(); + $asserts = Assertions::createFromResolvedPhpDocBlock($resolvedPhpDoc); + $acceptsNamedArguments = $resolvedPhpDoc->acceptsNamedArguments(); + $selfOutType = $resolvedPhpDoc->getSelfOutTag() !== null ? $resolvedPhpDoc->getSelfOutTag()->getType() : null; $phpDocComment = null; - if ($resolvedPhpDoc !== null) { - $templateTypeMap = $resolvedPhpDoc->getTemplateTypeMap(); - $immediatelyInvokedCallableParameters = array_map(static fn (bool $immediate) => TrinaryLogic::createFromBoolean($immediate), $resolvedPhpDoc->getParamsImmediatelyInvokedCallable()); - $closureThisParameters = array_map(static fn ($tag) => $tag->getType(), $resolvedPhpDoc->getParamClosureThisTags()); - $phpDocReturnType = $this->getPhpDocReturnType($phpDocBlockClassReflection, $resolvedPhpDoc, $nativeReturnType); - $phpDocThrowType = $resolvedPhpDoc->getThrowsTag() !== null ? $resolvedPhpDoc->getThrowsTag()->getType() : null; - foreach ($resolvedPhpDoc->getParamTags() as $paramName => $paramTag) { - if (array_key_exists($paramName, $phpDocParameterTypes)) { - continue; - } - $phpDocParameterTypes[$paramName] = $paramTag->getType(); - } - foreach ($phpDocParameterTypes as $paramName => $paramType) { - $phpDocParameterTypes[$paramName] = TemplateTypeHelper::resolveTemplateTypes( - $paramType, - $phpDocBlockClassReflection->getActiveTemplateTypeMap(), - $phpDocBlockClassReflection->getCallSiteVarianceMap(), - TemplateTypeVariance::createContravariant(), - ); - } - foreach ($resolvedPhpDoc->getParamOutTags() as $paramName => $paramOutTag) { - $phpDocParameterOutTypes[$paramName] = TemplateTypeHelper::resolveTemplateTypes( - $paramOutTag->getType(), - $phpDocBlockClassReflection->getActiveTemplateTypeMap(), - $phpDocBlockClassReflection->getCallSiteVarianceMap(), - TemplateTypeVariance::createCovariant(), - ); - } - if (!$isDeprecated) { - $deprecatedDescription = $resolvedPhpDoc->getDeprecatedTag() !== null ? $resolvedPhpDoc->getDeprecatedTag()->getMessage() : null; - $isDeprecated = $resolvedPhpDoc->isDeprecated(); - } - $isInternal = $resolvedPhpDoc->isInternal(); - $isFinal = $resolvedPhpDoc->isFinal(); - $isPure ??= $resolvedPhpDoc->isPure(); - $asserts = Assertions::createFromResolvedPhpDocBlock($resolvedPhpDoc); - $acceptsNamedArguments = $resolvedPhpDoc->acceptsNamedArguments(); - $selfOutType = $resolvedPhpDoc->getSelfOutTag() !== null ? $resolvedPhpDoc->getSelfOutTag()->getType() : null; - if ($resolvedPhpDoc->hasPhpDocString()) { - $phpDocComment = $resolvedPhpDoc->getPhpDocString(); - } + if ($resolvedPhpDoc->hasPhpDocString()) { + $phpDocComment = $resolvedPhpDoc->getPhpDocString(); } return $this->methodReflectionFactory->create( @@ -943,7 +945,6 @@ public function createUserlandMethodReflection(ClassReflection $fileDeclaringCla $phpDocParameterTypes, $phpDocReturnType, $phpDocThrowType, - $resolvedPhpDoc, $deprecatedDescription, $isDeprecated, $isInternal, @@ -961,21 +962,31 @@ public function createUserlandMethodReflection(ClassReflection $fileDeclaringCla } /** + * @param array $stubPhpDocParameterTypes + * @param array $stubPhpDocParameterVariadicity * @param array $phpDocParameterTypes * @param array $phpDocParameterNameMapping + * @param array $stubPhpDocParameterOutTypes * @param array $phpDocParameterOutTypes + * @param array $stubImmediatelyInvokedCallableParameters * @param array $immediatelyInvokedCallableParameters + * @param array $stubClosureThisParameters * @param array $closureThisParameters */ private function createNativeMethodVariant( FunctionSignature $methodSignature, + array $stubPhpDocParameterTypes, + array $stubPhpDocParameterVariadicity, + ?Type $stubPhpDocReturnType, array $phpDocParameterTypes, ?Type $phpDocReturnType, array $phpDocParameterNameMapping, + array $stubPhpDocParameterOutTypes, array $phpDocParameterOutTypes, + array $stubImmediatelyInvokedCallableParameters, array $immediatelyInvokedCallableParameters, + array $stubClosureThisParameters, array $closureThisParameters, - bool $phpDocFromStubs, bool $usePhpDocParameterNames, ): ExtendedFunctionVariant { @@ -987,23 +998,32 @@ private function createNativeMethodVariant( $phpDocParameterName = $phpDocParameterNameMapping[$parameterSignature->getName()] ?? $parameterSignature->getName(); - if (isset($phpDocParameterTypes[$phpDocParameterName])) { + if (isset($stubPhpDocParameterTypes[$parameterSignature->getName()])) { + $type = $stubPhpDocParameterTypes[$parameterSignature->getName()]; + $phpDocType = $stubPhpDocParameterTypes[$parameterSignature->getName()]; + } elseif (isset($phpDocParameterTypes[$phpDocParameterName])) { $phpDocType = $phpDocParameterTypes[$phpDocParameterName]; - $type = $phpDocFromStubs ? $phpDocType : TypehintHelper::decideType($parameterSignature->getType(), $phpDocType); + $type = TypehintHelper::decideType($parameterSignature->getType(), $phpDocType); } - if (isset($phpDocParameterOutTypes[$phpDocParameterName])) { + if (isset($stubPhpDocParameterOutTypes[$parameterSignature->getName()])) { + $parameterOutType = $stubPhpDocParameterOutTypes[$parameterSignature->getName()]; + } elseif (isset($phpDocParameterOutTypes[$phpDocParameterName])) { $parameterOutType = $phpDocParameterOutTypes[$phpDocParameterName]; } - if (isset($immediatelyInvokedCallableParameters[$phpDocParameterName])) { + if (isset($stubImmediatelyInvokedCallableParameters[$parameterSignature->getName()])) { + $immediatelyInvoked = $stubImmediatelyInvokedCallableParameters[$parameterSignature->getName()]; + } elseif (isset($immediatelyInvokedCallableParameters[$phpDocParameterName])) { $immediatelyInvoked = $immediatelyInvokedCallableParameters[$phpDocParameterName]; } else { $immediatelyInvoked = TrinaryLogic::createMaybe(); } $closureThisType = null; - if (isset($closureThisParameters[$phpDocParameterName])) { + if (isset($stubClosureThisParameters[$parameterSignature->getName()])) { + $closureThisType = $stubClosureThisParameters[$parameterSignature->getName()]; + } elseif (isset($closureThisParameters[$phpDocParameterName])) { $closureThisType = $closureThisParameters[$phpDocParameterName]; } @@ -1016,7 +1036,7 @@ private function createNativeMethodVariant( $phpDocType ?? new MixedType(), $parameterSignature->getNativeType(), $parameterSignature->passedByReference(), - $parameterSignature->isVariadic(), + $stubPhpDocParameterVariadicity[$parameterSignature->getName()] ?? $parameterSignature->isVariadic(), $parameterSignature->getDefaultValue(), $parameterOutType ?? $parameterSignature->getOutType(), $immediatelyInvoked, @@ -1025,8 +1045,9 @@ private function createNativeMethodVariant( ); } - if ($phpDocFromStubs && $phpDocReturnType !== null) { - $returnType = $phpDocReturnType; + if ($stubPhpDocReturnType !== null) { + $returnType = $stubPhpDocReturnType; + $phpDocReturnType = $stubPhpDocReturnType; } else { $returnType = TypehintHelper::decideType($methodSignature->getReturnType(), $phpDocReturnType); } diff --git a/src/Reflection/Php/PhpMethodFromParserNodeReflection.php b/src/Reflection/Php/PhpMethodFromParserNodeReflection.php index 25aa5fefa8b..7116cf34ff6 100644 --- a/src/Reflection/Php/PhpMethodFromParserNodeReflection.php +++ b/src/Reflection/Php/PhpMethodFromParserNodeReflection.php @@ -4,7 +4,6 @@ use PhpParser\Node; use PhpParser\Node\Stmt\ClassMethod; -use PHPStan\PhpDoc\ResolvedPhpDocBlock; use PHPStan\Reflection\Assertions; use PHPStan\Reflection\AttributeReflection; use PHPStan\Reflection\ClassMemberReflection; @@ -66,7 +65,6 @@ public function __construct( Assertions $assertions, private ?Type $selfOutType, ?string $phpDocComment, - private ?ResolvedPhpDocBlock $resolvedPhpDoc, array $parameterOutTypes, array $immediatelyInvokedCallableParameters, array $phpDocClosureThisTypeParameters, @@ -298,9 +296,4 @@ public function hasSideEffects(): TrinaryLogic return TrinaryLogic::createMaybe(); } - public function getResolvedPhpDoc(): ?ResolvedPhpDocBlock - { - return $this->resolvedPhpDoc; - } - } diff --git a/src/Reflection/Php/PhpMethodReflection.php b/src/Reflection/Php/PhpMethodReflection.php index 34f28635007..0da97725e74 100644 --- a/src/Reflection/Php/PhpMethodReflection.php +++ b/src/Reflection/Php/PhpMethodReflection.php @@ -6,7 +6,6 @@ use PHPStan\BetterReflection\Reflection\Adapter\ReflectionParameter; use PHPStan\DependencyInjection\GenerateFactory; use PHPStan\Internal\DeprecatedAttributeHelper; -use PHPStan\PhpDoc\ResolvedPhpDocBlock; use PHPStan\Reflection\Assertions; use PHPStan\Reflection\AttributeReflection; use PHPStan\Reflection\AttributeReflectionFactory; @@ -74,7 +73,6 @@ public function __construct( private array $phpDocParameterTypes, private ?Type $phpDocReturnType, private ?Type $phpDocThrowType, - private ?ResolvedPhpDocBlock $resolvedPhpDocBlock, private ?string $deprecatedDescription, private bool $isDeprecated, private bool $isInternal, @@ -415,7 +413,6 @@ public function changePropertyGetHookPhpDocType(Type $phpDocType): self $this->phpDocParameterTypes, $phpDocType, $this->phpDocThrowType, - $this->resolvedPhpDocBlock, $this->deprecatedDescription, $this->isDeprecated, $this->isInternal, @@ -448,7 +445,6 @@ public function changePropertySetHookPhpDocType(string $parameterName, Type $php $phpDocParameterTypes, $this->phpDocReturnType, $this->phpDocThrowType, - $this->resolvedPhpDocBlock, $this->deprecatedDescription, $this->isDeprecated, $this->isInternal, @@ -480,9 +476,4 @@ public function mustUseReturnValue(): TrinaryLogic return TrinaryLogic::createNo(); } - public function getResolvedPhpDoc(): ?ResolvedPhpDocBlock - { - return $this->resolvedPhpDocBlock; - } - } diff --git a/src/Reflection/Php/PhpMethodReflectionFactory.php b/src/Reflection/Php/PhpMethodReflectionFactory.php index 6ac366fc1b4..ec95a2de818 100644 --- a/src/Reflection/Php/PhpMethodReflectionFactory.php +++ b/src/Reflection/Php/PhpMethodReflectionFactory.php @@ -3,7 +3,6 @@ namespace PHPStan\Reflection\Php; use PHPStan\BetterReflection\Reflection\Adapter\ReflectionMethod; -use PHPStan\PhpDoc\ResolvedPhpDocBlock; use PHPStan\Reflection\Assertions; use PHPStan\Reflection\AttributeReflection; use PHPStan\Reflection\ClassReflection; @@ -29,7 +28,6 @@ public function create( array $phpDocParameterTypes, ?Type $phpDocReturnType, ?Type $phpDocThrowType, - ?ResolvedPhpDocBlock $resolvedPhpDocBlock, ?string $deprecatedDescription, bool $isDeprecated, bool $isInternal, diff --git a/src/Reflection/Php/PhpPropertyReflection.php b/src/Reflection/Php/PhpPropertyReflection.php index 8b30efb00ba..ce6f8a2e37c 100644 --- a/src/Reflection/Php/PhpPropertyReflection.php +++ b/src/Reflection/Php/PhpPropertyReflection.php @@ -3,7 +3,6 @@ namespace PHPStan\Reflection\Php; use PHPStan\BetterReflection\Reflection\Adapter\ReflectionProperty; -use PHPStan\PhpDoc\ResolvedPhpDocBlock; use PHPStan\Reflection\AttributeReflection; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ExtendedMethodReflection; @@ -38,7 +37,6 @@ public function __construct( private ReflectionProperty $reflection, private ?ExtendedMethodReflection $getHook, private ?ExtendedMethodReflection $setHook, - private ?ResolvedPhpDocBlock $resolvedPhpDocBlock, private ?string $deprecatedDescription, private bool $isDeprecated, private bool $isInternal, @@ -330,9 +328,4 @@ public function isDummy(): TrinaryLogic return TrinaryLogic::createNo(); } - public function getResolvedPhpDoc(): ?ResolvedPhpDocBlock - { - return $this->resolvedPhpDocBlock; - } - } diff --git a/src/Reflection/RealClassClassConstantReflection.php b/src/Reflection/RealClassClassConstantReflection.php index d0b69f5eedc..83caa997c64 100644 --- a/src/Reflection/RealClassClassConstantReflection.php +++ b/src/Reflection/RealClassClassConstantReflection.php @@ -5,7 +5,6 @@ use PhpParser\Node\Expr; use PHPStan\BetterReflection\Reflection\Adapter\ReflectionClassConstant; use PHPStan\Internal\DeprecatedAttributeHelper; -use PHPStan\PhpDoc\ResolvedPhpDocBlock; use PHPStan\TrinaryLogic; use PHPStan\Type\Type; use PHPStan\Type\TypehintHelper; @@ -24,7 +23,6 @@ public function __construct( private ReflectionClassConstant $reflection, private ?Type $nativeType, private ?Type $phpDocType, - private ?ResolvedPhpDocBlock $resolvedPhpDocBlock, private ?string $deprecatedDescription, private bool $isDeprecated, private bool $isInternal, @@ -96,11 +94,6 @@ public function getDeclaringClass(): ClassReflection return $this->declaringClass; } - public function getResolvedPhpDoc(): ?ResolvedPhpDocBlock - { - return $this->resolvedPhpDocBlock; - } - public function isStatic(): bool { return true; diff --git a/src/Reflection/ResolvedMethodReflection.php b/src/Reflection/ResolvedMethodReflection.php index 151b3372894..57b51070a67 100644 --- a/src/Reflection/ResolvedMethodReflection.php +++ b/src/Reflection/ResolvedMethodReflection.php @@ -2,7 +2,7 @@ namespace PHPStan\Reflection; -use PHPStan\PhpDoc\ResolvedPhpDocBlock; +use PHPStan\Reflection\Php\PhpMethodReflection; use PHPStan\TrinaryLogic; use PHPStan\Type\Generic\TemplateTypeHelper; use PHPStan\Type\Generic\TemplateTypeMap; @@ -98,6 +98,15 @@ public function getDeclaringClass(): ClassReflection return $this->reflection->getDeclaringClass(); } + public function getDeclaringTrait(): ?ClassReflection + { + if ($this->reflection instanceof PhpMethodReflection) { + return $this->reflection->getDeclaringTrait(); + } + + return null; + } + public function isStatic(): bool { return $this->reflection->isStatic(); @@ -227,9 +236,4 @@ public function mustUseReturnValue(): TrinaryLogic return $this->reflection->mustUseReturnValue(); } - public function getResolvedPhpDoc(): ?ResolvedPhpDocBlock - { - return $this->reflection->getResolvedPhpDoc(); - } - } diff --git a/src/Reflection/Type/IntersectionTypeMethodReflection.php b/src/Reflection/Type/IntersectionTypeMethodReflection.php index 7aab93fa82d..c4083409987 100644 --- a/src/Reflection/Type/IntersectionTypeMethodReflection.php +++ b/src/Reflection/Type/IntersectionTypeMethodReflection.php @@ -2,7 +2,6 @@ namespace PHPStan\Reflection\Type; -use PHPStan\PhpDoc\ResolvedPhpDocBlock; use PHPStan\Reflection\Assertions; use PHPStan\Reflection\ClassMemberReflection; use PHPStan\Reflection\ClassReflection; @@ -260,11 +259,6 @@ public function mustUseReturnValue(): TrinaryLogic return TrinaryLogic::lazyMaxMin($this->methods, static fn (ExtendedMethodReflection $method): TrinaryLogic => $method->mustUseReturnValue()); } - public function getResolvedPhpDoc(): ?ResolvedPhpDocBlock - { - return $this->getMethodWithMostParameters()->getResolvedPhpDoc(); - } - /** * Since every intersected method should be compatible, * selects the method whose variant has the widest parameter list, diff --git a/src/Reflection/Type/UnionTypeMethodReflection.php b/src/Reflection/Type/UnionTypeMethodReflection.php index 89557b4e2c0..e1f7ed37c68 100644 --- a/src/Reflection/Type/UnionTypeMethodReflection.php +++ b/src/Reflection/Type/UnionTypeMethodReflection.php @@ -2,7 +2,6 @@ namespace PHPStan\Reflection\Type; -use PHPStan\PhpDoc\ResolvedPhpDocBlock; use PHPStan\Reflection\Assertions; use PHPStan\Reflection\ClassMemberReflection; use PHPStan\Reflection\ClassReflection; @@ -217,9 +216,4 @@ public function mustUseReturnValue(): TrinaryLogic return TrinaryLogic::lazyExtremeIdentity($this->methods, static fn (ExtendedMethodReflection $method): TrinaryLogic => $method->mustUseReturnValue()); } - public function getResolvedPhpDoc(): ?ResolvedPhpDocBlock - { - return $this->methods[0]->getResolvedPhpDoc(); - } - } diff --git a/src/Reflection/WrappedExtendedMethodReflection.php b/src/Reflection/WrappedExtendedMethodReflection.php index d028d80d04b..6fb39dd2f49 100644 --- a/src/Reflection/WrappedExtendedMethodReflection.php +++ b/src/Reflection/WrappedExtendedMethodReflection.php @@ -2,7 +2,6 @@ namespace PHPStan\Reflection; -use PHPStan\PhpDoc\ResolvedPhpDocBlock; use PHPStan\Reflection\Php\ExtendedDummyParameter; use PHPStan\TrinaryLogic; use PHPStan\Type\Generic\TemplateTypeVarianceMap; @@ -180,9 +179,4 @@ public function mustUseReturnValue(): TrinaryLogic return TrinaryLogic::createNo(); } - public function getResolvedPhpDoc(): ?ResolvedPhpDocBlock - { - return null; - } - } diff --git a/src/Rules/RestrictedUsage/RewrittenDeclaringClassClassConstantReflection.php b/src/Rules/RestrictedUsage/RewrittenDeclaringClassClassConstantReflection.php index a92a6172737..2584f4f8c04 100644 --- a/src/Rules/RestrictedUsage/RewrittenDeclaringClassClassConstantReflection.php +++ b/src/Rules/RestrictedUsage/RewrittenDeclaringClassClassConstantReflection.php @@ -3,7 +3,6 @@ namespace PHPStan\Rules\RestrictedUsage; use PhpParser\Node\Expr; -use PHPStan\PhpDoc\ResolvedPhpDocBlock; use PHPStan\Reflection\ClassConstantReflection; use PHPStan\Reflection\ClassReflection; use PHPStan\TrinaryLogic; @@ -114,9 +113,4 @@ public function getFileName(): ?string return $this->constantReflection->getFileName(); } - public function getResolvedPhpDoc(): ?ResolvedPhpDocBlock - { - return $this->constantReflection->getResolvedPhpDoc(); - } - } diff --git a/src/Rules/RestrictedUsage/RewrittenDeclaringClassMethodReflection.php b/src/Rules/RestrictedUsage/RewrittenDeclaringClassMethodReflection.php index 43fc7ffaedc..c834dfd5b1c 100644 --- a/src/Rules/RestrictedUsage/RewrittenDeclaringClassMethodReflection.php +++ b/src/Rules/RestrictedUsage/RewrittenDeclaringClassMethodReflection.php @@ -2,7 +2,6 @@ namespace PHPStan\Rules\RestrictedUsage; -use PHPStan\PhpDoc\ResolvedPhpDocBlock; use PHPStan\Reflection\Assertions; use PHPStan\Reflection\ClassMemberReflection; use PHPStan\Reflection\ClassReflection; @@ -151,9 +150,4 @@ public function mustUseReturnValue(): TrinaryLogic return $this->methodReflection->mustUseReturnValue(); } - public function getResolvedPhpDoc(): ?ResolvedPhpDocBlock - { - return $this->methodReflection->getResolvedPhpDoc(); - } - } diff --git a/tests/PHPStan/Analyser/AnalyserTest.php b/tests/PHPStan/Analyser/AnalyserTest.php index 39da6a5be9e..ee40a2188b3 100644 --- a/tests/PHPStan/Analyser/AnalyserTest.php +++ b/tests/PHPStan/Analyser/AnalyserTest.php @@ -21,6 +21,7 @@ use PHPStan\Node\Printer\Printer; use PHPStan\Parser\RichParser; use PHPStan\PhpDoc\PhpDocInheritanceResolver; +use PHPStan\PhpDoc\StubPhpDocProvider; use PHPStan\Reflection\ClassReflectionFactory; use PHPStan\Reflection\InitializerExprTypeResolver; use PHPStan\Rules\AlwaysFailRule; @@ -807,7 +808,7 @@ private function createAnalyser(): Analyser $container = self::getContainer(); $typeSpecifier = $container->getService('typeSpecifier'); $fileTypeMapper = $container->getByType(FileTypeMapper::class); - $phpDocInheritanceResolver = new PhpDocInheritanceResolver($fileTypeMapper); + $phpDocInheritanceResolver = new PhpDocInheritanceResolver($fileTypeMapper, $container->getByType(StubPhpDocProvider::class)); $nodeScopeResolver = new NodeScopeResolver( $container, diff --git a/tests/PHPStan/Rules/Constants/data/overriding-constant-native-types.php b/tests/PHPStan/Rules/Constants/data/overriding-constant-native-types.php index e96d4c450e7..9b8afbbcadf 100644 --- a/tests/PHPStan/Rules/Constants/data/overriding-constant-native-types.php +++ b/tests/PHPStan/Rules/Constants/data/overriding-constant-native-types.php @@ -48,22 +48,3 @@ class PharChild extends \Phar const int|string NONE = 1; // error } - -class ResultA { - public function __construct(public string $value) {} -} - -class ResultB extends ResultA { - public function rot13(): string { return str_rot13($this->value); } -} - -/** @template-implements I */ -class In implements I { - public const string ResultType = ResultB::class; -} - -/** @template T of ResultA */ -interface I { - /** @var class-string */ - public const string ResultType = ResultA::class; -} From 685c2a95b4a916c504d1dd80079bb2e0f3e7bf14 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Mon, 4 May 2026 11:43:36 +0200 Subject: [PATCH 8/9] Revert "Declare Memcached methods as impure (#5536)" This reverts commit fe750c5587a75b175150feeade5894c89918134f. --- conf/config.neon | 1 - .../Php/PhpClassReflectionExtension.php | 20 +++------- stubs/Memcached.stub | 16 -------- .../PHPStan/Rules/Comparison/Bug14534Test.php | 40 ------------------- ...rictComparisonOfDifferentTypesRuleTest.php | 5 --- tests/PHPStan/Rules/Comparison/bug-14534.neon | 3 -- .../Rules/Comparison/data/bug-13444.php | 28 ------------- .../Rules/Comparison/data/bug-14534.php | 21 ---------- .../Rules/Comparison/data/bug-14534.stub | 6 --- 9 files changed, 5 insertions(+), 135 deletions(-) delete mode 100644 stubs/Memcached.stub delete mode 100644 tests/PHPStan/Rules/Comparison/Bug14534Test.php delete mode 100644 tests/PHPStan/Rules/Comparison/bug-14534.neon delete mode 100644 tests/PHPStan/Rules/Comparison/data/bug-13444.php delete mode 100644 tests/PHPStan/Rules/Comparison/data/bug-14534.php delete mode 100644 tests/PHPStan/Rules/Comparison/data/bug-14534.stub diff --git a/conf/config.neon b/conf/config.neon index d9f02ebcc36..51e81212fb2 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -125,7 +125,6 @@ parameters: universalObjectCratesClasses: - stdClass stubFiles: - - ../stubs/Memcached.stub - ../stubs/ReflectionAttribute.stub - ../stubs/ReflectionClassConstant.stub - ../stubs/ReflectionFunctionAbstract.stub diff --git a/src/Reflection/Php/PhpClassReflectionExtension.php b/src/Reflection/Php/PhpClassReflectionExtension.php index 34306232bf2..62c02c0e8cf 100644 --- a/src/Reflection/Php/PhpClassReflectionExtension.php +++ b/src/Reflection/Php/PhpClassReflectionExtension.php @@ -613,12 +613,6 @@ private function createMethod( $acceptsNamedArguments = true; $selfOutType = null; $phpDocComment = null; - - $isPure = null; - if ($this->signatureMapProvider->hasMethodMetadata($declaringClassName, $methodReflection->getName())) { - $isPure = !$this->signatureMapProvider->getMethodMetadata($declaringClassName, $methodReflection->getName())['hasSideEffects']; - } - $methodSignaturesResult = $this->signatureMapProvider->getMethodSignatures($declaringClassName, $methodReflection->getName(), $methodReflection); foreach ($methodSignaturesResult as $signatureType => $methodSignatures) { if ($methodSignatures === null) { @@ -750,22 +744,18 @@ private function createMethod( } } - if ($isPure === null) { - $classResolvedPhpDoc = $declaringClass->getResolvedPhpDoc(); - if ($classResolvedPhpDoc !== null && $classResolvedPhpDoc->areAllMethodsPure()) { - $isPure = true; - } elseif ($classResolvedPhpDoc !== null && $classResolvedPhpDoc->areAllMethodsImpure()) { - $isPure = false; - } + if ($this->signatureMapProvider->hasMethodMetadata($declaringClassName, $methodReflection->getName())) { + $hasSideEffects = TrinaryLogic::createFromBoolean($this->signatureMapProvider->getMethodMetadata($declaringClassName, $methodReflection->getName())['hasSideEffects']); + } else { + $hasSideEffects = TrinaryLogic::createMaybe(); } - return new NativeMethodReflection( $this->reflectionProviderProvider->getReflectionProvider(), $declaringClass, $methodReflection, $variantsByType['positional'], $variantsByType['named'] ?? null, - $isPure !== null ? TrinaryLogic::createFromBoolean(!$isPure) : TrinaryLogic::createMaybe(), + $hasSideEffects, $throwType, $asserts, $acceptsNamedArguments, diff --git a/stubs/Memcached.stub b/stubs/Memcached.stub deleted file mode 100644 index d2e86e22ab6..00000000000 --- a/stubs/Memcached.stub +++ /dev/null @@ -1,16 +0,0 @@ - - */ -class Bug14534Test extends RuleTestCase -{ - - protected function getRule(): Rule - { - return new StrictComparisonOfDifferentTypesRule( - self::getContainer()->getByType(RicherScopeGetTypeHelper::class), - new PossiblyImpureTipHelper(true), - true, - true, - true, - ); - } - - public function testRule(): void - { - $this->analyse([__DIR__ . '/data/bug-14534.php'], []); - } - - public static function getAdditionalConfigFiles(): array - { - return array_merge( - parent::getAdditionalConfigFiles(), - [__DIR__ . '/bug-14534.neon'], - ); - } - -} diff --git a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php index a0851910a60..a40c0703b2c 100644 --- a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php @@ -1192,11 +1192,6 @@ public function testBug14446(): void $this->analyse([__DIR__ . '/../../Analyser/data/bug-14446.php'], []); } - public function testBug13444(): void - { - $this->analyse([__DIR__ . '/data/bug-13444.php'], []); - } - public function testBug14473(): void { $this->analyse([__DIR__ . '/data/bug-14519.php'], []); diff --git a/tests/PHPStan/Rules/Comparison/bug-14534.neon b/tests/PHPStan/Rules/Comparison/bug-14534.neon deleted file mode 100644 index 06adf3f5db4..00000000000 --- a/tests/PHPStan/Rules/Comparison/bug-14534.neon +++ /dev/null @@ -1,3 +0,0 @@ -parameters: - stubFiles: - - data/bug-14534.stub diff --git a/tests/PHPStan/Rules/Comparison/data/bug-13444.php b/tests/PHPStan/Rules/Comparison/data/bug-13444.php deleted file mode 100644 index 487ce9ae19e..00000000000 --- a/tests/PHPStan/Rules/Comparison/data/bug-13444.php +++ /dev/null @@ -1,28 +0,0 @@ -get($key, null, \Memcached::GET_EXTENDED); - - if ($memcached->getResultCode() !== \Memcached::RES_SUCCESS) { - return; - } - - if (!is_array($extendedReturn) || !isset($extendedReturn['value']) || !isset($extendedReturn['cas'])) { - return; - } - - $data = $extendedReturn['value']; - $cas = $extendedReturn['cas']; - \assert(is_float($cas)); - - // Do some work on the data.. - $memcached->cas($cas, $key, $data); - - } while ($memcached->getResultCode() !== \Memcached::RES_SUCCESS); -} diff --git a/tests/PHPStan/Rules/Comparison/data/bug-14534.php b/tests/PHPStan/Rules/Comparison/data/bug-14534.php deleted file mode 100644 index 98094e6d1ea..00000000000 --- a/tests/PHPStan/Rules/Comparison/data/bug-14534.php +++ /dev/null @@ -1,21 +0,0 @@ -key() === 1) { - return $spl->key() === 1; - } - - return false; -} - -function test2(\SplTempFileObject $spl): bool -{ - if ($spl->key() === 1) { - return $spl->key() === 1; - } - - return false; -} diff --git a/tests/PHPStan/Rules/Comparison/data/bug-14534.stub b/tests/PHPStan/Rules/Comparison/data/bug-14534.stub deleted file mode 100644 index d1f3eeb3cc9..00000000000 --- a/tests/PHPStan/Rules/Comparison/data/bug-14534.stub +++ /dev/null @@ -1,6 +0,0 @@ - Date: Mon, 4 May 2026 11:49:09 +0200 Subject: [PATCH 9/9] Revert "Fix phpstan/phpstan#9733: PHPDoc types from overridden function are not included in trait typeinfo when aliased (#5204)" This reverts commit 47e49ed380b5188d9aed3f562a7bd0aad80c2fe7. --- tests/PHPStan/Analyser/nsrt/bug-9733.php | 24 ------------------------ 1 file changed, 24 deletions(-) delete mode 100644 tests/PHPStan/Analyser/nsrt/bug-9733.php diff --git a/tests/PHPStan/Analyser/nsrt/bug-9733.php b/tests/PHPStan/Analyser/nsrt/bug-9733.php deleted file mode 100644 index cc496a71e94..00000000000 --- a/tests/PHPStan/Analyser/nsrt/bug-9733.php +++ /dev/null @@ -1,24 +0,0 @@ -', $array); - } -} - -class Concrete extends Base{ - use MyTrait { - test as test2; - } -}