lowercase function name => stub file relative path */ private array $functionFiles = []; /** @var array, variadicFrom: int|null}> lowercase function name => by-ref info */ private array $byRef = []; /** @var array */ private array $parsedFiles = []; public function __construct() { $this->parser = (new ParserFactory())->createForNewestSupportedVersion(); foreach (PhpStormStubsMap::FUNCTIONS as $name => $file) { $this->functionFiles[strtolower((string) $name)] = $file; } } /** * @return array{fixed: list, variadicFrom: int|null}|null * null when the function is not a known builtin */ public function byRefInfo(string $lowerName): ?array { if (isset($this->byRef[$lowerName])) { return $this->byRef[$lowerName]; } $file = $this->functionFiles[$lowerName] ?? null; if ($file === null) { return null; } $this->parseStubFile($file); // Defined in the map but somehow absent from the parsed file: // treat as unknown rather than silently non-by-ref. return $this->byRef[$lowerName] ?? null; } private function parseStubFile(string $relativePath): void { if (isset($this->parsedFiles[$relativePath])) { return; } $this->parsedFiles[$relativePath] = true; $path = PhpStormStubsMap::DIR . '/' . $relativePath; $code = file_get_contents($path); if ($code === false) { throw new \RuntimeException("cannot read stub file $path"); } $stmts = $this->parser->parse($code); if ($stmts === null) { throw new \RuntimeException("cannot parse stub file $path"); } $byRef = &$this->byRef; $collector = new class($byRef) extends NodeVisitorAbstract { /** @param array, variadicFrom: int|null}> $byRef */ public function __construct(private array &$byRef) { } public function enterNode(Node $node): null { if (!$node instanceof Node\Stmt\Function_) { return null; } $name = strtolower($node->name->toString()); $fixed = []; $variadicFrom = null; foreach ($node->params as $i => $param) { if (!$param->byRef) { continue; } if ($param->variadic) { $variadicFrom = $variadicFrom === null ? $i : min($variadicFrom, $i); } else { $fixed[] = $i; } } // Stub files occasionally declare a function more than once // (per-version signatures); merge conservatively. if (isset($this->byRef[$name])) { $fixed = array_values(array_unique(array_merge($this->byRef[$name]['fixed'], $fixed))); sort($fixed); $prev = $this->byRef[$name]['variadicFrom']; $variadicFrom = $prev === null ? $variadicFrom : ($variadicFrom === null ? $prev : min($prev, $variadicFrom)); } $this->byRef[$name] = ['fixed' => $fixed, 'variadicFrom' => $variadicFrom]; return null; } }; $traverser = new NodeTraverser(); $traverser->addVisitor($collector); $traverser->traverse($stmts); } }