From 8fe91e39e1477932480fd029b9d97ec4731e4006 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 9 Jan 2026 08:12:50 +0100 Subject: [PATCH 1/6] Find all constant types at once --- .../SignatureMap/Php8SignatureMapProvider.php | 56 +++++++++++-------- 1 file changed, 32 insertions(+), 24 deletions(-) diff --git a/src/Reflection/SignatureMap/Php8SignatureMapProvider.php b/src/Reflection/SignatureMap/Php8SignatureMapProvider.php index 61b1553782..1c9c9401c1 100644 --- a/src/Reflection/SignatureMap/Php8SignatureMapProvider.php +++ b/src/Reflection/SignatureMap/Php8SignatureMapProvider.php @@ -44,9 +44,12 @@ final class Php8SignatureMapProvider implements SignatureMapProvider /** @var array> */ private array $methodNodes = []; - /** @var array> */ + /** @var array> */ private array $constantTypes = []; + /** @var array> */ + private array $stubbedConstantTypes = []; + private Php8StubsMap $map; public function __construct( @@ -485,40 +488,45 @@ private function findConstantType(string $className, string $constantName): ?Typ return $this->constantTypes[$lowerClassName][$lowerConstantName]; } - $stubFile = self::DIRECTORY . '/' . $this->map->classes[$lowerClassName]; - $nodes = $this->fileNodesFetcher->fetchNodes($stubFile); - $classes = $nodes->getClassNodes(); - if (count($classes) !== 1) { - throw new ShouldNotHappenException(sprintf('Class %s stub not found in %s.', $className, $stubFile)); - } - - $class = $classes[$lowerClassName]; - if (count($class) !== 1) { - throw new ShouldNotHappenException(sprintf('Class %s stub not found in %s.', $className, $stubFile)); - } + // read/parse each stubFile only once + if (!array_key_exists($lowerClassName, $this->stubbedConstantTypes)) { + $stubFile = self::DIRECTORY . '/' . $this->map->classes[$lowerClassName]; + $nodes = $this->fileNodesFetcher->fetchNodes($stubFile); + $classes = $nodes->getClassNodes(); + if (count($classes) !== 1) { + throw new ShouldNotHappenException(sprintf('Class %s stub not found in %s.', $className, $stubFile)); + } - foreach ($class[0]->getNode()->stmts as $stmt) { - if (!$stmt instanceof ClassConst) { - continue; + $class = $classes[$lowerClassName]; + if (count($class) !== 1) { + throw new ShouldNotHappenException(sprintf('Class %s stub not found in %s.', $className, $stubFile)); } - foreach ($stmt->consts as $const) { - if ($const->name->toString() !== $constantName) { + // find and remember all constants within the stubFile + foreach ($class[0]->getNode()->stmts as $stmt) { + if (!$stmt instanceof ClassConst) { continue; } - if (!$this->isForCurrentVersion($stmt->attrGroups)) { - continue; - } + foreach ($stmt->consts as $const) { + if ($stmt->type === null) { + continue; + } - if ($stmt->type === null) { - return null; - } + if (!$this->isForCurrentVersion($stmt->attrGroups)) { + continue; + } - return $this->constantTypes[$lowerClassName][$lowerConstantName] = ParserNodeTypeToPHPStanType::resolve($stmt->type, null); + $this->stubbedConstantTypes[$lowerClassName][$const->name->toLowerString()] = ParserNodeTypeToPHPStanType::resolve($stmt->type, null); + } } } + // mark only the requested constant as being found + if (isset($this->stubbedConstantTypes[$lowerClassName][$lowerConstantName])) { + return $this->constantTypes[$lowerClassName][$lowerConstantName] = $this->stubbedConstantTypes[$lowerClassName][$lowerConstantName]; + } + return null; } From b74edcc8a618a42f00c874da58c84dabe601555d Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 9 Jan 2026 08:17:13 +0100 Subject: [PATCH 2/6] Find all stubbed method types at once --- .../SignatureMap/Php8SignatureMapProvider.php | 48 ++++++++++++------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/src/Reflection/SignatureMap/Php8SignatureMapProvider.php b/src/Reflection/SignatureMap/Php8SignatureMapProvider.php index 1c9c9401c1..8ec4ebd646 100644 --- a/src/Reflection/SignatureMap/Php8SignatureMapProvider.php +++ b/src/Reflection/SignatureMap/Php8SignatureMapProvider.php @@ -41,9 +41,12 @@ final class Php8SignatureMapProvider implements SignatureMapProvider private const DIRECTORY = __DIR__ . '/../../../vendor/phpstan/php-8-stubs'; - /** @var array> */ + /** @var array> */ private array $methodNodes = []; + /** @var array> */ + private array $stubbedMethodNodes = []; + /** @var array> */ private array $constantTypes = []; @@ -89,31 +92,42 @@ private function findMethodNode(string $className, string $methodName): ?array return $this->methodNodes[$lowerClassName][$lowerMethodName]; } - $stubFile = self::DIRECTORY . '/' . $this->map->classes[$lowerClassName]; - $nodes = $this->fileNodesFetcher->fetchNodes($stubFile); - $classes = $nodes->getClassNodes(); - if (count($classes) !== 1) { - throw new ShouldNotHappenException(sprintf('Class %s stub not found in %s.', $className, $stubFile)); - } - - $class = $classes[$lowerClassName]; - if (count($class) !== 1) { - throw new ShouldNotHappenException(sprintf('Class %s stub not found in %s.', $className, $stubFile)); - } + if (!array_key_exists($lowerClassName, $this->stubbedConstantTypes)) { + $stubFile = self::DIRECTORY . '/' . $this->map->classes[$lowerClassName]; + $nodes = $this->fileNodesFetcher->fetchNodes($stubFile); + $classes = $nodes->getClassNodes(); + if (count($classes) !== 1) { + throw new ShouldNotHappenException(sprintf('Class %s stub not found in %s.', $className, $stubFile)); + } - foreach ($class[0]->getNode()->stmts as $stmt) { - if (!$stmt instanceof ClassMethod) { - continue; + $class = $classes[$lowerClassName]; + if (count($class) !== 1) { + throw new ShouldNotHappenException(sprintf('Class %s stub not found in %s.', $className, $stubFile)); } - if ($stmt->name->toLowerString() === $lowerMethodName) { + // find and remember all methods within the stubFile + foreach ($class[0]->getNode()->stmts as $stmt) { + if (!$stmt instanceof ClassMethod) { + continue; + } + + if ($stmt->name->toLowerString() !== $lowerMethodName) { + continue; + } + if (!$this->isForCurrentVersion($stmt->attrGroups)) { continue; } - return $this->methodNodes[$lowerClassName][$lowerMethodName] = [$stmt, $stubFile]; + + $this->stubbedMethodNodes[$lowerClassName][$lowerMethodName] = [$stmt, $stubFile]; } } + // mark only the requested method as being found + if (isset($this->stubbedMethodNodes[$lowerClassName][$lowerMethodName])) { + return $this->methodNodes[$lowerClassName][$lowerMethodName] = $this->stubbedMethodNodes[$lowerClassName][$lowerMethodName]; + } + return null; } From 3aa0985bc342a9b4351bdb302f47c54b5613efde Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 9 Jan 2026 08:23:52 +0100 Subject: [PATCH 3/6] parse/read each class-stub file only once --- .../SignatureMap/Php8SignatureMapProvider.php | 104 ++++++++---------- 1 file changed, 46 insertions(+), 58 deletions(-) diff --git a/src/Reflection/SignatureMap/Php8SignatureMapProvider.php b/src/Reflection/SignatureMap/Php8SignatureMapProvider.php index 8ec4ebd646..1b87320e98 100644 --- a/src/Reflection/SignatureMap/Php8SignatureMapProvider.php +++ b/src/Reflection/SignatureMap/Php8SignatureMapProvider.php @@ -92,26 +92,55 @@ private function findMethodNode(string $className, string $methodName): ?array return $this->methodNodes[$lowerClassName][$lowerMethodName]; } - if (!array_key_exists($lowerClassName, $this->stubbedConstantTypes)) { - $stubFile = self::DIRECTORY . '/' . $this->map->classes[$lowerClassName]; - $nodes = $this->fileNodesFetcher->fetchNodes($stubFile); - $classes = $nodes->getClassNodes(); - if (count($classes) !== 1) { - throw new ShouldNotHappenException(sprintf('Class %s stub not found in %s.', $className, $stubFile)); - } + $this->findClassStubs($className); + if (isset($this->stubbedMethodNodes[$lowerClassName][$lowerMethodName])) { + return $this->methodNodes[$lowerClassName][$lowerMethodName] = $this->stubbedMethodNodes[$lowerClassName][$lowerMethodName]; + } - $class = $classes[$lowerClassName]; - if (count($class) !== 1) { - throw new ShouldNotHappenException(sprintf('Class %s stub not found in %s.', $className, $stubFile)); - } + return null; + } + + private function findClassStubs(string $className): void + { + $lowerClassName = strtolower($className); + + if ( + array_key_exists($lowerClassName, $this->stubbedMethodNodes) + || array_key_exists($lowerClassName, $this->stubbedConstantTypes) + ) { + return; + } - // find and remember all methods within the stubFile - foreach ($class[0]->getNode()->stmts as $stmt) { - if (!$stmt instanceof ClassMethod) { + $stubFile = self::DIRECTORY . '/' . $this->map->classes[$lowerClassName]; + $nodes = $this->fileNodesFetcher->fetchNodes($stubFile); + $classes = $nodes->getClassNodes(); + if (count($classes) !== 1) { + throw new ShouldNotHappenException(sprintf('Class %s stub not found in %s.', $className, $stubFile)); + } + + $class = $classes[$lowerClassName]; + if (count($class) !== 1) { + throw new ShouldNotHappenException(sprintf('Class %s stub not found in %s.', $className, $stubFile)); + } + + // find and remember all methods/constants within the stubFile + foreach ($class[0]->getNode()->stmts as $stmt) { + if ($stmt instanceof ClassMethod) { + if (!$this->isForCurrentVersion($stmt->attrGroups)) { continue; } - if ($stmt->name->toLowerString() !== $lowerMethodName) { + $this->stubbedMethodNodes[$lowerClassName][$stmt->name->toLowerString()] = [$stmt, $stubFile]; + + continue; + } + + if (!$stmt instanceof ClassConst) { + continue; + } + + foreach ($stmt->consts as $const) { + if ($stmt->type === null) { continue; } @@ -119,16 +148,9 @@ private function findMethodNode(string $className, string $methodName): ?array continue; } - $this->stubbedMethodNodes[$lowerClassName][$lowerMethodName] = [$stmt, $stubFile]; + $this->stubbedConstantTypes[$lowerClassName][$const->name->toLowerString()] = ParserNodeTypeToPHPStanType::resolve($stmt->type, null); } } - - // mark only the requested method as being found - if (isset($this->stubbedMethodNodes[$lowerClassName][$lowerMethodName])) { - return $this->methodNodes[$lowerClassName][$lowerMethodName] = $this->stubbedMethodNodes[$lowerClassName][$lowerMethodName]; - } - - return null; } /** @@ -502,41 +524,7 @@ private function findConstantType(string $className, string $constantName): ?Typ return $this->constantTypes[$lowerClassName][$lowerConstantName]; } - // read/parse each stubFile only once - if (!array_key_exists($lowerClassName, $this->stubbedConstantTypes)) { - $stubFile = self::DIRECTORY . '/' . $this->map->classes[$lowerClassName]; - $nodes = $this->fileNodesFetcher->fetchNodes($stubFile); - $classes = $nodes->getClassNodes(); - if (count($classes) !== 1) { - throw new ShouldNotHappenException(sprintf('Class %s stub not found in %s.', $className, $stubFile)); - } - - $class = $classes[$lowerClassName]; - if (count($class) !== 1) { - throw new ShouldNotHappenException(sprintf('Class %s stub not found in %s.', $className, $stubFile)); - } - - // find and remember all constants within the stubFile - foreach ($class[0]->getNode()->stmts as $stmt) { - if (!$stmt instanceof ClassConst) { - continue; - } - - foreach ($stmt->consts as $const) { - if ($stmt->type === null) { - continue; - } - - if (!$this->isForCurrentVersion($stmt->attrGroups)) { - continue; - } - - $this->stubbedConstantTypes[$lowerClassName][$const->name->toLowerString()] = ParserNodeTypeToPHPStanType::resolve($stmt->type, null); - } - } - } - - // mark only the requested constant as being found + $this->findClassStubs($className); if (isset($this->stubbedConstantTypes[$lowerClassName][$lowerConstantName])) { return $this->constantTypes[$lowerClassName][$lowerConstantName] = $this->stubbedConstantTypes[$lowerClassName][$lowerConstantName]; } From 2f3b473227ff1ced263133f8145d52b8b03137fc Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 9 Jan 2026 08:43:09 +0100 Subject: [PATCH 4/6] make sure each file is read only once, even if it only contained symbols not applicable for the current php version --- src/Reflection/SignatureMap/Php8SignatureMapProvider.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Reflection/SignatureMap/Php8SignatureMapProvider.php b/src/Reflection/SignatureMap/Php8SignatureMapProvider.php index 1b87320e98..f6291ac44c 100644 --- a/src/Reflection/SignatureMap/Php8SignatureMapProvider.php +++ b/src/Reflection/SignatureMap/Php8SignatureMapProvider.php @@ -123,6 +123,9 @@ private function findClassStubs(string $className): void throw new ShouldNotHappenException(sprintf('Class %s stub not found in %s.', $className, $stubFile)); } + $this->stubbedMethodNodes[$lowerClassName] = []; + $this->stubbedConstantTypes[$lowerClassName] = []; + // find and remember all methods/constants within the stubFile foreach ($class[0]->getNode()->stmts as $stmt) { if ($stmt instanceof ClassMethod) { From af8239cb86a2cf7824073e8685c90f37a0a0bc49 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 9 Jan 2026 11:00:25 +0100 Subject: [PATCH 5/6] simplify --- .../SignatureMap/Php8SignatureMapProvider.php | 32 ++++++------------- 1 file changed, 10 insertions(+), 22 deletions(-) diff --git a/src/Reflection/SignatureMap/Php8SignatureMapProvider.php b/src/Reflection/SignatureMap/Php8SignatureMapProvider.php index f6291ac44c..74281d16d9 100644 --- a/src/Reflection/SignatureMap/Php8SignatureMapProvider.php +++ b/src/Reflection/SignatureMap/Php8SignatureMapProvider.php @@ -44,15 +44,9 @@ final class Php8SignatureMapProvider implements SignatureMapProvider /** @var array> */ private array $methodNodes = []; - /** @var array> */ - private array $stubbedMethodNodes = []; - /** @var array> */ private array $constantTypes = []; - /** @var array> */ - private array $stubbedConstantTypes = []; - private Php8StubsMap $map; public function __construct( @@ -88,13 +82,10 @@ private function findMethodNode(string $className, string $methodName): ?array { $lowerClassName = strtolower($className); $lowerMethodName = strtolower($methodName); - if (isset($this->methodNodes[$lowerClassName][$lowerMethodName])) { - return $this->methodNodes[$lowerClassName][$lowerMethodName]; - } $this->findClassStubs($className); - if (isset($this->stubbedMethodNodes[$lowerClassName][$lowerMethodName])) { - return $this->methodNodes[$lowerClassName][$lowerMethodName] = $this->stubbedMethodNodes[$lowerClassName][$lowerMethodName]; + if (isset($this->methodNodes[$lowerClassName][$lowerMethodName])) { + return $this->methodNodes[$lowerClassName][$lowerMethodName]; } return null; @@ -105,8 +96,8 @@ private function findClassStubs(string $className): void $lowerClassName = strtolower($className); if ( - array_key_exists($lowerClassName, $this->stubbedMethodNodes) - || array_key_exists($lowerClassName, $this->stubbedConstantTypes) + isset($this->methodNodes[$lowerClassName]) + || isset($this->constantTypes[$lowerClassName]) ) { return; } @@ -123,8 +114,8 @@ private function findClassStubs(string $className): void throw new ShouldNotHappenException(sprintf('Class %s stub not found in %s.', $className, $stubFile)); } - $this->stubbedMethodNodes[$lowerClassName] = []; - $this->stubbedConstantTypes[$lowerClassName] = []; + $this->methodNodes[$lowerClassName] = []; + $this->constantTypes[$lowerClassName] = []; // find and remember all methods/constants within the stubFile foreach ($class[0]->getNode()->stmts as $stmt) { @@ -133,7 +124,7 @@ private function findClassStubs(string $className): void continue; } - $this->stubbedMethodNodes[$lowerClassName][$stmt->name->toLowerString()] = [$stmt, $stubFile]; + $this->methodNodes[$lowerClassName][$stmt->name->toLowerString()] = [$stmt, $stubFile]; continue; } @@ -151,7 +142,7 @@ private function findClassStubs(string $className): void continue; } - $this->stubbedConstantTypes[$lowerClassName][$const->name->toLowerString()] = ParserNodeTypeToPHPStanType::resolve($stmt->type, null); + $this->constantTypes[$lowerClassName][$const->name->toLowerString()] = ParserNodeTypeToPHPStanType::resolve($stmt->type, null); } } } @@ -523,13 +514,10 @@ private function findConstantType(string $className, string $constantName): ?Typ { $lowerClassName = strtolower($className); $lowerConstantName = strtolower($constantName); - if (isset($this->constantTypes[$lowerClassName][$lowerConstantName])) { - return $this->constantTypes[$lowerClassName][$lowerConstantName]; - } $this->findClassStubs($className); - if (isset($this->stubbedConstantTypes[$lowerClassName][$lowerConstantName])) { - return $this->constantTypes[$lowerClassName][$lowerConstantName] = $this->stubbedConstantTypes[$lowerClassName][$lowerConstantName]; + if (isset($this->constantTypes[$lowerClassName][$lowerConstantName])) { + return $this->constantTypes[$lowerClassName][$lowerConstantName]; } return null; From eeecc6f143efbd773fdfdef49c657ba1a2bff924 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 9 Jan 2026 11:07:51 +0100 Subject: [PATCH 6/6] more precise types --- .../SourceLocator/CachingVisitor.php | 12 ++++++------ .../SourceLocator/FetchedNodesResult.php | 12 ++++++------ .../SignatureMap/Php8SignatureMapProvider.php | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Reflection/BetterReflection/SourceLocator/CachingVisitor.php b/src/Reflection/BetterReflection/SourceLocator/CachingVisitor.php index 11abc81813..27bfb04022 100644 --- a/src/Reflection/BetterReflection/SourceLocator/CachingVisitor.php +++ b/src/Reflection/BetterReflection/SourceLocator/CachingVisitor.php @@ -20,13 +20,13 @@ final class CachingVisitor extends NodeVisitorAbstract private string $contents; - /** @var array>> */ + /** @var array>> */ private array $classNodes; - /** @var array>> */ + /** @var array>> */ private array $functionNodes; - /** @var array>> */ + /** @var array>> */ private array $constantNodes; private ?Node\Stmt\Namespace_ $currentNamespaceNode = null; @@ -124,7 +124,7 @@ public function leaveNode(Node $node) } /** - * @return array>> + * @return array>> */ public function getClassNodes(): array { @@ -132,7 +132,7 @@ public function getClassNodes(): array } /** - * @return array>> + * @return array>> */ public function getFunctionNodes(): array { @@ -140,7 +140,7 @@ public function getFunctionNodes(): array } /** - * @return array>> + * @return array>> */ public function getConstantNodes(): array { diff --git a/src/Reflection/BetterReflection/SourceLocator/FetchedNodesResult.php b/src/Reflection/BetterReflection/SourceLocator/FetchedNodesResult.php index ac90178d49..9061e3845a 100644 --- a/src/Reflection/BetterReflection/SourceLocator/FetchedNodesResult.php +++ b/src/Reflection/BetterReflection/SourceLocator/FetchedNodesResult.php @@ -8,9 +8,9 @@ final class FetchedNodesResult { /** - * @param array>> $classNodes - * @param array>> $functionNodes - * @param array>> $constantNodes + * @param array>> $classNodes + * @param array>> $functionNodes + * @param array>> $constantNodes */ public function __construct( private array $classNodes, @@ -21,7 +21,7 @@ public function __construct( } /** - * @return array>> + * @return array>> */ public function getClassNodes(): array { @@ -29,7 +29,7 @@ public function getClassNodes(): array } /** - * @return array>> + * @return array>> */ public function getFunctionNodes(): array { @@ -37,7 +37,7 @@ public function getFunctionNodes(): array } /** - * @return array>> + * @return array>> */ public function getConstantNodes(): array { diff --git a/src/Reflection/SignatureMap/Php8SignatureMapProvider.php b/src/Reflection/SignatureMap/Php8SignatureMapProvider.php index 74281d16d9..19884d7037 100644 --- a/src/Reflection/SignatureMap/Php8SignatureMapProvider.php +++ b/src/Reflection/SignatureMap/Php8SignatureMapProvider.php @@ -97,7 +97,7 @@ private function findClassStubs(string $className): void if ( isset($this->methodNodes[$lowerClassName]) - || isset($this->constantTypes[$lowerClassName]) + || isset($this->constantTypes[$lowerClassName]) ) { return; }