From 765b7749d31675d3cbf541b146ead1aafd6d6ae9 Mon Sep 17 00:00:00 2001 From: nsfisis Date: Tue, 21 Jul 2026 02:54:18 +0900 Subject: feat(plugin-class-classifier): add deterministic plugin-boundary classifier Decides, for every composer/composer class, how it is treated at the plugin boundary (rust-proxy / rust-snapshot / contract / two-world / php-native / unsupported) so that upstream updates re-classify new or rewritten classes without re-deriving the design by hand. Rules and category definitions live in docs/dev/plugin-class-classification.md; the tool (PHP + nikic/PHP-Parser) implements them as a reachability closure with direction marks, per-method pure/mutator analysis, and a leaf-first fixed point for unreachable classes, with three small versioned exception lists. Co-Authored-By: Claude Fable 5 --- .../src/BuiltinSignatures.php | 126 +++++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 scripts/plugin-class-classifier/src/BuiltinSignatures.php (limited to 'scripts/plugin-class-classifier/src/BuiltinSignatures.php') diff --git a/scripts/plugin-class-classifier/src/BuiltinSignatures.php b/scripts/plugin-class-classifier/src/BuiltinSignatures.php new file mode 100644 index 00000000..af3d77f3 --- /dev/null +++ b/scripts/plugin-class-classifier/src/BuiltinSignatures.php @@ -0,0 +1,126 @@ + 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); + } +} -- cgit v1.3.1