diff --git a/conf/config.neon b/conf/config.neon index ff97f0c932..39e5b8a3d9 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -486,6 +486,7 @@ services: implicitThrows: %exceptions.implicitThrows% treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain% detectDeadTypeInMultiCatch: %featureToggles.detectDeadTypeInMultiCatch% + universalObjectCratesClasses: %universalObjectCratesClasses% - class: PHPStan\Analyser\ConstantResolver @@ -1916,6 +1917,7 @@ services: class: PHPStan\Reflection\BetterReflection\BetterReflectionProvider arguments: reflector: @betterReflectionReflector + universalObjectCratesClasses: %universalObjectCratesClasses% autowired: false - @@ -1931,6 +1933,8 @@ services: - implement: PHPStan\Reflection\BetterReflection\BetterReflectionProviderFactory + arguments: + universalObjectCratesClasses: %universalObjectCratesClasses% - class: PHPStan\Reflection\BetterReflection\SourceStubber\PhpStormStubsSourceStubberFactory diff --git a/conf/config.stubValidator.neon b/conf/config.stubValidator.neon index 6c4c8bde25..1645698a92 100644 --- a/conf/config.stubValidator.neon +++ b/conf/config.stubValidator.neon @@ -20,6 +20,7 @@ services: class: PHPStan\Reflection\BetterReflection\BetterReflectionProvider arguments: reflector: @stubReflector + universalObjectCratesClasses: %universalObjectCratesClasses% autowired: false stubReflector: diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index e2df562722..99c91dd495 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -205,6 +205,7 @@ class NodeScopeResolver /** * @param string[][] $earlyTerminatingMethodCalls className(string) => methods(string[]) * @param array $earlyTerminatingFunctionCalls + * @param string[] $universalObjectCratesClasses */ public function __construct( private readonly ReflectionProvider $reflectionProvider, @@ -226,6 +227,7 @@ public function __construct( private readonly bool $polluteScopeWithAlwaysIterableForeach, private readonly array $earlyTerminatingMethodCalls, private readonly array $earlyTerminatingFunctionCalls, + private readonly array $universalObjectCratesClasses, private readonly bool $implicitThrows, private readonly bool $treatPhpDocTypesAsCertain, private readonly bool $detectDeadTypeInMultiCatch, @@ -1707,6 +1709,7 @@ private function createAstClassReflection(Node\Stmt\ClassLike $stmt, string $cla null, null, null, + $this->universalObjectCratesClasses, sprintf('%s:%d', $scope->getFile(), $stmt->getStartLine()), ); } diff --git a/src/Reflection/BetterReflection/BetterReflectionProvider.php b/src/Reflection/BetterReflection/BetterReflectionProvider.php index 6160c3095d..c23953561b 100644 --- a/src/Reflection/BetterReflection/BetterReflectionProvider.php +++ b/src/Reflection/BetterReflection/BetterReflectionProvider.php @@ -68,6 +68,9 @@ class BetterReflectionProvider implements ReflectionProvider /** @var array */ private array $cachedConstants = []; + /** + * @param string[] $universalObjectCratesClasses + */ public function __construct( private ReflectionProvider\ReflectionProviderProvider $reflectionProviderProvider, private InitializerExprTypeResolver $initializerExprTypeResolver, @@ -84,6 +87,7 @@ public function __construct( private FileHelper $fileHelper, private PhpStormStubsSourceStubber $phpstormStubsSourceStubber, private SignatureMapProvider $signatureMapProvider, + private array $universalObjectCratesClasses, ) { } @@ -144,6 +148,7 @@ public function getClass(string $className): ClassReflection null, null, $this->stubPhpDocProvider->findClassPhpDoc($reflectionClass->getName()), + $this->universalObjectCratesClasses, ); $this->classReflections[$reflectionClassName] = $classReflection; @@ -221,6 +226,7 @@ public function getAnonymousClassReflection(Node\Stmt\Class_ $classNode, Scope $ $scopeFile, null, $this->stubPhpDocProvider->findClassPhpDoc($className), + $this->universalObjectCratesClasses, ); $this->classReflections[$className] = self::$anonymousClasses[$className]; diff --git a/src/Reflection/ClassReflection.php b/src/Reflection/ClassReflection.php index f078b1b2d2..1644acc459 100644 --- a/src/Reflection/ClassReflection.php +++ b/src/Reflection/ClassReflection.php @@ -26,6 +26,7 @@ use PHPStan\PhpDoc\Tag\TypeAliasTag; use PHPStan\Reflection\Php\PhpClassReflectionExtension; use PHPStan\Reflection\Php\PhpPropertyReflection; +use PHPStan\Reflection\Php\UniversalObjectCratesClassReflectionExtension; use PHPStan\Reflection\SignatureMap\SignatureMapProvider; use PHPStan\ShouldNotHappenException; use PHPStan\Type\CircularTypeAliasDefinitionException; @@ -45,7 +46,6 @@ use PHPStan\Type\TypehintHelper; use PHPStan\Type\VerbosityLevel; use ReflectionException; -use stdClass; use function array_diff; use function array_filter; use function array_key_exists; @@ -131,6 +131,7 @@ class ClassReflection * @param PropertiesClassReflectionExtension[] $propertiesClassReflectionExtensions * @param MethodsClassReflectionExtension[] $methodsClassReflectionExtensions * @param AllowedSubTypesClassReflectionExtension[] $allowedSubTypesClassReflectionExtensions + * @param string[] $universalObjectCratesClasses */ public function __construct( private ReflectionProvider $reflectionProvider, @@ -148,6 +149,7 @@ public function __construct( private ?string $anonymousFilename, private ?TemplateTypeMap $resolvedTemplateTypeMap, private ?ResolvedPhpDocBlock $stubPhpDocBlock, + private array $universalObjectCratesClasses, private ?string $extraCacheKey = null, private ?TemplateTypeVarianceMap $resolvedCallSiteVarianceMap = null, ) @@ -389,7 +391,11 @@ public function allowsDynamicProperties(): bool return false; } - if ($this->is(stdClass::class)) { + if (UniversalObjectCratesClassReflectionExtension::isUniversalObjectCrate( + $this->reflectionProvider, + $this->universalObjectCratesClasses, + $this, + )) { return true; } @@ -1412,6 +1418,7 @@ public function withTypes(array $types): self $this->anonymousFilename, $this->typeMapFromList($types), $this->stubPhpDocBlock, + $this->universalObjectCratesClasses, null, $this->resolvedCallSiteVarianceMap, ); @@ -1438,6 +1445,7 @@ public function withVariances(array $variances): self $this->anonymousFilename, $this->resolvedTemplateTypeMap, $this->stubPhpDocBlock, + $this->universalObjectCratesClasses, null, $this->varianceMapFromList($variances), ); diff --git a/src/Testing/RuleTestCase.php b/src/Testing/RuleTestCase.php index 97b06fd99b..3880319684 100644 --- a/src/Testing/RuleTestCase.php +++ b/src/Testing/RuleTestCase.php @@ -99,6 +99,7 @@ private function getAnalyser(): Analyser $this->shouldPolluteScopeWithAlwaysIterableForeach(), [], [], + self::getContainer()->getParameter('universalObjectCratesClasses'), self::getContainer()->getParameter('exceptions')['implicitThrows'], $this->shouldTreatPhpDocTypesAsCertain(), self::getContainer()->getParameter('featureToggles')['detectDeadTypeInMultiCatch'], diff --git a/src/Testing/TypeInferenceTestCase.php b/src/Testing/TypeInferenceTestCase.php index f3698ca115..108c49541c 100644 --- a/src/Testing/TypeInferenceTestCase.php +++ b/src/Testing/TypeInferenceTestCase.php @@ -66,6 +66,7 @@ public static function processFile( true, static::getEarlyTerminatingMethodCalls(), static::getEarlyTerminatingFunctionCalls(), + self::getContainer()->getParameter('universalObjectCratesClasses'), true, self::getContainer()->getParameter('treatPhpDocTypesAsCertain'), self::getContainer()->getParameter('featureToggles')['detectDeadTypeInMultiCatch'], diff --git a/tests/PHPStan/Analyser/AnalyserTest.php b/tests/PHPStan/Analyser/AnalyserTest.php index c830112b81..ef1ee421c3 100644 --- a/tests/PHPStan/Analyser/AnalyserTest.php +++ b/tests/PHPStan/Analyser/AnalyserTest.php @@ -22,6 +22,7 @@ use PHPStan\Rules\Properties\ReadWritePropertiesExtensionProvider; use PHPStan\Testing\PHPStanTestCase; use PHPStan\Type\FileTypeMapper; +use stdClass; use function array_map; use function array_merge; use function assert; @@ -643,6 +644,7 @@ private function createAnalyser(bool $reportUnmatchedIgnoredErrors): Analyser true, [], [], + [stdClass::class], true, $this->shouldTreatPhpDocTypesAsCertain(), self::getContainer()->getParameter('featureToggles')['detectDeadTypeInMultiCatch'],