Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/Compiler.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions lib/VM.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
8 changes: 6 additions & 2 deletions patches/overlays/php-cfg/Op/Stmt/Enum_.php
Original file line number Diff line number Diff line change
Expand Up @@ -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'];
}
}
1 change: 1 addition & 0 deletions patches/overlays/php-cfg/enum-parser-methods.php
Original file line number Diff line number Diff line change
Expand Up @@ -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)
);
Expand Down
23 changes: 23 additions & 0 deletions patches/php-cfg-enum-implements.patch
Original file line number Diff line number Diff line change
@@ -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'];
}
}
48 changes: 48 additions & 0 deletions script/apply-patches.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
;;
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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 $?
Expand Down Expand Up @@ -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"
Expand Down
4 changes: 2 additions & 2 deletions script/capability-syntax-lib.php
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
19 changes: 19 additions & 0 deletions test/compliance/cases/language/enum_implements_metadata.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
--TEST--
Language: backed enum implements interface metadata (#2299)
--FILE--
<?php
interface Labeled
{
}

enum Status: string implements Labeled
{
case Active = 'active';
}

echo Status::Active;
echo "\n";
echo enum_exists('Status') ? '1' : '0';
--EXPECT--
active
1