diff --git a/src/Analyser/LazyInternalScopeFactory.php b/src/Analyser/LazyInternalScopeFactory.php index 7b58363387..5e2a5919bf 100644 --- a/src/Analyser/LazyInternalScopeFactory.php +++ b/src/Analyser/LazyInternalScopeFactory.php @@ -17,6 +17,7 @@ use PHPStan\Rules\Properties\PropertyReflectionFinder; use PHPStan\Type\ClosureType; use PHPStan\Type\ExpressionTypeResolverExtension; +use WeakReference; #[GenerateFactory(interface: InternalScopeFactoryFactory::class, resultType: LazyInternalScopeFactory::class)] final class LazyInternalScopeFactory implements InternalScopeFactory @@ -46,6 +47,11 @@ final class LazyInternalScopeFactory implements InternalScopeFactory private ?AttributeReflectionFactory $attributeReflectionFactory = null; + private ?self $twin = null; + + /** @var WeakReference|null */ + private ?WeakReference $origin = null; + /** * @param callable(Node $node, Scope $scope): void|null $nodeCallback */ @@ -131,12 +137,43 @@ public function create( public function toFiberFactory(): InternalScopeFactory { - return new self($this->container, $this->nodeCallback, true); + return $this->fiber ? $this : $this->twin(); } public function toMutatingFactory(): InternalScopeFactory { - return new self($this->container, $this->nodeCallback, false); + return $this->fiber ? $this->twin() : $this; + } + + /** + * The factory for the other scope flavour, created once. Scopes switch + * flavour constantly, and a fresh factory would start with empty memos — + * resolving every service above out of the container again on its first + * create(). + * + * The pair is held one way strongly and the other way weakly: a factory + * belongs to a single analysed file (ScopeFactory::create() makes one per + * file, closing over that file's node callback), and a strong cycle here + * would keep every file's callback alive for the whole run — PHPStan runs + * with the cycle collector disabled, so nothing would ever free it. + */ + private function twin(): self + { + if ($this->twin !== null) { + return $this->twin; + } + + if ($this->origin !== null) { + $origin = $this->origin->get(); + if ($origin !== null) { + return $origin; + } + } + + $this->twin = new self($this->container, $this->nodeCallback, !$this->fiber); + $this->twin->origin = WeakReference::create($this); + + return $this->twin; } }