From de25ca570ca441bb8eb4d07976d44de076e32e0d Mon Sep 17 00:00:00 2001 From: PurHur Date: Wed, 27 May 2026 21:41:50 +0000 Subject: [PATCH] Language: record enum implements metadata in CFG and VM (#2299) Parse `enum Foo: T implements Bar` in php-cfg, emit classImplements on TYPE_DECLARE_ENUM, and store interfaces on ClassEntry for self-host subset v1. Co-authored-by: Cursor --- lib/Compiler.php | 1 + lib/VM.php | 1 + patches/overlays/php-cfg/Op/Stmt/Enum_.php | 8 +++- .../overlays/php-cfg/enum-parser-methods.php | 1 + patches/php-cfg-enum-implements.patch | 23 +++++++++ script/apply-patches.sh | 48 +++++++++++++++++++ script/capability-syntax-lib.php | 4 +- .../language/enum_implements_metadata.phpt | 19 ++++++++ 8 files changed, 101 insertions(+), 4 deletions(-) create mode 100644 patches/php-cfg-enum-implements.patch create mode 100644 test/compliance/cases/language/enum_implements_metadata.phpt diff --git a/lib/Compiler.php b/lib/Compiler.php index 57d13a100b5..ef252f06e3b 100755 --- a/lib/Compiler.php +++ b/lib/Compiler.php @@ -922,6 +922,7 @@ protected function compileEnum(Op\Stmt\Enum_ $enum, Block $block): OpCode OpCode::TYPE_DECLARE_ENUM, $this->compileOperand($enum->name, $block, true) ); + $return->classImplements = $this->interfaceNamesFromOperands($enum->implements); $return->block1 = $this->compileEnumBody($enum->stmts); return $return; diff --git a/lib/VM.php b/lib/VM.php index 0260910bab5..2d4522b7a18 100755 --- a/lib/VM.php +++ b/lib/VM.php @@ -748,6 +748,7 @@ private function runFrames(): int } $classEntry = new ClassEntry($name); $classEntry->isEnum = true; + $classEntry->interfaces = $op->classImplements; self::defineClass($classEntry, $op->block1); $this->context->classes[$lcname] = $classEntry; $this->context->enums[$lcname] = true; diff --git a/patches/overlays/php-cfg/Op/Stmt/Enum_.php b/patches/overlays/php-cfg/Op/Stmt/Enum_.php index c939f6fcbf9..6fb6593a32f 100644 --- a/patches/overlays/php-cfg/Op/Stmt/Enum_.php +++ b/patches/overlays/php-cfg/Op/Stmt/Enum_.php @@ -13,14 +13,18 @@ class Enum_ extends ClassLike /** @var Type|null Backing scalar type (string|int) when declared as `enum Foo: string` */ public $backedType = null; - public function __construct(Operand $name, ?Type $backedType, Block $stmts, array $attributes = []) + /** @var Operand[] Implemented interface name operands */ + public $implements = []; + + public function __construct(Operand $name, ?Type $backedType, array $implements, Block $stmts, array $attributes = []) { parent::__construct($name, $stmts, $attributes); $this->backedType = $backedType; + $this->implements = $implements; } public function getVariableNames(): array { - return ['name']; + return ['name', 'implements']; } } diff --git a/patches/overlays/php-cfg/enum-parser-methods.php b/patches/overlays/php-cfg/enum-parser-methods.php index 074fb110fa8..da4e5515922 100644 --- a/patches/overlays/php-cfg/enum-parser-methods.php +++ b/patches/overlays/php-cfg/enum-parser-methods.php @@ -22,6 +22,7 @@ protected function parseStmt_Enum(Stmt\Enum_ $node) $this->block->children[] = new Op\Stmt\Enum_( $name, $backedType, + $this->parseExprList($node->implements), $stmtsBlock, $this->mapAttributes($node) ); diff --git a/patches/php-cfg-enum-implements.patch b/patches/php-cfg-enum-implements.patch new file mode 100644 index 00000000000..0e528e1bc0f --- /dev/null +++ b/patches/php-cfg-enum-implements.patch @@ -0,0 +1,23 @@ +--- vendor/ircmaxell/php-cfg/lib/PHPCfg/Op/Stmt/Enum_.php ++++ vendor/ircmaxell/php-cfg/lib/PHPCfg/Op/Stmt/Enum_.php +@@ -12,15 +12,19 @@ + /** @var Type|null Backing scalar type (string|int) when declared as `enum Foo: string` */ + public $backedType = null; + +- public function __construct(Operand $name, ?Type $backedType, Block $stmts, array $attributes = []) ++ /** @var Operand[] Implemented interface name operands */ ++ public $implements = []; ++ ++ public function __construct(Operand $name, ?Type $backedType, array $implements, Block $stmts, array $attributes = []) + { + parent::__construct($name, $stmts, $attributes); + $this->backedType = $backedType; ++ $this->implements = $implements; + } + + public function getVariableNames(): array + { +- return ['name']; ++ return ['name', 'implements']; + } + } diff --git a/script/apply-patches.sh b/script/apply-patches.sh index 8bda867a71c..1d1423ae974 100755 --- a/script/apply-patches.sh +++ b/script/apply-patches.sh @@ -185,6 +185,9 @@ patch_already_applied() { php-cfg-enum.patch) grep -q 'parseStmt_Enum' "$ROOT/vendor/ircmaxell/php-cfg/lib/PHPCfg/Parser.php" 2>/dev/null ;; + php-cfg-enum-implements.patch) + grep -q 'public $implements' "$ROOT/vendor/ircmaxell/php-cfg/lib/PHPCfg/Op/Stmt/Enum_.php" 2>/dev/null + ;; php-cfg-named-args.patch) grep -q 'callArgName' "$ROOT/vendor/ircmaxell/php-cfg/lib/PHPCfg/Parser.php" 2>/dev/null ;; @@ -337,6 +340,46 @@ PY echo "Applied php-cfg-enum.patch (overlay)" } +apply_php_cfg_enum_implements_overlay() { + local parser="$ROOT/vendor/ircmaxell/php-cfg/lib/PHPCfg/Parser.php" + local op="$ROOT/vendor/ircmaxell/php-cfg/lib/PHPCfg/Op/Stmt/Enum_.php" + local overlay="$PATCH_DIR/overlays/php-cfg" + if grep -q 'public $implements' "$op" 2>/dev/null; then + echo "Skip php-cfg-enum-implements.patch (already applied)" + return 0 + fi + if ! grep -q 'function parseStmt_Enum' "$parser" 2>/dev/null; then + echo "Skip php-cfg-enum-implements.patch (parseStmt_Enum missing; apply php-cfg-enum.patch first)" >&2 + return 1 + fi + cp "$overlay/Op/Stmt/Enum_.php" "$op" + python3 - "$parser" <<'PY' +import re +import sys +from pathlib import Path + +parser_path = Path(sys.argv[1]) +text = parser_path.read_text() +pattern = re.compile( + r"( \$this->block->children\[\] = new Op\\Stmt\\Enum_\(\n" + r" \$name,\n" + r" \$backedType,\n)" + r"( \$stmtsBlock,)", + re.MULTILINE, +) +replacement = r"\1 $this->parseExprList($node->implements),\n\2" +if 'parseExprList($node->implements)' in text: + parser_path.write_text(text) + raise SystemExit(0) +new_text, count = pattern.subn(replacement, text, count=1) +if count != 1: + sys.stderr.write("php-cfg-enum-implements: Enum_ ctor call not found in Parser.php\n") + raise SystemExit(1) +parser_path.write_text(new_text) +PY + echo "Applied php-cfg-enum-implements.patch (overlay)" +} + apply_php_cfg_intersection_type_overlay() { local parser="$ROOT/vendor/ircmaxell/php-cfg/lib/PHPCfg/Parser.php" local printer="$ROOT/vendor/ircmaxell/php-cfg/lib/PHPCfg/Printer.php" @@ -1154,6 +1197,10 @@ apply_patch() { apply_php_cfg_enum_overlay return $? fi + if [[ "$(basename "$patch")" == "php-cfg-enum-implements.patch" ]]; then + apply_php_cfg_enum_implements_overlay + return $? + fi if [[ "$(basename "$patch")" == "php-cfg-intersection-type.patch" ]]; then apply_php_cfg_intersection_type_overlay return $? @@ -1297,6 +1344,7 @@ if [[ -d "$ROOT/vendor/ircmaxell/php-cfg" ]]; then apply_patch "$PATCH_DIR/php-cfg-arrow-function.patch" apply_patch "$PATCH_DIR/php-cfg-anonymous-class.patch" apply_patch "$PATCH_DIR/php-cfg-enum.patch" + apply_patch "$PATCH_DIR/php-cfg-enum-implements.patch" apply_patch "$PATCH_DIR/php-cfg-named-args.patch" apply_patch "$PATCH_DIR/php-cfg-spread.patch" apply_patch "$PATCH_DIR/php-cfg-never-type.patch" diff --git a/script/capability-syntax-lib.php b/script/capability-syntax-lib.php index 625fe108a90..4429cbe30f5 100644 --- a/script/capability-syntax-lib.php +++ b/script/capability-syntax-lib.php @@ -43,8 +43,8 @@ function syntaxRowDefinitions(): array 'construct' => 'Enum declarations `enum Foo: string { case Bar = \'x\'; }`', 'opcodes' => ['TYPE_DECLARE_ENUM', 'TYPE_DECLARE_CLASS_CONST', 'TYPE_CLASS_CONST_FETCH'], 'issue' => 1356, - 'notes' => ['Backed enum cases as class constants; `Foo::Bar` const-like fetch; `enum_exists` registry'], - 'probe' => 'enum Status: string { case Ok = \'ok\'; } echo Status::Ok; echo enum_exists(\'Status\') ? \'1\' : \'0\';', + 'notes' => ['Backed enum cases as class constants; `Foo::Bar` const-like fetch; `enum_exists` registry; `implements` metadata (#2299)'], + 'probe' => 'interface L {} enum Status: string implements L { case Ok = \'ok\'; } echo Status::Ok; echo enum_exists(\'Status\') ? \'1\' : \'0\';', ], [ 'id' => 'instance_methods', diff --git a/test/compliance/cases/language/enum_implements_metadata.phpt b/test/compliance/cases/language/enum_implements_metadata.phpt new file mode 100644 index 00000000000..5c6cf1b0159 --- /dev/null +++ b/test/compliance/cases/language/enum_implements_metadata.phpt @@ -0,0 +1,19 @@ +--TEST-- +Language: backed enum implements interface metadata (#2299) +--FILE-- +