summaryrefslogtreecommitdiff
path: root/Zend
diff options
context:
space:
mode:
authorIlija Tovilo <ilija.tovilo@me.com>2020-06-10 23:10:18 +0200
committerIlija Tovilo <ilija.tovilo@me.com>2021-03-17 19:08:03 +0100
commit269c8dac1d56ee85d71ae94d9b28dd7d8e8de7b7 (patch)
tree810ac41b2157ff4e8063f9696f97e1a9d77837c4 /Zend
parenta6fc427b8c51015c16541c112a26dd06bd75e99e (diff)
downloadphp-git-269c8dac1d56ee85d71ae94d9b28dd7d8e8de7b7.tar.gz
Implement enums
RFC: https://wiki.php.net/rfc/enumerations Co-authored-by: Nikita Popov <nikita.ppv@gmail.com> Closes GH-6489.
Diffstat (limited to 'Zend')
-rw-r--r--Zend/Optimizer/zend_inference.c2
-rw-r--r--Zend/tests/enum/__call.phpt29
-rw-r--r--Zend/tests/enum/__callStatic.phpt27
-rw-r--r--Zend/tests/enum/__class__.phpt19
-rw-r--r--Zend/tests/enum/__function__.phpt19
-rw-r--r--Zend/tests/enum/__get.phpt17
-rw-r--r--Zend/tests/enum/__invoke.phpt24
-rw-r--r--Zend/tests/enum/__isset.phpt16
-rw-r--r--Zend/tests/enum/__method__.phpt19
-rw-r--r--Zend/tests/enum/ast-dumper.phpt48
-rw-r--r--Zend/tests/enum/backed-cases-int.phpt26
-rw-r--r--Zend/tests/enum/backed-cases-string.phpt26
-rw-r--r--Zend/tests/enum/backed-duplicate-int.phpt13
-rw-r--r--Zend/tests/enum/backed-duplicate-string.phpt15
-rw-r--r--Zend/tests/enum/backed-from-invalid-int.phpt19
-rw-r--r--Zend/tests/enum/backed-from-invalid-string.phpt21
-rw-r--r--Zend/tests/enum/backed-from-invalid-type.phpt34
-rw-r--r--Zend/tests/enum/backed-from-unknown-hash.phpt17
-rw-r--r--Zend/tests/enum/backed-from.phpt36
-rw-r--r--Zend/tests/enum/backed-int-case-without-value.phpt14
-rw-r--r--Zend/tests/enum/backed-int-const-expr.phpt20
-rw-r--r--Zend/tests/enum/backed-int-const-invalid-expr.phpt14
-rw-r--r--Zend/tests/enum/backed-int.phpt17
-rw-r--r--Zend/tests/enum/backed-invalid.phpt10
-rw-r--r--Zend/tests/enum/backed-mismatch.phpt12
-rw-r--r--Zend/tests/enum/backed-negative-int.phpt21
-rw-r--r--Zend/tests/enum/backed-string-heredoc.phpt30
-rw-r--r--Zend/tests/enum/backed-string.phpt17
-rw-r--r--Zend/tests/enum/backed-tryFrom-casing.phpt15
-rw-r--r--Zend/tests/enum/backed-tryFrom-unknown-hash.phpt17
-rw-r--r--Zend/tests/enum/backed-tryFrom.phpt40
-rw-r--r--Zend/tests/enum/backed-type-no-union.phpt10
-rw-r--r--Zend/tests/enum/basic-methods.phpt21
-rw-r--r--Zend/tests/enum/case-attributes.phpt25
-rw-r--r--Zend/tests/enum/case-in-class.phpt12
-rw-r--r--Zend/tests/enum/cases-refcount.phpt22
-rw-r--r--Zend/tests/enum/comparison.phpt57
-rw-r--r--Zend/tests/enum/constant-aliases.phpt21
-rw-r--r--Zend/tests/enum/constants.phpt14
-rw-r--r--Zend/tests/enum/default-parameter.phpt18
-rw-r--r--Zend/tests/enum/enum-as-constant.phpt22
-rw-r--r--Zend/tests/enum/enum-as-params.phpt35
-rw-r--r--Zend/tests/enum/enum-attributes.phpt23
-rw-r--r--Zend/tests/enum/enum-in-constant.phpt22
-rw-r--r--Zend/tests/enum/enum-in-static-var.phpt21
-rw-r--r--Zend/tests/enum/enum-reserved-non-modifiers.phpt32
-rw-r--r--Zend/tests/enum/enum_exists.phpt41
-rw-r--r--Zend/tests/enum/final.phpt12
-rw-r--r--Zend/tests/enum/implements.phpt34
-rw-r--r--Zend/tests/enum/instanceof-backed-enum.phpt20
-rw-r--r--Zend/tests/enum/instanceof-unitenum.phpt18
-rw-r--r--Zend/tests/enum/instanceof.phpt25
-rw-r--r--Zend/tests/enum/json_encode.phpt59
-rw-r--r--Zend/tests/enum/keyword-no-bc-break.phpt42
-rw-r--r--Zend/tests/enum/keyword-whitespace.phpt16
-rw-r--r--Zend/tests/enum/name-property.phpt50
-rw-r--r--Zend/tests/enum/namespaces.phpt32
-rw-r--r--Zend/tests/enum/no-cases.phpt16
-rw-r--r--Zend/tests/enum/no-class-implements-backed-enum.phpt10
-rw-r--r--Zend/tests/enum/no-class-implements-unit-enum.phpt10
-rw-r--r--Zend/tests/enum/no-clone.phpt18
-rw-r--r--Zend/tests/enum/no-constructors.phpt12
-rw-r--r--Zend/tests/enum/no-destruct.phpt12
-rw-r--r--Zend/tests/enum/no-dynamic-properties.phpt20
-rw-r--r--Zend/tests/enum/no-enum-implements-backed-enum.phpt10
-rw-r--r--Zend/tests/enum/no-enum-implements-unit-enum.phpt10
-rw-r--r--Zend/tests/enum/no-from.phpt16
-rw-r--r--Zend/tests/enum/no-implement-serializable-indirect.phpt24
-rw-r--r--Zend/tests/enum/no-implement-serializable.phpt22
-rw-r--r--Zend/tests/enum/no-name-property.phpt12
-rw-r--r--Zend/tests/enum/no-new-through-reflection.phpt16
-rw-r--r--Zend/tests/enum/no-new.phpt16
-rw-r--r--Zend/tests/enum/no-non-backed-enum-implements-backed-enum.phpt10
-rw-r--r--Zend/tests/enum/no-pass-properties-by-ref.phpt26
-rw-r--r--Zend/tests/enum/no-properties.phpt12
-rw-r--r--Zend/tests/enum/no-return-properties-by-ref.phpt27
-rw-r--r--Zend/tests/enum/no-static-properties.phpt12
-rw-r--r--Zend/tests/enum/no-unsed-value.phpt14
-rw-r--r--Zend/tests/enum/no-unset-propertes.phpt37
-rw-r--r--Zend/tests/enum/no-value-property.phpt12
-rw-r--r--Zend/tests/enum/no-write-properties-through-foreach-reference.phpt22
-rw-r--r--Zend/tests/enum/no-write-properties-through-references.phpt23
-rw-r--r--Zend/tests/enum/no-write-properties.phpt37
-rw-r--r--Zend/tests/enum/non-backed-enum-with-expr-value.phpt12
-rw-r--r--Zend/tests/enum/non-backed-enum-with-int-value.phpt12
-rw-r--r--Zend/tests/enum/non-backed-enum-with-invalid-value.phpt12
-rw-r--r--Zend/tests/enum/non-backed-enum-with-string-value.phpt12
-rw-r--r--Zend/tests/enum/offsetGet-in-const-expr.phpt29
-rw-r--r--Zend/tests/enum/print_r.phpt37
-rw-r--r--Zend/tests/enum/reflectionclass.phpt20
-rw-r--r--Zend/tests/enum/serialization-round-trip.phpt14
-rw-r--r--Zend/tests/enum/serialize.phpt14
-rw-r--r--Zend/tests/enum/spl-object-storage.phpt37
-rw-r--r--Zend/tests/enum/static-methods.phpt28
-rw-r--r--Zend/tests/enum/traits-no-__construct.phpt21
-rw-r--r--Zend/tests/enum/traits-no-cases-method.phpt34
-rw-r--r--Zend/tests/enum/traits-no-forbidden-methods.phpt21
-rw-r--r--Zend/tests/enum/traits-no-properties.phpt25
-rw-r--r--Zend/tests/enum/traits.phpt31
-rw-r--r--Zend/tests/enum/unit-cases.phpt28
-rw-r--r--Zend/tests/enum/unserialize-const.phpt18
-rw-r--r--Zend/tests/enum/unserialize-missing-colon.phpt17
-rw-r--r--Zend/tests/enum/unserialize-non-enum.phpt15
-rw-r--r--Zend/tests/enum/unserialize-non-existent-case.phpt17
-rw-r--r--Zend/tests/enum/unserialize-refcount.phpt45
-rw-r--r--Zend/tests/enum/unserialize.phpt24
-rw-r--r--Zend/tests/enum/value-property-type.phpt20
-rw-r--r--Zend/tests/enum/var_dump-nested.phpt20
-rw-r--r--Zend/tests/enum/var_dump-reference.phpt22
-rw-r--r--Zend/tests/enum/var_export.phpt14
-rw-r--r--Zend/zend.c15
-rw-r--r--Zend/zend.h3
-rw-r--r--Zend/zend_API.c4
-rw-r--r--Zend/zend_ast.c65
-rw-r--r--Zend/zend_ast.h4
-rw-r--r--Zend/zend_builtin_functions.c5
-rw-r--r--Zend/zend_builtin_functions.stub.php2
-rw-r--r--Zend/zend_builtin_functions_arginfo.h9
-rw-r--r--Zend/zend_compile.c140
-rw-r--r--Zend/zend_compile.h10
-rw-r--r--Zend/zend_default_classes.c2
-rw-r--r--Zend/zend_enum.c373
-rw-r--r--Zend/zend_enum.h52
-rw-r--r--Zend/zend_enum.stub.php15
-rw-r--r--Zend/zend_enum_arginfo.h49
-rw-r--r--Zend/zend_inheritance.c16
-rw-r--r--Zend/zend_language_parser.y27
-rw-r--r--Zend/zend_language_scanner.l13
-rw-r--r--Zend/zend_objects_API.c5
-rw-r--r--Zend/zend_opcode.c3
-rw-r--r--Zend/zend_string.h4
131 files changed, 3211 insertions, 18 deletions
diff --git a/Zend/Optimizer/zend_inference.c b/Zend/Optimizer/zend_inference.c
index e600139229..42f37fa0c9 100644
--- a/Zend/Optimizer/zend_inference.c
+++ b/Zend/Optimizer/zend_inference.c
@@ -3485,7 +3485,7 @@ static zend_always_inline int _zend_update_type_info(
break;
case ZEND_FETCH_CONSTANT:
case ZEND_FETCH_CLASS_CONSTANT:
- UPDATE_SSA_TYPE(MAY_BE_RC1|MAY_BE_RCN|MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG|MAY_BE_DOUBLE|MAY_BE_STRING|MAY_BE_RESOURCE|MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY, ssa_op->result_def);
+ UPDATE_SSA_TYPE(MAY_BE_RC1|MAY_BE_RCN|MAY_BE_ANY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY, ssa_op->result_def);
break;
case ZEND_STRLEN:
tmp = MAY_BE_LONG;
diff --git a/Zend/tests/enum/__call.phpt b/Zend/tests/enum/__call.phpt
new file mode 100644
index 0000000000..b2be5eb040
--- /dev/null
+++ b/Zend/tests/enum/__call.phpt
@@ -0,0 +1,29 @@
+--TEST--
+Enum __call
+--FILE--
+<?php
+
+enum Foo {
+ case Bar;
+
+ public function __call(string $name, array $args)
+ {
+ return [$name, $args];
+ }
+}
+
+var_dump(Foo::Bar->baz('qux', 'quux'));
+
+?>
+--EXPECT--
+array(2) {
+ [0]=>
+ string(3) "baz"
+ [1]=>
+ array(2) {
+ [0]=>
+ string(3) "qux"
+ [1]=>
+ string(4) "quux"
+ }
+}
diff --git a/Zend/tests/enum/__callStatic.phpt b/Zend/tests/enum/__callStatic.phpt
new file mode 100644
index 0000000000..d3acd38239
--- /dev/null
+++ b/Zend/tests/enum/__callStatic.phpt
@@ -0,0 +1,27 @@
+--TEST--
+Enum __callStatic
+--FILE--
+<?php
+
+enum Foo {
+ public static function __callStatic(string $name, array $args)
+ {
+ return [$name, $args];
+ }
+}
+
+var_dump(Foo::bar('baz', 'qux'));
+
+?>
+--EXPECT--
+array(2) {
+ [0]=>
+ string(3) "bar"
+ [1]=>
+ array(2) {
+ [0]=>
+ string(3) "baz"
+ [1]=>
+ string(3) "qux"
+ }
+}
diff --git a/Zend/tests/enum/__class__.phpt b/Zend/tests/enum/__class__.phpt
new file mode 100644
index 0000000000..0b9a5834e4
--- /dev/null
+++ b/Zend/tests/enum/__class__.phpt
@@ -0,0 +1,19 @@
+--TEST--
+Enum __CLASS__
+--FILE--
+<?php
+
+enum Foo {
+ case Bar;
+
+ public function printClass()
+ {
+ echo __CLASS__ . "\n";
+ }
+}
+
+Foo::Bar->printClass();
+
+?>
+--EXPECT--
+Foo
diff --git a/Zend/tests/enum/__function__.phpt b/Zend/tests/enum/__function__.phpt
new file mode 100644
index 0000000000..d460593873
--- /dev/null
+++ b/Zend/tests/enum/__function__.phpt
@@ -0,0 +1,19 @@
+--TEST--
+Enum __FUNCTION__
+--FILE--
+<?php
+
+enum Foo {
+ case Bar;
+
+ public function printFunction()
+ {
+ echo __FUNCTION__ . "\n";
+ }
+}
+
+Foo::Bar->printFunction();
+
+?>
+--EXPECT--
+printFunction
diff --git a/Zend/tests/enum/__get.phpt b/Zend/tests/enum/__get.phpt
new file mode 100644
index 0000000000..885cc2f2c8
--- /dev/null
+++ b/Zend/tests/enum/__get.phpt
@@ -0,0 +1,17 @@
+--TEST--
+Enum __get
+--FILE--
+<?php
+
+enum Foo {
+ case Bar;
+
+ public function __get(string $name)
+ {
+ return '__get';
+ }
+}
+
+?>
+--EXPECTF--
+Fatal error: Enum may not include __get in %s on line %d
diff --git a/Zend/tests/enum/__invoke.phpt b/Zend/tests/enum/__invoke.phpt
new file mode 100644
index 0000000000..63a35b56fd
--- /dev/null
+++ b/Zend/tests/enum/__invoke.phpt
@@ -0,0 +1,24 @@
+--TEST--
+Enum __invoke
+--FILE--
+<?php
+
+enum Foo {
+ case Bar;
+
+ public function __invoke(...$args)
+ {
+ return $args;
+ }
+}
+
+var_dump((Foo::Bar)('baz', 'qux'));
+
+?>
+--EXPECT--
+array(2) {
+ [0]=>
+ string(3) "baz"
+ [1]=>
+ string(3) "qux"
+}
diff --git a/Zend/tests/enum/__isset.phpt b/Zend/tests/enum/__isset.phpt
new file mode 100644
index 0000000000..76409f075b
--- /dev/null
+++ b/Zend/tests/enum/__isset.phpt
@@ -0,0 +1,16 @@
+--TEST--
+Enum __isset
+--FILE--
+<?php
+
+enum Foo {
+ case Bar;
+
+ public function __isset($property) {
+ return true;
+ }
+}
+
+?>
+--EXPECTF--
+Fatal error: Enum may not include __isset in %s on line %d
diff --git a/Zend/tests/enum/__method__.phpt b/Zend/tests/enum/__method__.phpt
new file mode 100644
index 0000000000..5d4195566e
--- /dev/null
+++ b/Zend/tests/enum/__method__.phpt
@@ -0,0 +1,19 @@
+--TEST--
+Enum __METHOD__
+--FILE--
+<?php
+
+enum Foo {
+ case Bar;
+
+ public function printMethod()
+ {
+ echo __METHOD__ . "\n";
+ }
+}
+
+Foo::Bar->printMethod();
+
+?>
+--EXPECT--
+Foo::printMethod
diff --git a/Zend/tests/enum/ast-dumper.phpt b/Zend/tests/enum/ast-dumper.phpt
new file mode 100644
index 0000000000..ed38e44e7c
--- /dev/null
+++ b/Zend/tests/enum/ast-dumper.phpt
@@ -0,0 +1,48 @@
+--TEST--
+Enum AST dumper
+--FILE--
+<?php
+
+try {
+ assert((function () {
+ enum Foo {
+ case Bar;
+ }
+
+ #[EnumAttr]
+ enum IntFoo: int {
+ #[CaseAttr]
+ case Bar = 1 << 0;
+ case Baz = 1 << 1;
+
+ public function self() {
+ return $this;
+ }
+ }
+
+ return false;
+ })());
+} catch (Error $e) {
+ echo $e->getMessage();
+}
+
+?>
+--EXPECT--
+assert(function () {
+ enum Foo {
+ case Bar;
+ }
+
+ #[EnumAttr]
+ enum IntFoo: int {
+ #[CaseAttr]
+ case Bar = 1 << 0;
+ case Baz = 1 << 1;
+ public function self() {
+ return $this;
+ }
+
+ }
+
+ return false;
+}())
diff --git a/Zend/tests/enum/backed-cases-int.phpt b/Zend/tests/enum/backed-cases-int.phpt
new file mode 100644
index 0000000000..85d2527c39
--- /dev/null
+++ b/Zend/tests/enum/backed-cases-int.phpt
@@ -0,0 +1,26 @@
+--TEST--
+Int backed enums with can list cases
+--FILE--
+<?php
+
+enum Suit: int {
+ case Hearts = 2;
+ case Diamonds = 1;
+ case Clubs = 4;
+ case Spades = 3;
+}
+
+var_dump(Suit::cases());
+
+?>
+--EXPECT--
+array(4) {
+ [0]=>
+ enum(Suit::Hearts)
+ [1]=>
+ enum(Suit::Diamonds)
+ [2]=>
+ enum(Suit::Clubs)
+ [3]=>
+ enum(Suit::Spades)
+}
diff --git a/Zend/tests/enum/backed-cases-string.phpt b/Zend/tests/enum/backed-cases-string.phpt
new file mode 100644
index 0000000000..6d7deef77e
--- /dev/null
+++ b/Zend/tests/enum/backed-cases-string.phpt
@@ -0,0 +1,26 @@
+--TEST--
+String backed enums can list cases
+--FILE--
+<?php
+
+enum Suit: string {
+ case Hearts = 'H';
+ case Diamonds = 'D';
+ case Clubs = 'C';
+ case Spades = 'S';
+}
+
+var_dump(Suit::cases());
+
+?>
+--EXPECT--
+array(4) {
+ [0]=>
+ enum(Suit::Hearts)
+ [1]=>
+ enum(Suit::Diamonds)
+ [2]=>
+ enum(Suit::Clubs)
+ [3]=>
+ enum(Suit::Spades)
+}
diff --git a/Zend/tests/enum/backed-duplicate-int.phpt b/Zend/tests/enum/backed-duplicate-int.phpt
new file mode 100644
index 0000000000..4da70bc3dd
--- /dev/null
+++ b/Zend/tests/enum/backed-duplicate-int.phpt
@@ -0,0 +1,13 @@
+--TEST--
+Backed enums reject duplicate int values
+--FILE--
+<?php
+
+enum Foo: int {
+ case Bar = 0;
+ case Baz = 0;
+}
+
+?>
+--EXPECTF--
+Fatal error: Duplicate value in enum Foo for cases Bar and Baz in %s on line %s
diff --git a/Zend/tests/enum/backed-duplicate-string.phpt b/Zend/tests/enum/backed-duplicate-string.phpt
new file mode 100644
index 0000000000..7d42c2ac30
--- /dev/null
+++ b/Zend/tests/enum/backed-duplicate-string.phpt
@@ -0,0 +1,15 @@
+--TEST--
+Backed enums reject duplicate string values
+--FILE--
+<?php
+
+enum Suit: string {
+ case Hearts = 'H';
+ case Diamonds = 'D';
+ case Clubs = 'C';
+ case Spades = 'H';
+}
+
+?>
+--EXPECTF--
+Fatal error: Duplicate value in enum Suit for cases Hearts and Spades in %s on line %s
diff --git a/Zend/tests/enum/backed-from-invalid-int.phpt b/Zend/tests/enum/backed-from-invalid-int.phpt
new file mode 100644
index 0000000000..d976f3d0fb
--- /dev/null
+++ b/Zend/tests/enum/backed-from-invalid-int.phpt
@@ -0,0 +1,19 @@
+--TEST--
+BackedEnum::from() reject invalid int
+--FILE--
+<?php
+
+enum Foo: int {
+ case Bar = 0;
+ case Baz = 1;
+}
+
+try {
+ var_dump(Foo::from(2));
+} catch (Error $e) {
+ echo $e->getMessage() . "\n";
+}
+
+?>
+--EXPECT--
+2 is not a valid backing value for enum "Foo"
diff --git a/Zend/tests/enum/backed-from-invalid-string.phpt b/Zend/tests/enum/backed-from-invalid-string.phpt
new file mode 100644
index 0000000000..db8b791d8b
--- /dev/null
+++ b/Zend/tests/enum/backed-from-invalid-string.phpt
@@ -0,0 +1,21 @@
+--TEST--
+BackedEnum::from() reject invalid string
+--FILE--
+<?php
+
+enum Suit: string {
+ case Hearts = 'H';
+ case Diamonds = 'D';
+ case Clubs = 'C';
+ case Spades = 'S';
+}
+
+try {
+ var_dump(Suit::from('A'));
+} catch (Error $e) {
+ echo $e->getMessage() . "\n";
+}
+
+?>
+--EXPECT--
+"A" is not a valid backing value for enum "Suit"
diff --git a/Zend/tests/enum/backed-from-invalid-type.phpt b/Zend/tests/enum/backed-from-invalid-type.phpt
new file mode 100644
index 0000000000..3f35bef64f
--- /dev/null
+++ b/Zend/tests/enum/backed-from-invalid-type.phpt
@@ -0,0 +1,34 @@
+--TEST--
+BackedEnum::from() reject invalid type
+--FILE--
+<?php
+
+enum Suit: string {
+ case Hearts = 'H';
+ case Diamonds = 'D';
+ case Clubs = 'C';
+ case Spades = 'S';
+}
+
+try {
+ var_dump(Suit::from(42));
+} catch (Error $e) {
+ echo $e->getMessage() . "\n";
+}
+
+enum Foo: int {
+ case Bar = 0;
+ case Baz = 1;
+}
+
+try {
+ var_dump(Foo::from('H'));
+} catch (Error $e) {
+ echo $e->getMessage() . "\n";
+}
+
+
+?>
+--EXPECT--
+"42" is not a valid backing value for enum "Suit"
+Foo::from(): Argument #1 ($value) must be of type int, string given
diff --git a/Zend/tests/enum/backed-from-unknown-hash.phpt b/Zend/tests/enum/backed-from-unknown-hash.phpt
new file mode 100644
index 0000000000..eb5938d0d6
--- /dev/null
+++ b/Zend/tests/enum/backed-from-unknown-hash.phpt
@@ -0,0 +1,17 @@
+--TEST--
+BackedEnum::from() unknown hash
+--FILE--
+<?php
+
+enum Foo: string {
+ case Bar = 'B';
+}
+
+$s = 'A';
+$s++;
+
+var_dump(Foo::from($s));
+
+?>
+--EXPECT--
+enum(Foo::Bar)
diff --git a/Zend/tests/enum/backed-from.phpt b/Zend/tests/enum/backed-from.phpt
new file mode 100644
index 0000000000..2c77c3e388
--- /dev/null
+++ b/Zend/tests/enum/backed-from.phpt
@@ -0,0 +1,36 @@
+--TEST--
+BackedEnum::from()
+--FILE--
+<?php
+
+enum Suit: string {
+ case Hearts = 'H';
+ case Diamonds = 'D';
+ case Clubs = 'C';
+ case Spades = 'S';
+}
+
+var_dump(Suit::from('H'));
+var_dump(Suit::from('D'));
+var_dump(Suit::from('C'));
+var_dump(Suit::from('S'));
+
+enum Foo: int {
+ case Bar = 1;
+ case Baz = 2;
+ case Beep = 3;
+}
+
+var_dump(Foo::from(1));
+var_dump(Foo::from(2));
+var_dump(Foo::from(3));
+
+?>
+--EXPECT--
+enum(Suit::Hearts)
+enum(Suit::Diamonds)
+enum(Suit::Clubs)
+enum(Suit::Spades)
+enum(Foo::Bar)
+enum(Foo::Baz)
+enum(Foo::Beep)
diff --git a/Zend/tests/enum/backed-int-case-without-value.phpt b/Zend/tests/enum/backed-int-case-without-value.phpt
new file mode 100644
index 0000000000..4b84c069b4
--- /dev/null
+++ b/Zend/tests/enum/backed-int-case-without-value.phpt
@@ -0,0 +1,14 @@
+--TEST--
+Int backed enums with case without value
+--FILE--
+<?php
+
+enum Foo: int {
+ case Bar;
+}
+
+var_dump(Foo::Bar->value);
+
+?>
+--EXPECTF--
+Fatal error: Case Bar of backed enum Foo must have a value in %s on line %d
diff --git a/Zend/tests/enum/backed-int-const-expr.phpt b/Zend/tests/enum/backed-int-const-expr.phpt
new file mode 100644
index 0000000000..afb6491c51
--- /dev/null
+++ b/Zend/tests/enum/backed-int-const-expr.phpt
@@ -0,0 +1,20 @@
+--TEST--
+Int enum const expr
+--FILE--
+<?php
+
+enum Foo: int {
+ case Bar = 1 << 0;
+ case Baz = 1 << 1;
+ case Qux = 1 << 2;
+}
+
+var_dump(Foo::Bar->value);
+var_dump(Foo::Baz->value);
+var_dump(Foo::Qux->value);
+
+?>
+--EXPECT--
+int(1)
+int(2)
+int(4)
diff --git a/Zend/tests/enum/backed-int-const-invalid-expr.phpt b/Zend/tests/enum/backed-int-const-invalid-expr.phpt
new file mode 100644
index 0000000000..097bb27883
--- /dev/null
+++ b/Zend/tests/enum/backed-int-const-invalid-expr.phpt
@@ -0,0 +1,14 @@
+--TEST--
+Int enum invalid const expr
+--FILE--
+<?php
+
+enum Foo: int {
+ case Bar = 1 + $x;
+}
+
+var_dump(Foo::Bar->value);
+
+?>
+--EXPECTF--
+Fatal error: Enum case value must be constant in %s on line %d
diff --git a/Zend/tests/enum/backed-int.phpt b/Zend/tests/enum/backed-int.phpt
new file mode 100644
index 0000000000..113629f692
--- /dev/null
+++ b/Zend/tests/enum/backed-int.phpt
@@ -0,0 +1,17 @@
+--TEST--
+Int enum value
+--FILE--
+<?php
+
+enum Foo: int {
+ case Bar = 0;
+ case Baz = 1;
+}
+
+var_dump(Foo::Bar->value);
+var_dump(Foo::Baz->value);
+
+?>
+--EXPECT--
+int(0)
+int(1)
diff --git a/Zend/tests/enum/backed-invalid.phpt b/Zend/tests/enum/backed-invalid.phpt
new file mode 100644
index 0000000000..bc6f71f79d
--- /dev/null
+++ b/Zend/tests/enum/backed-invalid.phpt
@@ -0,0 +1,10 @@
+--TEST--
+Invalid enum backing type
+--FILE--
+<?php
+
+enum Foo: Bar {}
+
+?>
+--EXPECTF--
+Fatal error: Enum backing type must be int or string, Bar given in %s on line %d
diff --git a/Zend/tests/enum/backed-mismatch.phpt b/Zend/tests/enum/backed-mismatch.phpt
new file mode 100644
index 0000000000..0b28fb3be5
--- /dev/null
+++ b/Zend/tests/enum/backed-mismatch.phpt
@@ -0,0 +1,12 @@
+--TEST--
+Mismatched enum backing type
+--FILE--
+<?php
+
+enum Foo: int {
+ case Bar = 'bar';
+}
+
+?>
+--EXPECTF--
+Fatal error: Enum case type string does not match enum backing type int in %s on line %d
diff --git a/Zend/tests/enum/backed-negative-int.phpt b/Zend/tests/enum/backed-negative-int.phpt
new file mode 100644
index 0000000000..4342ecd7b2
--- /dev/null
+++ b/Zend/tests/enum/backed-negative-int.phpt
@@ -0,0 +1,21 @@
+--TEST--
+Backed enum with negative int
+--FILE--
+<?php
+
+enum Foo: int {
+ case Bar = -1;
+ case Baz = -2;
+}
+
+var_dump(Foo::Bar->value);
+var_dump(Foo::Baz->value);
+var_dump(Foo::from(-1));
+var_dump(Foo::from(-2));
+
+?>
+--EXPECT--
+int(-1)
+int(-2)
+enum(Foo::Bar)
+enum(Foo::Baz)
diff --git a/Zend/tests/enum/backed-string-heredoc.phpt b/Zend/tests/enum/backed-string-heredoc.phpt
new file mode 100644
index 0000000000..b2e9b8f3e9
--- /dev/null
+++ b/Zend/tests/enum/backed-string-heredoc.phpt
@@ -0,0 +1,30 @@
+--TEST--
+String enum value with heredoc
+--FILE--
+<?php
+
+enum Foo: string {
+ case Bar = <<<BAR
+ Bar
+ bar
+ bar
+ BAR;
+
+ case Baz = <<<'BAZ'
+ Baz
+ baz
+ baz
+ BAZ;
+}
+
+echo Foo::Bar->value . "\n";
+echo Foo::Baz->value . "\n";
+
+?>
+--EXPECT--
+Bar
+bar
+bar
+Baz
+baz
+baz
diff --git a/Zend/tests/enum/backed-string.phpt b/Zend/tests/enum/backed-string.phpt
new file mode 100644
index 0000000000..afa7db827e
--- /dev/null
+++ b/Zend/tests/enum/backed-string.phpt
@@ -0,0 +1,17 @@
+--TEST--
+String enum value
+--FILE--
+<?php
+
+enum Foo: string {
+ case Bar = 'bar';
+ case Baz = 'baz';
+}
+
+echo Foo::Bar->value . "\n";
+echo Foo::Baz->value . "\n";
+
+?>
+--EXPECT--
+bar
+baz
diff --git a/Zend/tests/enum/backed-tryFrom-casing.phpt b/Zend/tests/enum/backed-tryFrom-casing.phpt
new file mode 100644
index 0000000000..213faa3a62
--- /dev/null
+++ b/Zend/tests/enum/backed-tryFrom-casing.phpt
@@ -0,0 +1,15 @@
+--TEST--
+BackedEnum::tryFrom() casing in reflection
+--FILE--
+<?php
+
+enum Foo: string {}
+
+$reflectionEnum = new ReflectionEnum(Foo::class);
+$reflectionMethod = $reflectionEnum->getMethod('tryFrom');
+
+echo $reflectionMethod->getName() . "\n";
+
+?>
+--EXPECT--
+tryFrom
diff --git a/Zend/tests/enum/backed-tryFrom-unknown-hash.phpt b/Zend/tests/enum/backed-tryFrom-unknown-hash.phpt
new file mode 100644
index 0000000000..80ffe1dc39
--- /dev/null
+++ b/Zend/tests/enum/backed-tryFrom-unknown-hash.phpt
@@ -0,0 +1,17 @@
+--TEST--
+BackedEnum::tryFrom() unknown hash
+--FILE--
+<?php
+
+enum Foo: string {
+ case Bar = 'B';
+}
+
+$s = 'A';
+$s++;
+
+var_dump(Foo::tryFrom($s));
+
+?>
+--EXPECT--
+enum(Foo::Bar)
diff --git a/Zend/tests/enum/backed-tryFrom.phpt b/Zend/tests/enum/backed-tryFrom.phpt
new file mode 100644
index 0000000000..81c36fcc2b
--- /dev/null
+++ b/Zend/tests/enum/backed-tryFrom.phpt
@@ -0,0 +1,40 @@
+--TEST--
+BackedEnum::tryFrom()
+--FILE--
+<?php
+
+enum Suit: string {
+ case Hearts = 'H';
+ case Diamonds = 'D';
+ case Clubs = 'C';
+ case Spades = 'S';
+}
+
+var_dump(Suit::tryFrom('H'));
+var_dump(Suit::tryFrom('D'));
+var_dump(Suit::tryFrom('C'));
+var_dump(Suit::tryFrom('S'));
+var_dump(Suit::tryFrom('X'));
+
+enum Foo: int {
+ case Bar = 1;
+ case Baz = 2;
+ case Beep = 3;
+}
+
+var_dump(Foo::tryFrom(1));
+var_dump(Foo::tryFrom(2));
+var_dump(Foo::tryFrom(3));
+var_dump(Foo::tryFrom(4));
+
+?>
+--EXPECT--
+enum(Suit::Hearts)
+enum(Suit::Diamonds)
+enum(Suit::Clubs)
+enum(Suit::Spades)
+NULL
+enum(Foo::Bar)
+enum(Foo::Baz)
+enum(Foo::Beep)
+NULL
diff --git a/Zend/tests/enum/backed-type-no-union.phpt b/Zend/tests/enum/backed-type-no-union.phpt
new file mode 100644
index 0000000000..8330f8de3d
--- /dev/null
+++ b/Zend/tests/enum/backed-type-no-union.phpt
@@ -0,0 +1,10 @@
+--TEST--
+Backed enums type can't be union
+--FILE--
+<?php
+
+enum Foo: int|string {}
+
+?>
+--EXPECTF--
+Fatal error: Enum backing type must be int or string, string|int given in %s on line %d
diff --git a/Zend/tests/enum/basic-methods.phpt b/Zend/tests/enum/basic-methods.phpt
new file mode 100644
index 0000000000..ee1c2c24d7
--- /dev/null
+++ b/Zend/tests/enum/basic-methods.phpt
@@ -0,0 +1,21 @@
+--TEST--
+Enum methods
+--FILE--
+<?php
+
+enum Foo {
+ case Bar;
+ case Baz;
+
+ public function dump() {
+ var_dump($this);
+ }
+}
+
+Foo::Bar->dump();
+Foo::Baz->dump();
+
+?>
+--EXPECT--
+enum(Foo::Bar)
+enum(Foo::Baz)
diff --git a/Zend/tests/enum/case-attributes.phpt b/Zend/tests/enum/case-attributes.phpt
new file mode 100644
index 0000000000..a5ec8600cd
--- /dev/null
+++ b/Zend/tests/enum/case-attributes.phpt
@@ -0,0 +1,25 @@
+--TEST--
+Enum case attributes
+--FILE--
+<?php
+
+#[Attribute(Attribute::TARGET_CLASS_CONSTANT)]
+class EnumCaseAttribute {
+ public function __construct(
+ public string $value,
+ ) {}
+}
+
+enum Foo {
+ #[EnumCaseAttribute('Bar')]
+ case Bar;
+}
+
+var_dump((new \ReflectionClassConstant(Foo::class, 'Bar'))->getAttributes(EnumCaseAttribute::class)[0]->newInstance());
+
+?>
+--EXPECT--
+object(EnumCaseAttribute)#1 (1) {
+ ["value"]=>
+ string(3) "Bar"
+}
diff --git a/Zend/tests/enum/case-in-class.phpt b/Zend/tests/enum/case-in-class.phpt
new file mode 100644
index 0000000000..61713e6f89
--- /dev/null
+++ b/Zend/tests/enum/case-in-class.phpt
@@ -0,0 +1,12 @@
+--TEST--
+Enum case in class
+--FILE--
+<?php
+
+class Foo {
+ case Bar;
+}
+
+?>
+--EXPECTF--
+Fatal error: Case can only be used in enums in %s on line %d
diff --git a/Zend/tests/enum/cases-refcount.phpt b/Zend/tests/enum/cases-refcount.phpt
new file mode 100644
index 0000000000..040589ca23
--- /dev/null
+++ b/Zend/tests/enum/cases-refcount.phpt
@@ -0,0 +1,22 @@
+--TEST--
+Enum cases increases refcount
+--FILE--
+<?php
+
+enum Foo {
+ case Bar;
+}
+
+function callCases() {
+ Foo::cases();
+}
+
+callCases();
+debug_zval_dump(Foo::Bar);
+
+?>
+--EXPECT--
+object(Foo)#1 (1) refcount(2){
+ ["name"]=>
+ string(3) "Bar" interned
+}
diff --git a/Zend/tests/enum/comparison.phpt b/Zend/tests/enum/comparison.phpt
new file mode 100644
index 0000000000..5df2f282ec
--- /dev/null
+++ b/Zend/tests/enum/comparison.phpt
@@ -0,0 +1,57 @@
+--TEST--
+Enum comparison
+--FILE--
+<?php
+
+enum Foo {
+ case Bar;
+ case Baz;
+}
+
+$bar = Foo::Bar;
+$baz = Foo::Baz;
+
+var_dump($bar === $bar);
+var_dump($bar == $bar);
+
+var_dump($bar === $baz);
+var_dump($bar == $baz);
+
+var_dump($baz === $bar);
+var_dump($baz == $bar);
+
+var_dump($bar > $bar);
+var_dump($bar < $bar);
+var_dump($bar >= $bar);
+var_dump($bar <= $bar);
+
+var_dump($bar > $baz);
+var_dump($bar < $baz);
+var_dump($bar >= $baz);
+var_dump($bar <= $baz);
+
+var_dump($bar > true);
+var_dump($bar < true);
+var_dump($bar >= true);
+var_dump($bar <= true);
+
+?>
+--EXPECT--
+bool(true)
+bool(true)
+bool(false)
+bool(false)
+bool(false)
+bool(false)
+bool(false)
+bool(false)
+bool(true)
+bool(true)
+bool(false)
+bool(false)
+bool(false)
+bool(false)
+bool(false)
+bool(false)
+bool(false)
+bool(false)
diff --git a/Zend/tests/enum/constant-aliases.phpt b/Zend/tests/enum/constant-aliases.phpt
new file mode 100644
index 0000000000..1652eb3c48
--- /dev/null
+++ b/Zend/tests/enum/constant-aliases.phpt
@@ -0,0 +1,21 @@
+--TEST--
+Enum constants can alias cases
+--FILE--
+<?php
+
+enum Foo {
+ case Bar;
+ const Baz = self::Bar;
+}
+
+function test(Foo $var) {
+ echo "works\n";
+}
+
+test(Foo::Bar);
+test(Foo::Baz);
+
+?>
+--EXPECT--
+works
+works
diff --git a/Zend/tests/enum/constants.phpt b/Zend/tests/enum/constants.phpt
new file mode 100644
index 0000000000..cc983e5184
--- /dev/null
+++ b/Zend/tests/enum/constants.phpt
@@ -0,0 +1,14 @@
+--TEST--
+Enum allows constants
+--FILE--
+<?php
+
+enum Foo {
+ const BAR = 'Bar';
+}
+
+echo Foo::BAR . "\n";
+
+?>
+--EXPECT--
+Bar
diff --git a/Zend/tests/enum/default-parameter.phpt b/Zend/tests/enum/default-parameter.phpt
new file mode 100644
index 0000000000..02aa05b5a8
--- /dev/null
+++ b/Zend/tests/enum/default-parameter.phpt
@@ -0,0 +1,18 @@
+--TEST--
+Enum in default parameter
+--FILE--
+<?php
+
+enum Foo {
+ case Bar;
+}
+
+function baz(Foo $foo = Foo::Bar) {
+ var_dump($foo);
+}
+
+baz();
+
+?>
+--EXPECT--
+enum(Foo::Bar)
diff --git a/Zend/tests/enum/enum-as-constant.phpt b/Zend/tests/enum/enum-as-constant.phpt
new file mode 100644
index 0000000000..26a589ae79
--- /dev/null
+++ b/Zend/tests/enum/enum-as-constant.phpt
@@ -0,0 +1,22 @@
+--TEST--
+Enum cases can be referenced by constants
+--FILE--
+<?php
+
+enum Foo {
+ case Bar;
+}
+
+const Beep = Foo::Bar;
+
+class Test {
+ const Beep = Foo::Bar;
+}
+
+var_dump(Beep);
+var_dump(Test::Beep);
+
+?>
+--EXPECT--
+enum(Foo::Bar)
+enum(Foo::Bar)
diff --git a/Zend/tests/enum/enum-as-params.phpt b/Zend/tests/enum/enum-as-params.phpt
new file mode 100644
index 0000000000..90a1f0b5d0
--- /dev/null
+++ b/Zend/tests/enum/enum-as-params.phpt
@@ -0,0 +1,35 @@
+--TEST--
+Enum types as parameters
+--FILE--
+<?php
+
+enum Foo {
+ case Bar;
+}
+
+enum Baz {
+ case Qux;
+}
+
+function takesFoo(Foo $foo) {}
+function takesBaz(Baz $baz) {}
+
+takesFoo(Foo::Bar);
+takesBaz(Baz::Qux);
+
+try {
+ takesBaz(Foo::Bar);
+} catch (Error $e) {
+ echo $e->getMessage() . "\n";
+}
+
+try {
+ takesFoo(Baz::Qux);
+} catch (Error $e) {
+ echo $e->getMessage() . "\n";
+}
+
+?>
+--EXPECTF--
+takesBaz(): Argument #1 ($baz) must be of type Baz, Foo given, called in %s on line %d
+takesFoo(): Argument #1 ($foo) must be of type Foo, Baz given, called in %s on line %d
diff --git a/Zend/tests/enum/enum-attributes.phpt b/Zend/tests/enum/enum-attributes.phpt
new file mode 100644
index 0000000000..e0e57c744c
--- /dev/null
+++ b/Zend/tests/enum/enum-attributes.phpt
@@ -0,0 +1,23 @@
+--TEST--
+Enum attributes
+--FILE--
+<?php
+
+#[Attribute]
+class EnumAttribute {
+ public function __construct(
+ public string $value,
+ ) {}
+}
+
+#[EnumAttribute('Foo')]
+enum Foo {}
+
+var_dump((new \ReflectionClass(Foo::class))->getAttributes(EnumAttribute::class)[0]->newInstance());
+
+?>
+--EXPECT--
+object(EnumAttribute)#1 (1) {
+ ["value"]=>
+ string(3) "Foo"
+}
diff --git a/Zend/tests/enum/enum-in-constant.phpt b/Zend/tests/enum/enum-in-constant.phpt
new file mode 100644
index 0000000000..487b4a6aad
--- /dev/null
+++ b/Zend/tests/enum/enum-in-constant.phpt
@@ -0,0 +1,22 @@
+--TEST--
+Enum in constant
+--FILE--
+<?php
+
+enum Foo {
+ case Bar;
+}
+
+class Baz {
+ const BAR = Foo::Bar;
+}
+
+var_dump(Foo::Bar);
+var_dump(Baz::BAR);
+var_dump(Foo::Bar === Baz::BAR);
+
+?>
+--EXPECT--
+enum(Foo::Bar)
+enum(Foo::Bar)
+bool(true)
diff --git a/Zend/tests/enum/enum-in-static-var.phpt b/Zend/tests/enum/enum-in-static-var.phpt
new file mode 100644
index 0000000000..9f5ebc6264
--- /dev/null
+++ b/Zend/tests/enum/enum-in-static-var.phpt
@@ -0,0 +1,21 @@
+--TEST--
+Enum in static var
+--FILE--
+<?php
+
+enum Foo {
+ case Bar;
+}
+
+function example() {
+ static $bar = Foo::Bar;
+ return $bar;
+}
+
+var_dump(example());
+var_dump(example());
+
+?>
+--EXPECT--
+enum(Foo::Bar)
+enum(Foo::Bar)
diff --git a/Zend/tests/enum/enum-reserved-non-modifiers.phpt b/Zend/tests/enum/enum-reserved-non-modifiers.phpt
new file mode 100644
index 0000000000..b07114856f
--- /dev/null
+++ b/Zend/tests/enum/enum-reserved-non-modifiers.phpt
@@ -0,0 +1,32 @@
+--TEST--
+enum keyword is reserved_non_modifiers
+--FILE--
+<?php
+
+namespace enum {
+ class Foo {
+ public static function bar() {
+ return 'enum\Foo::bar()';
+ }
+ }
+}
+
+namespace {
+ class Foo {
+ const enum = 'enum const';
+
+ public static function enum() {
+ return 'enum static method';
+ }
+ }
+
+ echo \enum\Foo::bar() . "\n";
+ echo Foo::enum . "\n";
+ echo Foo::enum() . "\n";
+}
+
+?>
+--EXPECT--
+enum\Foo::bar()
+enum const
+enum static method
diff --git a/Zend/tests/enum/enum_exists.phpt b/Zend/tests/enum/enum_exists.phpt
new file mode 100644
index 0000000000..d1e86447c4
--- /dev/null
+++ b/Zend/tests/enum/enum_exists.phpt
@@ -0,0 +1,41 @@
+--TEST--
+enum_exists
+--FILE--
+<?php
+
+enum Foo {
+ case Bar;
+}
+
+class Baz {}
+
+spl_autoload_register(function ($className) {
+ echo "Triggered autoloader with class $className\n";
+
+ if ($className === 'Quux') {
+ enum Quux {}
+ }
+});
+
+var_dump(enum_exists(Foo::class));
+var_dump(enum_exists(Foo::Bar::class));
+var_dump(enum_exists(Baz::class));
+var_dump(enum_exists(Qux::class));
+var_dump(enum_exists(Quux::class, false));
+var_dump(enum_exists(Quux::class, true));
+var_dump(enum_exists(Quuz::class, false));
+var_dump(enum_exists(Quuz::class, true));
+
+?>
+--EXPECT--
+bool(true)
+bool(true)
+bool(false)
+Triggered autoloader with class Qux
+bool(false)
+bool(false)
+Triggered autoloader with class Quux
+bool(true)
+bool(false)
+Triggered autoloader with class Quuz
+bool(false)
diff --git a/Zend/tests/enum/final.phpt b/Zend/tests/enum/final.phpt
new file mode 100644
index 0000000000..ca13b7da8c
--- /dev/null
+++ b/Zend/tests/enum/final.phpt
@@ -0,0 +1,12 @@
+--TEST--
+Enum is final
+--FILE--
+<?php
+
+enum Foo {}
+
+class Bar extends Foo {}
+
+?>
+--EXPECTF--
+Fatal error: Class Bar may not inherit from final class (Foo) in %s on line %d
diff --git a/Zend/tests/enum/implements.phpt b/Zend/tests/enum/implements.phpt
new file mode 100644
index 0000000000..fc90ab2aef
--- /dev/null
+++ b/Zend/tests/enum/implements.phpt
@@ -0,0 +1,34 @@
+--TEST--
+Enum implements
+--FILE--
+<?php
+
+interface Colorful {
+ public function color(): string;
+}
+
+enum Suit implements Colorful {
+ case Hearts;
+ case Diamonds;
+ case Clubs;
+ case Spades;
+
+ public function color(): string {
+ return match ($this) {
+ self::Hearts, self::Diamonds => 'Red',
+ self::Clubs, self::Spades => 'Black',
+ };
+ }
+}
+
+echo Suit::Hearts->color() . "\n";
+echo Suit::Diamonds->color() . "\n";
+echo Suit::Clubs->color() . "\n";
+echo Suit::Spades->color() . "\n";
+
+?>
+--EXPECT--
+Red
+Red
+Black
+Black
diff --git a/Zend/tests/enum/instanceof-backed-enum.phpt b/Zend/tests/enum/instanceof-backed-enum.phpt
new file mode 100644
index 0000000000..4716835d11
--- /dev/null
+++ b/Zend/tests/enum/instanceof-backed-enum.phpt
@@ -0,0 +1,20 @@
+--TEST--
+Auto implement BackedEnum interface
+--FILE--
+<?php
+
+enum Foo {
+ case Bar;
+}
+
+enum Baz: int {
+ case Qux = 0;
+}
+
+var_dump(Foo::Bar instanceof BackedEnum);
+var_dump(Baz::Qux instanceof BackedEnum);
+
+?>
+--EXPECT--
+bool(false)
+bool(true)
diff --git a/Zend/tests/enum/instanceof-unitenum.phpt b/Zend/tests/enum/instanceof-unitenum.phpt
new file mode 100644
index 0000000000..5523796325
--- /dev/null
+++ b/Zend/tests/enum/instanceof-unitenum.phpt
@@ -0,0 +1,18 @@
+--TEST--
+Auto implement UnitEnum interface
+--FILE--
+<?php
+
+enum Foo {
+ case Bar;
+}
+
+class Baz {}
+
+var_dump(Foo::Bar instanceof UnitEnum);
+var_dump((new Baz()) instanceof UnitEnum);
+
+?>
+--EXPECT--
+bool(true)
+bool(false)
diff --git a/Zend/tests/enum/instanceof.phpt b/Zend/tests/enum/instanceof.phpt
new file mode 100644
index 0000000000..0e29b9d282
--- /dev/null
+++ b/Zend/tests/enum/instanceof.phpt
@@ -0,0 +1,25 @@
+--TEST--
+Enum instanceof
+--FILE--
+<?php
+
+enum Foo {
+ case Bar;
+}
+
+enum Baz {
+ case Qux;
+}
+
+var_dump(Foo::Bar instanceof Foo);
+var_dump(Baz::Qux instanceof Baz);
+
+var_dump(Foo::Bar instanceof Baz);
+var_dump(Baz::Qux instanceof Foo);
+
+?>
+--EXPECT--
+bool(true)
+bool(true)
+bool(false)
+bool(false)
diff --git a/Zend/tests/enum/json_encode.phpt b/Zend/tests/enum/json_encode.phpt
new file mode 100644
index 0000000000..016ca6107f
--- /dev/null
+++ b/Zend/tests/enum/json_encode.phpt
@@ -0,0 +1,59 @@
+--TEST--
+Enum in json_encode
+--FILE--
+<?php
+
+enum Foo {
+ case Bar;
+}
+
+enum IntFoo: int {
+ case Bar = 0;
+}
+
+enum StringFoo: string {
+ case Bar = 'Bar';
+}
+
+enum CustomFoo implements JsonSerializable {
+ case Bar;
+
+ public function jsonSerialize() {
+ return 'Custom ' . $this->name;
+ }
+}
+
+function test($value) {
+ var_dump(json_encode($value));
+ echo json_last_error_msg() . "\n";
+
+ try {
+ var_dump(json_encode($value, JSON_THROW_ON_ERROR));
+ echo json_last_error_msg() . "\n";
+ } catch (Exception $e) {
+ echo get_class($e) . ': ' . $e->getMessage() . "\n";
+ }
+}
+
+test(Foo::Bar);
+test(IntFoo::Bar);
+test(StringFoo::Bar);
+test(CustomFoo::Bar);
+
+?>
+--EXPECT--
+bool(false)
+Non-backed enums have no default serialization
+JsonException: Non-backed enums have no default serialization
+string(1) "0"
+No error
+string(1) "0"
+No error
+string(5) ""Bar""
+No error
+string(5) ""Bar""
+No error
+string(12) ""Custom Bar""
+No error
+string(12) ""Custom Bar""
+No error
diff --git a/Zend/tests/enum/keyword-no-bc-break.phpt b/Zend/tests/enum/keyword-no-bc-break.phpt
new file mode 100644
index 0000000000..92b8226afa
--- /dev/null
+++ b/Zend/tests/enum/keyword-no-bc-break.phpt
@@ -0,0 +1,42 @@
+--TEST--
+Enum keyword can still be used in classes, namespaces, functions and constants
+--FILE--
+<?php
+
+namespace enum {
+ class Foo {}
+}
+
+namespace foo {
+ class Bar {}
+ class enum extends Bar {}
+}
+
+namespace bar {
+ interface Baz {}
+ class enum implements Baz {}
+}
+
+namespace {
+ class enum {}
+
+ function enum() {
+ return 'enum function';
+ }
+
+ const enum = 'enum constant';
+
+ var_dump(new enum\Foo());
+ var_dump(new enum());
+ var_dump(enum());
+ var_dump(enum);
+}
+
+?>
+--EXPECT--
+object(enum\Foo)#1 (0) {
+}
+object(enum)#1 (0) {
+}
+string(13) "enum function"
+string(13) "enum constant"
diff --git a/Zend/tests/enum/keyword-whitespace.phpt b/Zend/tests/enum/keyword-whitespace.phpt
new file mode 100644
index 0000000000..bdcbd94079
--- /dev/null
+++ b/Zend/tests/enum/keyword-whitespace.phpt
@@ -0,0 +1,16 @@
+--TEST--
+Enum keyword can be followed by arbitrary whitespaces
+--FILE--
+<?php
+
+enum A {}
+enum B {}
+enum C {}
+enum E {}
+enum
+F {}
+enum
+ G {}
+
+?>
+--EXPECT--
diff --git a/Zend/tests/enum/name-property.phpt b/Zend/tests/enum/name-property.phpt
new file mode 100644
index 0000000000..2197f235bb
--- /dev/null
+++ b/Zend/tests/enum/name-property.phpt
@@ -0,0 +1,50 @@
+--TEST--
+Enum name property
+--FILE--
+<?php
+
+enum Foo {
+ case Bar;
+ case Baz;
+}
+
+enum IntFoo: int {
+ case Bar = 0;
+ case Baz = 1;
+}
+
+var_dump((new ReflectionClass(Foo::class))->getProperties());
+var_dump(Foo::Bar->name);
+
+var_dump((new ReflectionClass(IntFoo::class))->getProperties());
+var_dump(IntFoo::Bar->name);
+
+?>
+--EXPECT--
+array(1) {
+ [0]=>
+ object(ReflectionProperty)#2 (2) {
+ ["name"]=>
+ string(4) "name"
+ ["class"]=>
+ string(3) "Foo"
+ }
+}
+string(3) "Bar"
+array(2) {
+ [0]=>
+ object(ReflectionProperty)#3 (2) {
+ ["name"]=>
+ string(4) "name"
+ ["class"]=>
+ string(6) "IntFoo"
+ }
+ [1]=>
+ object(ReflectionProperty)#4 (2) {
+ ["name"]=>
+ string(5) "value"
+ ["class"]=>
+ string(6) "IntFoo"
+ }
+}
+string(3) "Bar"
diff --git a/Zend/tests/enum/namespaces.phpt b/Zend/tests/enum/namespaces.phpt
new file mode 100644
index 0000000000..261a885b55
--- /dev/null
+++ b/Zend/tests/enum/namespaces.phpt
@@ -0,0 +1,32 @@
+--TEST--
+Enum namespace
+--FILE--
+<?php
+
+namespace Foo {
+ enum Bar {
+ case Baz;
+
+ public function dump() {
+ var_dump(Bar::Baz);
+ }
+ }
+
+ function dumpBar() {
+ Bar::Baz->dump();
+ }
+}
+
+namespace {
+ use Foo\Bar;
+
+ \Foo\dumpBar();
+ \Foo\Bar::Baz->dump();
+ Bar::Baz->dump();
+}
+
+?>
+--EXPECT--
+enum(Foo\Bar::Baz)
+enum(Foo\Bar::Baz)
+enum(Foo\Bar::Baz)
diff --git a/Zend/tests/enum/no-cases.phpt b/Zend/tests/enum/no-cases.phpt
new file mode 100644
index 0000000000..cf518aefb9
--- /dev/null
+++ b/Zend/tests/enum/no-cases.phpt
@@ -0,0 +1,16 @@
+--TEST--
+Enum no manual cases method
+--FILE--
+<?php
+
+enum Foo {
+ case Bar;
+
+ public static function cases(): array {
+ return [];
+ }
+}
+
+?>
+--EXPECTF--
+Fatal error: Cannot redeclare Foo::cases() in %s on line %d
diff --git a/Zend/tests/enum/no-class-implements-backed-enum.phpt b/Zend/tests/enum/no-class-implements-backed-enum.phpt
new file mode 100644
index 0000000000..f6f37818da
--- /dev/null
+++ b/Zend/tests/enum/no-class-implements-backed-enum.phpt
@@ -0,0 +1,10 @@
+--TEST--
+Class cannot implement BackedEnum
+--FILE--
+<?php
+
+class Foo implements BackedEnum {}
+
+?>
+--EXPECTF--
+Fatal error: Non-enum class Foo cannot implement interface BackedEnum in %s on line %d
diff --git a/Zend/tests/enum/no-class-implements-unit-enum.phpt b/Zend/tests/enum/no-class-implements-unit-enum.phpt
new file mode 100644
index 0000000000..02ddd4567f
--- /dev/null
+++ b/Zend/tests/enum/no-class-implements-unit-enum.phpt
@@ -0,0 +1,10 @@
+--TEST--
+Class cannot implement UnitEnum
+--FILE--
+<?php
+
+class Foo implements UnitEnum {}
+
+?>
+--EXPECTF--
+Fatal error: Non-enum class Foo cannot implement interface UnitEnum in %s on line %d
diff --git a/Zend/tests/enum/no-clone.phpt b/Zend/tests/enum/no-clone.phpt
new file mode 100644
index 0000000000..2fadbb26b7
--- /dev/null
+++ b/Zend/tests/enum/no-clone.phpt
@@ -0,0 +1,18 @@
+--TEST--
+Enum disallows cloning
+--FILE--
+<?php
+
+enum Foo {
+ case Bar;
+}
+
+try {
+ var_dump(clone Foo::Bar);
+} catch (Error $e) {
+ echo $e->getMessage() . "\n";
+}
+
+?>
+--EXPECT--
+Trying to clone an uncloneable object of class Foo
diff --git a/Zend/tests/enum/no-constructors.phpt b/Zend/tests/enum/no-constructors.phpt
new file mode 100644
index 0000000000..70504e14eb
--- /dev/null
+++ b/Zend/tests/enum/no-constructors.phpt
@@ -0,0 +1,12 @@
+--TEST--
+Enum disallows constructor
+--FILE--
+<?php
+
+enum Foo {
+ public function __construct() {}
+}
+
+?>
+--EXPECTF--
+Fatal error: Enum may not include __construct in %s on line %d
diff --git a/Zend/tests/enum/no-destruct.phpt b/Zend/tests/enum/no-destruct.phpt
new file mode 100644
index 0000000000..730c29f847
--- /dev/null
+++ b/Zend/tests/enum/no-destruct.phpt
@@ -0,0 +1,12 @@
+--TEST--
+Enum disallows destructor
+--FILE--
+<?php
+
+enum Foo {
+ public function __destruct() {}
+}
+
+?>
+--EXPECTF--
+Fatal error: Enum may not include __destruct in %s on line %d
diff --git a/Zend/tests/enum/no-dynamic-properties.phpt b/Zend/tests/enum/no-dynamic-properties.phpt
new file mode 100644
index 0000000000..21766ce1b4
--- /dev/null
+++ b/Zend/tests/enum/no-dynamic-properties.phpt
@@ -0,0 +1,20 @@
+--TEST--
+Enum case disallows dynamic properties
+--FILE--
+<?php
+
+enum Foo {
+ case Bar;
+}
+
+$bar = Foo::Bar;
+
+try {
+ $bar->baz = 'Baz';
+} catch (\Error $e) {
+ echo $e->getMessage();
+}
+
+?>
+--EXPECT--
+Enum properties are immutable
diff --git a/Zend/tests/enum/no-enum-implements-backed-enum.phpt b/Zend/tests/enum/no-enum-implements-backed-enum.phpt
new file mode 100644
index 0000000000..69922c13a8
--- /dev/null
+++ b/Zend/tests/enum/no-enum-implements-backed-enum.phpt
@@ -0,0 +1,10 @@
+--TEST--
+Enum cannot manually implement BackedEnum
+--FILE--
+<?php
+
+enum Foo: int implements BackedEnum {}
+
+?>
+--EXPECTF--
+Fatal error: Class Foo cannot implement previously implemented interface BackedEnum in %s on line %d
diff --git a/Zend/tests/enum/no-enum-implements-unit-enum.phpt b/Zend/tests/enum/no-enum-implements-unit-enum.phpt
new file mode 100644
index 0000000000..458efbb67c
--- /dev/null
+++ b/Zend/tests/enum/no-enum-implements-unit-enum.phpt
@@ -0,0 +1,10 @@
+--TEST--
+Enum cannot manually implement UnitEnum
+--FILE--
+<?php
+
+enum Foo implements UnitEnum {}
+
+?>
+--EXPECTF--
+Fatal error: Class Foo cannot implement previously implemented interface UnitEnum in %s on line %d
diff --git a/Zend/tests/enum/no-from.phpt b/Zend/tests/enum/no-from.phpt
new file mode 100644
index 0000000000..1cf717de88
--- /dev/null
+++ b/Zend/tests/enum/no-from.phpt
@@ -0,0 +1,16 @@
+--TEST--
+Enum no manual from method
+--FILE--
+<?php
+
+enum Foo: int {
+ case Bar = 0;
+
+ public static function from(string|int $value): self {
+ return $this;
+ }
+}
+
+?>
+--EXPECTF--
+Fatal error: Cannot redeclare Foo::from() in %s on line %d
diff --git a/Zend/tests/enum/no-implement-serializable-indirect.phpt b/Zend/tests/enum/no-implement-serializable-indirect.phpt
new file mode 100644
index 0000000000..5e7bdc9338
--- /dev/null
+++ b/Zend/tests/enum/no-implement-serializable-indirect.phpt
@@ -0,0 +1,24 @@
+--TEST--
+Enum must not implement Serializable indirectly
+--FILE--
+<?php
+
+interface MySerializable extends Serializable {}
+
+enum Foo implements MySerializable {
+ case Bar;
+
+ public function serialize() {
+ return serialize('Hello');
+ }
+
+ public function unserialize($data) {
+ return unserialize($data);
+ }
+}
+
+var_dump(unserialize(serialize(Foo::Bar)));
+
+?>
+--EXPECTF--
+Fatal error: Enums may not implement the Serializable interface in %s on line %d
diff --git a/Zend/tests/enum/no-implement-serializable.phpt b/Zend/tests/enum/no-implement-serializable.phpt
new file mode 100644
index 0000000000..62485f2934
--- /dev/null
+++ b/Zend/tests/enum/no-implement-serializable.phpt
@@ -0,0 +1,22 @@
+--TEST--
+Enum must not implement Serializable
+--FILE--
+<?php
+
+enum Foo implements Serializable {
+ case Bar;
+
+ public function serialize() {
+ return serialize('Hello');
+ }
+
+ public function unserialize($data) {
+ return unserialize($data);
+ }
+}
+
+var_dump(unserialize(serialize(Foo::Bar)));
+
+?>
+--EXPECTF--
+Fatal error: Enums may not implement the Serializable interface in %s on line %d
diff --git a/Zend/tests/enum/no-name-property.phpt b/Zend/tests/enum/no-name-property.phpt
new file mode 100644
index 0000000000..a565c08e83
--- /dev/null
+++ b/Zend/tests/enum/no-name-property.phpt
@@ -0,0 +1,12 @@
+--TEST--
+Enum disallows name property
+--FILE--
+<?php
+
+enum Foo {
+ public string $name;
+}
+
+?>
+--EXPECTF--
+Fatal error: Enums may not include properties in %s on line %d
diff --git a/Zend/tests/enum/no-new-through-reflection.phpt b/Zend/tests/enum/no-new-through-reflection.phpt
new file mode 100644
index 0000000000..9a92559cd3
--- /dev/null
+++ b/Zend/tests/enum/no-new-through-reflection.phpt
@@ -0,0 +1,16 @@
+--TEST--
+Enum no new through reflection
+--FILE--
+<?php
+
+enum Foo {}
+
+try {
+ (new \ReflectionClass(Foo::class))->newInstanceWithoutConstructor();
+} catch (Error $e) {
+ echo $e->getMessage() . "\n";
+}
+
+?>
+--EXPECT--
+Cannot instantiate enum Foo
diff --git a/Zend/tests/enum/no-new.phpt b/Zend/tests/enum/no-new.phpt
new file mode 100644
index 0000000000..698e954882
--- /dev/null
+++ b/Zend/tests/enum/no-new.phpt
@@ -0,0 +1,16 @@
+--TEST--
+Enum no new
+--FILE--
+<?php
+
+enum Foo {}
+
+try {
+ new Foo();
+} catch (\Error $e) {
+ echo $e->getMessage();
+}
+
+?>
+--EXPECT--
+Cannot instantiate enum Foo
diff --git a/Zend/tests/enum/no-non-backed-enum-implements-backed-enum.phpt b/Zend/tests/enum/no-non-backed-enum-implements-backed-enum.phpt
new file mode 100644
index 0000000000..f560490de4
--- /dev/null
+++ b/Zend/tests/enum/no-non-backed-enum-implements-backed-enum.phpt
@@ -0,0 +1,10 @@
+--TEST--
+Non-backed enum cannot implement BackedEnum
+--FILE--
+<?php
+
+enum Foo implements BackedEnum {}
+
+?>
+--EXPECTF--
+Fatal error: Non-backed enum Foo cannot implement interface BackedEnum in %s on line %d
diff --git a/Zend/tests/enum/no-pass-properties-by-ref.phpt b/Zend/tests/enum/no-pass-properties-by-ref.phpt
new file mode 100644
index 0000000000..8f429dfa14
--- /dev/null
+++ b/Zend/tests/enum/no-pass-properties-by-ref.phpt
@@ -0,0 +1,26 @@
+--TEST--
+Enum properties cannot be passed by-ref
+--FILE--
+<?php
+
+enum Foo: int {
+ case Bar = 0;
+}
+
+function setBarValueByRef(&$bar, $value) {
+ $bar = $value;
+}
+
+try {
+ $bar = Foo::Bar;
+ $value = setBarValueByRef($bar->value, 1);
+} catch (Error $e) {
+ echo $e->getMessage() . "\n";
+}
+
+var_dump(Foo::Bar->value);
+
+?>
+--EXPECT--
+Cannot acquire reference to property Foo::$value
+int(0)
diff --git a/Zend/tests/enum/no-properties.phpt b/Zend/tests/enum/no-properties.phpt
new file mode 100644
index 0000000000..e846845d22
--- /dev/null
+++ b/Zend/tests/enum/no-properties.phpt
@@ -0,0 +1,12 @@
+--TEST--
+Enum disallows properties
+--FILE--
+<?php
+
+enum Foo {
+ public $bar;
+}
+
+?>
+--EXPECTF--
+Fatal error: Enums may not include properties in %s on line %d
diff --git a/Zend/tests/enum/no-return-properties-by-ref.phpt b/Zend/tests/enum/no-return-properties-by-ref.phpt
new file mode 100644
index 0000000000..988d480de1
--- /dev/null
+++ b/Zend/tests/enum/no-return-properties-by-ref.phpt
@@ -0,0 +1,27 @@
+--TEST--
+Enum properties cannot be returned by-ref
+--FILE--
+<?php
+
+enum Foo: int {
+ case Bar = 0;
+}
+
+function &getBarValueByRef() {
+ $bar = Foo::Bar;
+ return $bar->value;
+}
+
+try {
+ $value = &getBarValueByRef();
+ $value = 1;
+} catch (Error $e) {
+ echo $e->getMessage() . "\n";
+}
+
+var_dump(Foo::Bar->value);
+
+?>
+--EXPECT--
+Cannot acquire reference to property Foo::$value
+int(0)
diff --git a/Zend/tests/enum/no-static-properties.phpt b/Zend/tests/enum/no-static-properties.phpt
new file mode 100644
index 0000000000..4b823e98e9
--- /dev/null
+++ b/Zend/tests/enum/no-static-properties.phpt
@@ -0,0 +1,12 @@
+--TEST--
+Enum disallows static properties
+--FILE--
+<?php
+
+enum Foo {
+ public static $bar;
+}
+
+?>
+--EXPECTF--
+Fatal error: Enums may not include properties in %s on line %d
diff --git a/Zend/tests/enum/no-unsed-value.phpt b/Zend/tests/enum/no-unsed-value.phpt
new file mode 100644
index 0000000000..a1cbdd43ca
--- /dev/null
+++ b/Zend/tests/enum/no-unsed-value.phpt
@@ -0,0 +1,14 @@
+--TEST--
+Enum prevent unsetting value
+--FILE--
+<?php
+
+enum Foo: int {
+ case Bar = 0;
+}
+
+unset(Foo::Bar->value);
+
+?>
+--EXPECTF--
+Fatal error: Cannot use temporary expression in write context in %s on line %d
diff --git a/Zend/tests/enum/no-unset-propertes.phpt b/Zend/tests/enum/no-unset-propertes.phpt
new file mode 100644
index 0000000000..338b22a026
--- /dev/null
+++ b/Zend/tests/enum/no-unset-propertes.phpt
@@ -0,0 +1,37 @@
+--TEST--
+Enum properties cannot be unset
+--FILE--
+<?php
+
+enum Foo {
+ case Bar;
+}
+
+enum IntFoo: int {
+ case Bar = 0;
+}
+
+$foo = Foo::Bar;
+try {
+ unset($foo->name);
+} catch (Error $e) {
+ echo $e->getMessage() . "\n";
+}
+
+$intFoo = IntFoo::Bar;
+try {
+ unset($intFoo->name);
+} catch (Error $e) {
+ echo $e->getMessage() . "\n";
+}
+try {
+ unset($intFoo->value);
+} catch (Error $e) {
+ echo $e->getMessage() . "\n";
+}
+
+?>
+--EXPECT--
+Enum properties are immutable
+Enum properties are immutable
+Enum properties are immutable
diff --git a/Zend/tests/enum/no-value-property.phpt b/Zend/tests/enum/no-value-property.phpt
new file mode 100644
index 0000000000..d8b12f2ca8
--- /dev/null
+++ b/Zend/tests/enum/no-value-property.phpt
@@ -0,0 +1,12 @@
+--TEST--
+Enum disallows value property
+--FILE--
+<?php
+
+enum Foo: int {
+ public int $value;
+}
+
+?>
+--EXPECTF--
+Fatal error: Enums may not include properties in %s on line %d
diff --git a/Zend/tests/enum/no-write-properties-through-foreach-reference.phpt b/Zend/tests/enum/no-write-properties-through-foreach-reference.phpt
new file mode 100644
index 0000000000..d1c211802f
--- /dev/null
+++ b/Zend/tests/enum/no-write-properties-through-foreach-reference.phpt
@@ -0,0 +1,22 @@
+--TEST--
+Enum properties cannot be written to through reference in foreach
+--FILE--
+<?php
+
+enum Foo: int {
+ case Bar = 0;
+}
+
+try {
+ $bar = Foo::Bar;
+ foreach ([1] as &$bar->value) {}
+} catch (Error $e) {
+ echo $e->getMessage() . "\n";
+}
+
+var_dump(Foo::Bar->value);
+
+?>
+--EXPECT--
+Cannot acquire reference to property Foo::$value
+int(0)
diff --git a/Zend/tests/enum/no-write-properties-through-references.phpt b/Zend/tests/enum/no-write-properties-through-references.phpt
new file mode 100644
index 0000000000..81543af78d
--- /dev/null
+++ b/Zend/tests/enum/no-write-properties-through-references.phpt
@@ -0,0 +1,23 @@
+--TEST--
+Enum properties cannot be written to through references
+--FILE--
+<?php
+
+enum Foo: int {
+ case Bar = 0;
+}
+
+try {
+ $bar = Foo::Bar;
+ $value = &$bar->value;
+ $value = 1;
+} catch (Error $e) {
+ echo $e->getMessage() . "\n";
+}
+
+var_dump(Foo::Bar->value);
+
+?>
+--EXPECT--
+Cannot acquire reference to property Foo::$value
+int(0)
diff --git a/Zend/tests/enum/no-write-properties.phpt b/Zend/tests/enum/no-write-properties.phpt
new file mode 100644
index 0000000000..13cbe69e74
--- /dev/null
+++ b/Zend/tests/enum/no-write-properties.phpt
@@ -0,0 +1,37 @@
+--TEST--
+Enum properties cannot be written to
+--FILE--
+<?php
+
+enum Foo {
+ case Bar;
+}
+
+enum IntFoo: int {
+ case Bar = 0;
+}
+
+$bar = Foo::Bar;
+try {
+ $bar->name = 'Baz';
+} catch (Error $e) {
+ echo $e->getMessage() . "\n";
+}
+
+$intBar = Foo::Bar;
+try {
+ $intBar->name = 'Baz';
+} catch (Error $e) {
+ echo $e->getMessage() . "\n";
+}
+try {
+ $intBar->value = 1;
+} catch (Error $e) {
+ echo $e->getMessage() . "\n";
+}
+
+?>
+--EXPECT--
+Enum properties are immutable
+Enum properties are immutable
+Enum properties are immutable
diff --git a/Zend/tests/enum/non-backed-enum-with-expr-value.phpt b/Zend/tests/enum/non-backed-enum-with-expr-value.phpt
new file mode 100644
index 0000000000..95bd85f396
--- /dev/null
+++ b/Zend/tests/enum/non-backed-enum-with-expr-value.phpt
@@ -0,0 +1,12 @@
+--TEST--
+Non-backed enum errors when case has int expression value
+--FILE--
+<?php
+
+enum Foo {
+ case Bar = 1 + 1;
+}
+
+?>
+--EXPECTF--
+Fatal error: Case Bar of non-backed enum Foo must not have a value, try adding ": int" to the enum declaration in %s on line %d
diff --git a/Zend/tests/enum/non-backed-enum-with-int-value.phpt b/Zend/tests/enum/non-backed-enum-with-int-value.phpt
new file mode 100644
index 0000000000..4932e63d18
--- /dev/null
+++ b/Zend/tests/enum/non-backed-enum-with-int-value.phpt
@@ -0,0 +1,12 @@
+--TEST--
+Non-backed enum errors when case has int value
+--FILE--
+<?php
+
+enum Foo {
+ case Bar = 1;
+}
+
+?>
+--EXPECTF--
+Fatal error: Case Bar of non-backed enum Foo must not have a value, try adding ": int" to the enum declaration in %s on line %d
diff --git a/Zend/tests/enum/non-backed-enum-with-invalid-value.phpt b/Zend/tests/enum/non-backed-enum-with-invalid-value.phpt
new file mode 100644
index 0000000000..24b8277805
--- /dev/null
+++ b/Zend/tests/enum/non-backed-enum-with-invalid-value.phpt
@@ -0,0 +1,12 @@
+--TEST--
+Non-backed enum errors when case has invalid value
+--FILE--
+<?php
+
+enum Foo {
+ case Bar = 3.141;
+}
+
+?>
+--EXPECTF--
+Fatal error: Case Bar of non-backed enum Foo must not have a value in %s on line %d
diff --git a/Zend/tests/enum/non-backed-enum-with-string-value.phpt b/Zend/tests/enum/non-backed-enum-with-string-value.phpt
new file mode 100644
index 0000000000..f1a06e1a17
--- /dev/null
+++ b/Zend/tests/enum/non-backed-enum-with-string-value.phpt
@@ -0,0 +1,12 @@
+--TEST--
+Non-backed enum errors when case has string value
+--FILE--
+<?php
+
+enum Foo {
+ case Bar = 'Bar';
+}
+
+?>
+--EXPECTF--
+Fatal error: Case Bar of non-backed enum Foo must not have a value, try adding ": string" to the enum declaration in %s on line %d
diff --git a/Zend/tests/enum/offsetGet-in-const-expr.phpt b/Zend/tests/enum/offsetGet-in-const-expr.phpt
new file mode 100644
index 0000000000..e288b9a694
--- /dev/null
+++ b/Zend/tests/enum/offsetGet-in-const-expr.phpt
@@ -0,0 +1,29 @@
+--TEST--
+Enum offsetGet in constant expression
+--FILE--
+<?php
+
+enum Foo implements ArrayAccess {
+ case Bar;
+
+ public function offsetGet($key) {
+ return 42;
+ }
+
+ public function offsetExists($key) {}
+ public function offsetSet($key, $value) {}
+ public function offsetUnset($key) {}
+}
+
+class X {
+ const FOO_BAR = Foo::Bar[0];
+}
+
+var_dump(X::FOO_BAR);
+
+?>
+--EXPECTF--
+Fatal error: Uncaught Error: Cannot use [] on objects in constant expression in %s:%d
+Stack trace:
+#0 {main}
+ thrown in %s on line %d
diff --git a/Zend/tests/enum/print_r.phpt b/Zend/tests/enum/print_r.phpt
new file mode 100644
index 0000000000..9cb08fec73
--- /dev/null
+++ b/Zend/tests/enum/print_r.phpt
@@ -0,0 +1,37 @@
+--TEST--
+Enum print_r
+--FILE--
+<?php
+
+enum Foo {
+ case Bar;
+}
+
+enum IntFoo: int {
+ case Bar = 42;
+}
+
+enum StringFoo: string {
+ case Bar = 'Bar';
+}
+
+print_r(Foo::Bar);
+print_r(IntFoo::Bar);
+print_r(StringFoo::Bar);
+
+?>
+--EXPECT--
+Foo Enum
+(
+ [name] => Bar
+)
+IntFoo Enum:int
+(
+ [name] => Bar
+ [value] => 42
+)
+StringFoo Enum:string
+(
+ [name] => Bar
+ [value] => Bar
+)
diff --git a/Zend/tests/enum/reflectionclass.phpt b/Zend/tests/enum/reflectionclass.phpt
new file mode 100644
index 0000000000..59b99b3e6d
--- /dev/null
+++ b/Zend/tests/enum/reflectionclass.phpt
@@ -0,0 +1,20 @@
+--TEST--
+Enum reflection getConstants()
+--FILE--
+<?php
+
+enum Foo {
+ case Bar;
+ case Baz;
+}
+
+var_dump((new \ReflectionClass(Foo::class))->getConstants());
+
+?>
+--EXPECT--
+array(2) {
+ ["Bar"]=>
+ enum(Foo::Bar)
+ ["Baz"]=>
+ enum(Foo::Baz)
+}
diff --git a/Zend/tests/enum/serialization-round-trip.phpt b/Zend/tests/enum/serialization-round-trip.phpt
new file mode 100644
index 0000000000..d69cbea71f
--- /dev/null
+++ b/Zend/tests/enum/serialization-round-trip.phpt
@@ -0,0 +1,14 @@
+--TEST--
+Enum unserialize same instance
+--FILE--
+<?php
+
+enum Foo {
+ case Bar;
+}
+
+var_dump(Foo::Bar === unserialize(serialize(Foo::Bar)));
+
+?>
+--EXPECT--
+bool(true)
diff --git a/Zend/tests/enum/serialize.phpt b/Zend/tests/enum/serialize.phpt
new file mode 100644
index 0000000000..56545ebfae
--- /dev/null
+++ b/Zend/tests/enum/serialize.phpt
@@ -0,0 +1,14 @@
+--TEST--
+Enum serialize
+--FILE--
+<?php
+
+enum Foo {
+ case Bar;
+}
+
+echo serialize(Foo::Bar);
+
+?>
+--EXPECT--
+E:7:"Foo:Bar";
diff --git a/Zend/tests/enum/spl-object-storage.phpt b/Zend/tests/enum/spl-object-storage.phpt
new file mode 100644
index 0000000000..7c72299c34
--- /dev/null
+++ b/Zend/tests/enum/spl-object-storage.phpt
@@ -0,0 +1,37 @@
+--TEST--
+Enum in SplObjectStorage
+--FILE--
+<?php
+
+enum Foo {
+ case Bar;
+ case Baz;
+ case Qux;
+}
+
+$storage = new SplObjectStorage();
+$storage[Foo::Bar] = 'Bar';
+$storage[Foo::Baz] = 'Baz';
+
+var_dump($storage[Foo::Bar]);
+var_dump($storage[Foo::Baz]);
+
+var_dump($storage->contains(Foo::Bar));
+var_dump($storage->contains(Foo::Qux));
+
+$serialized = serialize($storage);
+var_dump($serialized);
+
+$unserialized = unserialize($serialized);
+var_dump($unserialized[Foo::Bar]);
+var_dump($unserialized[Foo::Baz]);
+
+?>
+--EXPECT--
+string(3) "Bar"
+string(3) "Baz"
+bool(true)
+bool(false)
+string(112) "O:16:"SplObjectStorage":2:{i:0;a:4:{i:0;E:7:"Foo:Bar";i:1;s:3:"Bar";i:2;E:7:"Foo:Baz";i:3;s:3:"Baz";}i:1;a:0:{}}"
+string(3) "Bar"
+string(3) "Baz"
diff --git a/Zend/tests/enum/static-methods.phpt b/Zend/tests/enum/static-methods.phpt
new file mode 100644
index 0000000000..361ee77664
--- /dev/null
+++ b/Zend/tests/enum/static-methods.phpt
@@ -0,0 +1,28 @@
+--TEST--
+Enum supports static methods
+--FILE--
+<?php
+
+enum Size {
+ case Small;
+ case Medium;
+ case Large;
+
+ public static function fromLength(int $cm) {
+ return match(true) {
+ $cm < 50 => static::Small,
+ $cm < 100 => static::Medium,
+ default => static::Large,
+ };
+ }
+}
+
+var_dump(Size::fromLength(23));
+var_dump(Size::fromLength(63));
+var_dump(Size::fromLength(123));
+
+?>
+--EXPECT--
+enum(Size::Small)
+enum(Size::Medium)
+enum(Size::Large)
diff --git a/Zend/tests/enum/traits-no-__construct.phpt b/Zend/tests/enum/traits-no-__construct.phpt
new file mode 100644
index 0000000000..0529a2a989
--- /dev/null
+++ b/Zend/tests/enum/traits-no-__construct.phpt
@@ -0,0 +1,21 @@
+--TEST--
+Enum traits no __construct
+--FILE--
+<?php
+
+trait Foo {
+ public function __construct() {
+ echo "Evil code\n";
+ }
+}
+
+enum Bar {
+ use Foo;
+ case Baz;
+}
+
+var_dump(Bar::Baz);
+
+?>
+--EXPECTF--
+Fatal error: Enum may not include __construct in %s on line %d
diff --git a/Zend/tests/enum/traits-no-cases-method.phpt b/Zend/tests/enum/traits-no-cases-method.phpt
new file mode 100644
index 0000000000..9acfb52d0e
--- /dev/null
+++ b/Zend/tests/enum/traits-no-cases-method.phpt
@@ -0,0 +1,34 @@
+--TEST--
+Using cases method from traits in enums has no effect
+--FILE--
+<?php
+
+trait Rectangle {
+ public static function cases(): array {
+ return [];
+ }
+}
+
+enum Suit {
+ use Rectangle;
+
+ case Hearts;
+ case Diamonds;
+ case Clubs;
+ case Spades;
+}
+
+var_dump(Suit::cases());
+
+?>
+--EXPECT--
+array(4) {
+ [0]=>
+ enum(Suit::Hearts)
+ [1]=>
+ enum(Suit::Diamonds)
+ [2]=>
+ enum(Suit::Clubs)
+ [3]=>
+ enum(Suit::Spades)
+}
diff --git a/Zend/tests/enum/traits-no-forbidden-methods.phpt b/Zend/tests/enum/traits-no-forbidden-methods.phpt
new file mode 100644
index 0000000000..1f310f48cd
--- /dev/null
+++ b/Zend/tests/enum/traits-no-forbidden-methods.phpt
@@ -0,0 +1,21 @@
+--TEST--
+Enum cannot have forbidden methods, even via traits
+--FILE--
+<?php
+
+trait Rectangle {
+ public function __construct() {}
+}
+
+enum Suit {
+ use Rectangle;
+
+ case Hearts;
+ case Diamonds;
+ case Clubs;
+ case Spades;
+}
+
+?>
+--EXPECTF--
+Fatal error: Enum may not include __construct in %s on line %d
diff --git a/Zend/tests/enum/traits-no-properties.phpt b/Zend/tests/enum/traits-no-properties.phpt
new file mode 100644
index 0000000000..5a6faf9dea
--- /dev/null
+++ b/Zend/tests/enum/traits-no-properties.phpt
@@ -0,0 +1,25 @@
+--TEST--
+Enum cannot have properties, even via traits
+--FILE--
+<?php
+
+trait Rectangle {
+ protected string $shape = "Rectangle";
+
+ public function shape(): string {
+ return $this->shape;
+ }
+}
+
+enum Suit {
+ use Rectangle;
+
+ case Hearts;
+ case Diamonds;
+ case Clubs;
+ case Spades;
+}
+
+?>
+--EXPECTF--
+Fatal error: Enum "Suit" may not include properties in %s on line %d
diff --git a/Zend/tests/enum/traits.phpt b/Zend/tests/enum/traits.phpt
new file mode 100644
index 0000000000..3de8e1559f
--- /dev/null
+++ b/Zend/tests/enum/traits.phpt
@@ -0,0 +1,31 @@
+--TEST--
+Enum can use traits
+--FILE--
+<?php
+
+trait Rectangle {
+ public function shape(): string {
+ return "Rectangle";
+ }
+}
+
+enum Suit {
+ use Rectangle;
+
+ case Hearts;
+ case Diamonds;
+ case Clubs;
+ case Spades;
+}
+
+echo Suit::Hearts->shape() . PHP_EOL;
+echo Suit::Diamonds->shape() . PHP_EOL;
+echo Suit::Clubs->shape() . PHP_EOL;
+echo Suit::Spades->shape() . PHP_EOL;
+
+?>
+--EXPECT--
+Rectangle
+Rectangle
+Rectangle
+Rectangle
diff --git a/Zend/tests/enum/unit-cases.phpt b/Zend/tests/enum/unit-cases.phpt
new file mode 100644
index 0000000000..83f2232159
--- /dev/null
+++ b/Zend/tests/enum/unit-cases.phpt
@@ -0,0 +1,28 @@
+--TEST--
+Unit enums can list cases
+--FILE--
+<?php
+
+enum Suit {
+ case Hearts;
+ case Diamonds;
+ case Clubs;
+ case Spades;
+ /** @deprecated Typo, use Suit::Hearts */
+ const Hearst = self::Hearts;
+}
+
+var_dump(Suit::cases());
+
+?>
+--EXPECT--
+array(4) {
+ [0]=>
+ enum(Suit::Hearts)
+ [1]=>
+ enum(Suit::Diamonds)
+ [2]=>
+ enum(Suit::Clubs)
+ [3]=>
+ enum(Suit::Spades)
+}
diff --git a/Zend/tests/enum/unserialize-const.phpt b/Zend/tests/enum/unserialize-const.phpt
new file mode 100644
index 0000000000..0731a68dcc
--- /dev/null
+++ b/Zend/tests/enum/unserialize-const.phpt
@@ -0,0 +1,18 @@
+--TEST--
+Enum unserialize const
+--FILE--
+<?php
+
+enum Foo {
+ case Bar;
+ const Baz = Foo::Bar;
+}
+
+var_dump(unserialize('E:7:"Foo:Baz";'));
+
+?>
+--EXPECTF--
+Warning: unserialize(): Foo::Baz is not an enum case in %s on line %d
+
+Notice: unserialize(): Error at offset 14 of 14 bytes in %s on line %d
+bool(false)
diff --git a/Zend/tests/enum/unserialize-missing-colon.phpt b/Zend/tests/enum/unserialize-missing-colon.phpt
new file mode 100644
index 0000000000..86cce32df4
--- /dev/null
+++ b/Zend/tests/enum/unserialize-missing-colon.phpt
@@ -0,0 +1,17 @@
+--TEST--
+Enum unserialize with missing colon
+--FILE--
+<?php
+
+enum Foo {
+ case Bar;
+}
+
+var_dump(unserialize('E:6:"FooBar";'));
+
+?>
+--EXPECTF--
+Warning: unserialize(): Invalid enum name 'FooBar' (missing colon) in %s on line %d
+
+Notice: unserialize(): Error at offset 0 of 13 bytes in %s on line %d
+bool(false)
diff --git a/Zend/tests/enum/unserialize-non-enum.phpt b/Zend/tests/enum/unserialize-non-enum.phpt
new file mode 100644
index 0000000000..82f4ec93db
--- /dev/null
+++ b/Zend/tests/enum/unserialize-non-enum.phpt
@@ -0,0 +1,15 @@
+--TEST--
+Enum unserialize non-enum
+--FILE--
+<?php
+
+class Foo {}
+
+var_dump(unserialize('E:7:"Foo:Bar";'));
+
+?>
+--EXPECTF--
+Warning: unserialize(): Class 'Foo' is not an enum in %s on line %d
+
+Notice: unserialize(): Error at offset 0 of 14 bytes in %s on line %d
+bool(false)
diff --git a/Zend/tests/enum/unserialize-non-existent-case.phpt b/Zend/tests/enum/unserialize-non-existent-case.phpt
new file mode 100644
index 0000000000..2364db5b15
--- /dev/null
+++ b/Zend/tests/enum/unserialize-non-existent-case.phpt
@@ -0,0 +1,17 @@
+--TEST--
+Enum unserialize non-existent case
+--FILE--
+<?php
+
+enum Foo {
+ case Bar;
+}
+
+var_dump(unserialize('E:7:"Foo:Baz";'));
+
+?>
+--EXPECTF--
+Warning: unserialize(): Undefined constant Foo::Baz in %s on line %d
+
+Notice: unserialize(): Error at offset 14 of 14 bytes in %s on line %d
+bool(false)
diff --git a/Zend/tests/enum/unserialize-refcount.phpt b/Zend/tests/enum/unserialize-refcount.phpt
new file mode 100644
index 0000000000..447b3af182
--- /dev/null
+++ b/Zend/tests/enum/unserialize-refcount.phpt
@@ -0,0 +1,45 @@
+--TEST--
+Enum unserialize refcount
+--FILE--
+<?php
+
+enum Foo {
+ case Bar;
+}
+
+debug_zval_dump(Foo::Bar);
+
+$foo = Foo::Bar;
+debug_zval_dump($foo);
+
+$bar = unserialize('E:7:"Foo:Bar";');
+debug_zval_dump($foo);
+
+unset($bar);
+debug_zval_dump($foo);
+
+unset($foo);
+debug_zval_dump(Foo::Bar);
+
+?>
+--EXPECT--
+object(Foo)#1 (1) refcount(2){
+ ["name"]=>
+ string(3) "Bar" interned
+}
+object(Foo)#1 (1) refcount(3){
+ ["name"]=>
+ string(3) "Bar" interned
+}
+object(Foo)#1 (1) refcount(4){
+ ["name"]=>
+ string(3) "Bar" interned
+}
+object(Foo)#1 (1) refcount(3){
+ ["name"]=>
+ string(3) "Bar" interned
+}
+object(Foo)#1 (1) refcount(2){
+ ["name"]=>
+ string(3) "Bar" interned
+}
diff --git a/Zend/tests/enum/unserialize.phpt b/Zend/tests/enum/unserialize.phpt
new file mode 100644
index 0000000000..c5a10a8fe5
--- /dev/null
+++ b/Zend/tests/enum/unserialize.phpt
@@ -0,0 +1,24 @@
+--TEST--
+Enum unserialize
+--FILE--
+<?php
+
+enum Foo {
+ case Bar;
+ case Quux;
+}
+
+$bar = unserialize('E:7:"Foo:Bar";');
+var_dump($bar);
+var_dump($bar === Foo::Bar);
+
+$quux = unserialize('E:8:"Foo:Quux";');
+var_dump($quux);
+var_dump($quux === Foo::Quux);
+
+?>
+--EXPECT--
+enum(Foo::Bar)
+bool(true)
+enum(Foo::Quux)
+bool(true)
diff --git a/Zend/tests/enum/value-property-type.phpt b/Zend/tests/enum/value-property-type.phpt
new file mode 100644
index 0000000000..ed009dd981
--- /dev/null
+++ b/Zend/tests/enum/value-property-type.phpt
@@ -0,0 +1,20 @@
+--TEST--
+Enum value property has automatic type
+--FILE--
+<?php
+
+enum IntEnum: int {
+ case Foo = 0;
+}
+
+enum StringEnum: string {
+ case Foo = 'Foo';
+}
+
+echo (new ReflectionProperty(IntEnum::class, 'value'))->getType() . "\n";
+echo (new ReflectionProperty(StringEnum::class, 'value'))->getType() . "\n";
+
+?>
+--EXPECT--
+int
+string
diff --git a/Zend/tests/enum/var_dump-nested.phpt b/Zend/tests/enum/var_dump-nested.phpt
new file mode 100644
index 0000000000..73a4e51c38
--- /dev/null
+++ b/Zend/tests/enum/var_dump-nested.phpt
@@ -0,0 +1,20 @@
+--TEST--
+Enum var_dump nested
+--FILE--
+<?php
+
+enum Foo {
+ case Bar;
+}
+
+var_dump([[Foo::Bar]]);
+
+?>
+--EXPECT--
+array(1) {
+ [0]=>
+ array(1) {
+ [0]=>
+ enum(Foo::Bar)
+ }
+}
diff --git a/Zend/tests/enum/var_dump-reference.phpt b/Zend/tests/enum/var_dump-reference.phpt
new file mode 100644
index 0000000000..fe04e7865c
--- /dev/null
+++ b/Zend/tests/enum/var_dump-reference.phpt
@@ -0,0 +1,22 @@
+--TEST--
+Enum var_dump reference
+--FILE--
+<?php
+
+enum Foo {
+ case Bar;
+ case Baz;
+}
+
+$arr = [Foo::Bar];
+$arr[1] = &$arr[0];
+var_dump($arr);
+
+?>
+--EXPECT--
+array(2) {
+ [0]=>
+ &enum(Foo::Bar)
+ [1]=>
+ &enum(Foo::Bar)
+}
diff --git a/Zend/tests/enum/var_export.phpt b/Zend/tests/enum/var_export.phpt
new file mode 100644
index 0000000000..430444d13d
--- /dev/null
+++ b/Zend/tests/enum/var_export.phpt
@@ -0,0 +1,14 @@
+--TEST--
+Enum var_export
+--FILE--
+<?php
+
+enum Foo {
+ case Bar;
+}
+
+var_export(Foo::Bar);
+
+?>
+--EXPECT--
+Foo::Bar
diff --git a/Zend/zend.c b/Zend/zend.c
index 822160508a..c76a7e6fba 100644
--- a/Zend/zend.c
+++ b/Zend/zend.c
@@ -457,11 +457,22 @@ static void zend_print_zval_r_to_buf(smart_str *buf, zval *expr, int indent) /*
{
HashTable *properties;
- zend_string *class_name = Z_OBJ_HANDLER_P(expr, get_class_name)(Z_OBJ_P(expr));
+ zend_object *zobj = Z_OBJ_P(expr);
+ zend_string *class_name = Z_OBJ_HANDLER_P(expr, get_class_name)(zobj);
smart_str_appends(buf, ZSTR_VAL(class_name));
zend_string_release_ex(class_name, 0);
- smart_str_appends(buf, " Object\n");
+ if (!(zobj->ce->ce_flags & ZEND_ACC_ENUM)) {
+ smart_str_appends(buf, " Object\n");
+ } else {
+ smart_str_appends(buf, " Enum");
+ if (zobj->ce->enum_backing_type != IS_UNDEF) {
+ smart_str_appendc(buf, ':');
+ smart_str_appends(buf, zend_get_type_by_const(zobj->ce->enum_backing_type));
+ }
+ smart_str_appendc(buf, '\n');
+ }
+
if (GC_IS_RECURSIVE(Z_OBJ_P(expr))) {
smart_str_appends(buf, " *RECURSION*");
return;
diff --git a/Zend/zend.h b/Zend/zend.h
index e09884941c..aed352cf8d 100644
--- a/Zend/zend.h
+++ b/Zend/zend.h
@@ -197,6 +197,9 @@ struct _zend_class_entry {
zend_trait_precedence **trait_precedences;
HashTable *attributes;
+ uint32_t enum_backing_type;
+ HashTable *backed_enum_table;
+
union {
struct {
zend_string *filename;
diff --git a/Zend/zend_API.c b/Zend/zend_API.c
index e532e9754a..ca6214ea1f 100644
--- a/Zend/zend_API.c
+++ b/Zend/zend_API.c
@@ -1583,11 +1583,13 @@ ZEND_API void object_properties_load(zend_object *object, HashTable *properties)
* calling zend_merge_properties(). */
static zend_always_inline zend_result _object_and_properties_init(zval *arg, zend_class_entry *class_type, HashTable *properties) /* {{{ */
{
- if (UNEXPECTED(class_type->ce_flags & (ZEND_ACC_INTERFACE|ZEND_ACC_TRAIT|ZEND_ACC_IMPLICIT_ABSTRACT_CLASS|ZEND_ACC_EXPLICIT_ABSTRACT_CLASS))) {
+ if (UNEXPECTED(class_type->ce_flags & (ZEND_ACC_INTERFACE|ZEND_ACC_TRAIT|ZEND_ACC_IMPLICIT_ABSTRACT_CLASS|ZEND_ACC_EXPLICIT_ABSTRACT_CLASS|ZEND_ACC_ENUM))) {
if (class_type->ce_flags & ZEND_ACC_INTERFACE) {
zend_throw_error(NULL, "Cannot instantiate interface %s", ZSTR_VAL(class_type->name));
} else if (class_type->ce_flags & ZEND_ACC_TRAIT) {
zend_throw_error(NULL, "Cannot instantiate trait %s", ZSTR_VAL(class_type->name));
+ } else if (class_type->ce_flags & ZEND_ACC_ENUM) {
+ zend_throw_error(NULL, "Cannot instantiate enum %s", ZSTR_VAL(class_type->name));
} else {
zend_throw_error(NULL, "Cannot instantiate abstract class %s", ZSTR_VAL(class_type->name));
}
diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c
index 73f2390c11..b8d3515155 100644
--- a/Zend/zend_ast.c
+++ b/Zend/zend_ast.c
@@ -24,6 +24,7 @@
#include "zend_smart_str.h"
#include "zend_exceptions.h"
#include "zend_constants.h"
+#include "zend_enum.h"
ZEND_API zend_ast_process_t zend_ast_process = NULL;
@@ -740,19 +741,49 @@ ZEND_API zend_result ZEND_FASTCALL zend_ast_evaluate(zval *result, zend_ast *ast
if (UNEXPECTED(zend_ast_evaluate(&op1, ast->child[0], scope) != SUCCESS)) {
ret = FAILURE;
- } else if (UNEXPECTED(zend_ast_evaluate(&op2, ast->child[1], scope) != SUCCESS)) {
+ break;
+ }
+
+ // DIM on objects is disallowed because it allows executing arbitrary expressions
+ if (Z_TYPE(op1) == IS_OBJECT) {
zval_ptr_dtor_nogc(&op1);
+ zend_throw_error(NULL, "Cannot use [] on objects in constant expression");
ret = FAILURE;
- } else {
- zend_fetch_dimension_const(result, &op1, &op2, (ast->attr & ZEND_DIM_IS) ? BP_VAR_IS : BP_VAR_R);
+ break;
+ }
+ if (UNEXPECTED(zend_ast_evaluate(&op2, ast->child[1], scope) != SUCCESS)) {
zval_ptr_dtor_nogc(&op1);
- zval_ptr_dtor_nogc(&op2);
- if (UNEXPECTED(EG(exception))) {
- return FAILURE;
- }
+ ret = FAILURE;
+ break;
}
+
+ zend_fetch_dimension_const(result, &op1, &op2, (ast->attr & ZEND_DIM_IS) ? BP_VAR_IS : BP_VAR_R);
+
+ zval_ptr_dtor_nogc(&op1);
+ zval_ptr_dtor_nogc(&op2);
+ if (UNEXPECTED(EG(exception))) {
+ return FAILURE;
+ }
+
break;
+ case ZEND_AST_CONST_ENUM_INIT:
+ {
+ zend_ast *class_name_ast = ast->child[0];
+ zend_string *class_name = zend_ast_get_str(class_name_ast);
+
+ zend_ast *case_name_ast = ast->child[1];
+ zend_string *case_name = zend_ast_get_str(case_name_ast);
+
+ zend_ast *case_value_ast = ast->child[2];
+ zval *case_value_zv = case_value_ast != NULL
+ ? zend_ast_get_zval(case_value_ast)
+ : NULL;
+
+ zend_class_entry *ce = zend_lookup_class(class_name);
+ zend_enum_new(result, ce, case_name, case_value_zv);
+ break;
+ }
default:
zend_throw_error(NULL, "Unsupported constant expression");
ret = FAILURE;
@@ -1558,6 +1589,8 @@ tail_call:
smart_str_appends(str, "interface ");
} else if (decl->flags & ZEND_ACC_TRAIT) {
smart_str_appends(str, "trait ");
+ } else if (decl->flags & ZEND_ACC_ENUM) {
+ smart_str_appends(str, "enum ");
} else {
if (decl->flags & ZEND_ACC_EXPLICIT_ABSTRACT_CLASS) {
smart_str_appends(str, "abstract ");
@@ -1568,6 +1601,10 @@ tail_call:
smart_str_appends(str, "class ");
}
smart_str_appendl(str, ZSTR_VAL(decl->name), ZSTR_LEN(decl->name));
+ if (decl->flags & ZEND_ACC_ENUM && decl->child[4]) {
+ smart_str_appends(str, ": ");
+ zend_ast_export_type(str, decl->child[4], indent);
+ }
zend_ast_export_class_no_header(str, decl, indent);
smart_str_appendc(str, '\n');
break;
@@ -2150,6 +2187,17 @@ simple_list:
smart_str_appendc(str, '$');
zend_ast_export_name(str, ast->child[1], 0, indent);
APPEND_DEFAULT_VALUE(2);
+ case ZEND_AST_ENUM_CASE:
+ if (ast->child[2]) {
+ zend_ast_export_attributes(str, ast->child[2], indent, 1);
+ }
+ smart_str_appends(str, "case ");
+ zend_ast_export_name(str, ast->child[0], 0, indent);
+ if (ast->child[1]) {
+ smart_str_appends(str, " = ");
+ zend_ast_export_ex(str, ast->child[1], 0, indent);
+ }
+ break;
/* 4 child nodes */
case ZEND_AST_FOR:
@@ -2273,6 +2321,9 @@ zend_ast * ZEND_FASTCALL zend_ast_with_attributes(zend_ast *ast, zend_ast *attr)
case ZEND_AST_CLASS_CONST_GROUP:
ast->child[1] = attr;
break;
+ case ZEND_AST_ENUM_CASE:
+ ast->child[2] = attr;
+ break;
EMPTY_SWITCH_DEFAULT_CASE()
}
diff --git a/Zend/zend_ast.h b/Zend/zend_ast.h
index 4d7335853e..fb6587b48c 100644
--- a/Zend/zend_ast.h
+++ b/Zend/zend_ast.h
@@ -159,6 +159,10 @@ enum _zend_ast_kind {
ZEND_AST_PROP_GROUP,
ZEND_AST_PROP_ELEM,
ZEND_AST_CONST_ELEM,
+ ZEND_AST_ENUM_CASE,
+
+ // Pseudo node for initializing enums
+ ZEND_AST_CONST_ENUM_INIT,
/* 4 child nodes */
ZEND_AST_FOR = 4 << ZEND_AST_NUM_CHILDREN_SHIFT,
diff --git a/Zend/zend_builtin_functions.c b/Zend/zend_builtin_functions.c
index 47c7c6ea31..4c720c53db 100644
--- a/Zend/zend_builtin_functions.c
+++ b/Zend/zend_builtin_functions.c
@@ -1039,6 +1039,11 @@ ZEND_FUNCTION(trait_exists)
}
/* }}} */
+ZEND_FUNCTION(enum_exists)
+{
+ class_exists_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_ACC_ENUM, 0);
+}
+
/* {{{ Checks if the function exists */
ZEND_FUNCTION(function_exists)
{
diff --git a/Zend/zend_builtin_functions.stub.php b/Zend/zend_builtin_functions.stub.php
index 87a530200d..9df6659f2d 100644
--- a/Zend/zend_builtin_functions.stub.php
+++ b/Zend/zend_builtin_functions.stub.php
@@ -63,6 +63,8 @@ function interface_exists(string $interface, bool $autoload = true): bool {}
function trait_exists(string $trait, bool $autoload = true): bool {}
+function enum_exists(string $enum, bool $autoload = true): bool {}
+
function function_exists(string $function): bool {}
function class_alias(string $class, string $alias, bool $autoload = true): bool {}
diff --git a/Zend/zend_builtin_functions_arginfo.h b/Zend/zend_builtin_functions_arginfo.h
index 6baaead27d..f707e0a8a3 100644
--- a/Zend/zend_builtin_functions_arginfo.h
+++ b/Zend/zend_builtin_functions_arginfo.h
@@ -1,5 +1,5 @@
/* This is a generated file, edit the .stub.php file instead.
- * Stub hash: 429fc9b22054348101d0b9d6746494e52dc04edf */
+ * Stub hash: c333499e3ea100d976f0fa8bb8800ed21b04f1c6 */
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_version, 0, 0, IS_STRING, 0)
ZEND_END_ARG_INFO()
@@ -108,6 +108,11 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_trait_exists, 0, 1, _IS_BOOL, 0)
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, autoload, _IS_BOOL, 0, "true")
ZEND_END_ARG_INFO()
+ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_enum_exists, 0, 1, _IS_BOOL, 0)
+ ZEND_ARG_TYPE_INFO(0, enum, IS_STRING, 0)
+ ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, autoload, _IS_BOOL, 0, "true")
+ZEND_END_ARG_INFO()
+
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_function_exists, 0, 1, _IS_BOOL, 0)
ZEND_ARG_TYPE_INFO(0, function, IS_STRING, 0)
ZEND_END_ARG_INFO()
@@ -238,6 +243,7 @@ ZEND_FUNCTION(property_exists);
ZEND_FUNCTION(class_exists);
ZEND_FUNCTION(interface_exists);
ZEND_FUNCTION(trait_exists);
+ZEND_FUNCTION(enum_exists);
ZEND_FUNCTION(function_exists);
ZEND_FUNCTION(class_alias);
ZEND_FUNCTION(get_included_files);
@@ -298,6 +304,7 @@ static const zend_function_entry ext_functions[] = {
ZEND_FE(class_exists, arginfo_class_exists)
ZEND_FE(interface_exists, arginfo_interface_exists)
ZEND_FE(trait_exists, arginfo_trait_exists)
+ ZEND_FE(enum_exists, arginfo_enum_exists)
ZEND_FE(function_exists, arginfo_function_exists)
ZEND_FE(class_alias, arginfo_class_alias)
ZEND_FE(get_included_files, arginfo_get_included_files)
diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c
index 3e0eae5f57..76c2ed9644 100644
--- a/Zend/zend_compile.c
+++ b/Zend/zend_compile.c
@@ -32,6 +32,7 @@
#include "zend_language_scanner.h"
#include "zend_inheritance.h"
#include "zend_vm.h"
+#include "zend_enum.h"
#define SET_NODE(target, src) do { \
target ## _type = (src)->op_type; \
@@ -1873,6 +1874,8 @@ ZEND_API void zend_initialize_class_data(zend_class_entry *ce, bool nullify_hand
ce->default_static_members_count = 0;
ce->properties_info_table = NULL;
ce->attributes = NULL;
+ ce->enum_backing_type = IS_UNDEF;
+ ce->backed_enum_table = NULL;
if (nullify_handlers) {
ce->constructor = NULL;
@@ -7131,7 +7134,11 @@ void zend_compile_prop_decl(zend_ast *ast, zend_ast *type_ast, uint32_t flags, z
uint32_t i, children = list->children;
if (ce->ce_flags & ZEND_ACC_INTERFACE) {
- zend_error_noreturn(E_COMPILE_ERROR, "Interfaces may not include member variables");
+ zend_error_noreturn(E_COMPILE_ERROR, "Interfaces may not include properties");
+ }
+
+ if (ce->ce_flags & ZEND_ACC_ENUM) {
+ zend_error_noreturn(E_COMPILE_ERROR, "Enums may not include properties");
}
if (flags & ZEND_ACC_ABSTRACT) {
@@ -7422,12 +7429,36 @@ static zend_string *zend_generate_anon_class_name(zend_ast_decl *decl)
return zend_new_interned_string(result);
}
+static void zend_compile_enum_backing_type(zend_class_entry *ce, zend_ast *enum_backing_type_ast)
+{
+ ZEND_ASSERT(ce->ce_flags & ZEND_ACC_ENUM);
+ zend_type type = zend_compile_typename(enum_backing_type_ast, 0);
+ uint32_t type_mask = ZEND_TYPE_PURE_MASK(type);
+ if (ZEND_TYPE_HAS_CLASS(type) || (type_mask != MAY_BE_LONG && type_mask != MAY_BE_STRING)) {
+ zend_string *type_string = zend_type_to_string(type);
+ zend_error_noreturn(E_COMPILE_ERROR,
+ "Enum backing type must be int or string, %s given",
+ ZSTR_VAL(type_string));
+ }
+ if (type_mask == MAY_BE_LONG) {
+ ce->enum_backing_type = IS_LONG;
+ } else {
+ ZEND_ASSERT(type_mask == MAY_BE_STRING);
+ ce->enum_backing_type = IS_STRING;
+ }
+ zend_type_release(type, 0);
+
+ ce->backed_enum_table = emalloc(sizeof(HashTable));
+ zend_hash_init(ce->backed_enum_table, 0, NULL, ZVAL_PTR_DTOR, 0);
+}
+
void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel) /* {{{ */
{
zend_ast_decl *decl = (zend_ast_decl *) ast;
zend_ast *extends_ast = decl->child[0];
zend_ast *implements_ast = decl->child[1];
zend_ast *stmt_ast = decl->child[2];
+ zend_ast *enum_backing_type_ast = decl->child[4];
zend_string *name, *lcname;
zend_class_entry *ce = zend_arena_alloc(&CG(arena), sizeof(zend_class_entry));
zend_op *opline;
@@ -7508,6 +7539,14 @@ void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel) /* {{{
zend_compile_implements(implements_ast);
}
+ if (ce->ce_flags & ZEND_ACC_ENUM) {
+ if (enum_backing_type_ast != NULL) {
+ zend_compile_enum_backing_type(ce, enum_backing_type_ast);
+ }
+ zend_enum_add_interfaces(ce);
+ zend_enum_register_props(ce);
+ }
+
zend_compile_stmt(stmt_ast);
/* Reset lineno for final opcodes and errors */
@@ -7604,6 +7643,99 @@ void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel) /* {{{
}
/* }}} */
+static void zend_compile_enum_case(zend_ast *ast)
+{
+ zend_class_entry *enum_class = CG(active_class_entry);
+ if (!(enum_class->ce_flags & ZEND_ACC_ENUM)) {
+ zend_error_noreturn(E_COMPILE_ERROR, "Case can only be used in enums");
+ }
+
+ zend_string *enum_case_name = zval_make_interned_string(zend_ast_get_zval(ast->child[0]));
+ zend_string *enum_class_name = enum_class->name;
+
+ zval class_name_zval;
+ ZVAL_STR_COPY(&class_name_zval, enum_class_name);
+ zend_ast *class_name_ast = zend_ast_create_zval(&class_name_zval);
+
+ zval case_name_zval;
+ ZVAL_STR_COPY(&case_name_zval, enum_case_name);
+ zend_ast *case_name_ast = zend_ast_create_zval(&case_name_zval);
+
+ zend_ast *case_value_zval_ast = NULL;
+ zend_ast *case_value_ast = ast->child[1];
+ if (enum_class->enum_backing_type != IS_UNDEF && case_value_ast == NULL) {
+ zend_error_noreturn(E_COMPILE_ERROR, "Case %s of backed enum %s must have a value",
+ ZSTR_VAL(enum_case_name),
+ ZSTR_VAL(enum_class_name));
+ }
+ if (case_value_ast != NULL) {
+ zend_eval_const_expr(&ast->child[1]);
+ case_value_ast = ast->child[1];
+ if (case_value_ast->kind != ZEND_AST_ZVAL) {
+ zend_error_noreturn(E_COMPILE_ERROR, "Enum case value must be constant");
+ }
+
+ zval case_value_zv;
+ ZVAL_COPY(&case_value_zv, zend_ast_get_zval(case_value_ast));
+ if (enum_class->enum_backing_type == IS_UNDEF) {
+ if (Z_TYPE(case_value_zv) == IS_LONG || Z_TYPE(case_value_zv) == IS_STRING) {
+ zend_error_noreturn(E_COMPILE_ERROR, "Case %s of non-backed enum %s must not have a value, try adding \": %s\" to the enum declaration",
+ ZSTR_VAL(enum_case_name),
+ ZSTR_VAL(enum_class_name),
+ zend_zval_type_name(&case_value_zv));
+ } else {
+ zend_error_noreturn(E_COMPILE_ERROR, "Case %s of non-backed enum %s must not have a value",
+ ZSTR_VAL(enum_case_name),
+ ZSTR_VAL(enum_class_name));
+ }
+ }
+
+ if (enum_class->enum_backing_type != Z_TYPE(case_value_zv)) {
+ zend_error_noreturn(E_COMPILE_ERROR, "Enum case type %s does not match enum backing type %s",
+ zend_get_type_by_const(Z_TYPE(case_value_zv)),
+ zend_get_type_by_const(enum_class->enum_backing_type));
+ }
+
+ case_value_zval_ast = zend_ast_create_zval(&case_value_zv);
+ Z_TRY_ADDREF(case_name_zval);
+ if (enum_class->enum_backing_type == IS_LONG) {
+ zend_long long_key = Z_LVAL(case_value_zv);
+ zval *existing_case_name = zend_hash_index_find(enum_class->backed_enum_table, long_key);
+ if (existing_case_name) {
+ zend_error_noreturn(E_COMPILE_ERROR, "Duplicate value in enum %s for cases %s and %s",
+ ZSTR_VAL(enum_class_name),
+ Z_STRVAL_P(existing_case_name),
+ ZSTR_VAL(enum_case_name));
+ }
+ zend_hash_index_add_new(enum_class->backed_enum_table, long_key, &case_name_zval);
+ } else {
+ ZEND_ASSERT(enum_class->enum_backing_type == IS_STRING);
+ zend_string *string_key = Z_STR(case_value_zv);
+ zval *existing_case_name = zend_hash_find(enum_class->backed_enum_table, string_key);
+ if (existing_case_name != NULL) {
+ zend_error_noreturn(E_COMPILE_ERROR, "Duplicate value in enum %s for cases %s and %s",
+ ZSTR_VAL(enum_class_name),
+ Z_STRVAL_P(existing_case_name),
+ ZSTR_VAL(enum_case_name));
+ }
+ zend_hash_add_new(enum_class->backed_enum_table, string_key, &case_name_zval);
+ }
+ }
+
+ zend_ast *const_enum_init_ast = zend_ast_create(ZEND_AST_CONST_ENUM_INIT, class_name_ast, case_name_ast, case_value_zval_ast);
+
+ zval value_zv;
+ zend_const_expr_to_zval(&value_zv, &const_enum_init_ast);
+ zend_class_constant *c = zend_declare_class_constant_ex(enum_class, enum_case_name, &value_zv, ZEND_ACC_PUBLIC, NULL);
+ Z_ACCESS_FLAGS(c->value) |= ZEND_CLASS_CONST_IS_CASE;
+ zend_ast_destroy(const_enum_init_ast);
+
+ zend_ast *attr_ast = ast->child[2];
+ if (attr_ast) {
+ zend_compile_attributes(&c->attributes, attr_ast, 0, ZEND_ATTRIBUTE_TARGET_CLASS_CONST);
+ }
+}
+
static HashTable *zend_get_import_ht(uint32_t type) /* {{{ */
{
switch (type) {
@@ -9317,7 +9449,8 @@ bool zend_is_allowed_in_const_expr(zend_ast_kind kind) /* {{{ */
|| kind == ZEND_AST_UNPACK
|| kind == ZEND_AST_CONST || kind == ZEND_AST_CLASS_CONST
|| kind == ZEND_AST_CLASS_NAME
- || kind == ZEND_AST_MAGIC_CONST || kind == ZEND_AST_COALESCE;
+ || kind == ZEND_AST_MAGIC_CONST || kind == ZEND_AST_COALESCE
+ || kind == ZEND_AST_CONST_ENUM_INIT;
}
/* }}} */
@@ -9572,6 +9705,9 @@ void zend_compile_stmt(zend_ast *ast) /* {{{ */
case ZEND_AST_METHOD:
zend_compile_func_decl(NULL, ast, 0);
break;
+ case ZEND_AST_ENUM_CASE:
+ zend_compile_enum_case(ast);
+ break;
case ZEND_AST_PROP_GROUP:
zend_compile_prop_group(ast);
break;
diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h
index 6f64624fd8..9e22837d61 100644
--- a/Zend/zend_compile.h
+++ b/Zend/zend_compile.h
@@ -233,13 +233,19 @@ typedef struct _zend_oparray_context {
/* op_array or class is preloaded | | | */
#define ZEND_ACC_PRELOADED (1 << 10) /* X | X | | */
/* | | | */
-/* Class Flags (unused: 28...) | | | */
+/* Flag to differenciate cases from constants. | | | */
+/* Stored in Z_ACCESS_FLAGS, must not conflict with | | | */
+/* ZEND_ACC_ visibility flags or IS_CONSTANT_VISITED_MARK | | | */
+#define ZEND_CLASS_CONST_IS_CASE (1 << 6) /* | | | X */
+/* | | | */
+/* Class Flags (unused: 29...) | | | */
/* =========== | | | */
/* | | | */
/* Special class types | | | */
#define ZEND_ACC_INTERFACE (1 << 0) /* X | | | */
#define ZEND_ACC_TRAIT (1 << 1) /* X | | | */
#define ZEND_ACC_ANON_CLASS (1 << 2) /* X | | | */
+#define ZEND_ACC_ENUM (1 << 28) /* X | | | */
/* | | | */
/* Class linked with parent, interfaces and traits | | | */
#define ZEND_ACC_LINKED (1 << 3) /* X | | | */
@@ -387,7 +393,7 @@ typedef struct _zend_property_info {
((offset - OBJ_PROP_TO_OFFSET(0)) / sizeof(zval))
typedef struct _zend_class_constant {
- zval value; /* access flags are stored in reserved: zval.u2.access_flags */
+ zval value; /* access flags and other constant flags are stored in reserved: zval.u2.access_flags */
zend_string *doc_comment;
HashTable *attributes;
zend_class_entry *ce;
diff --git a/Zend/zend_default_classes.c b/Zend/zend_default_classes.c
index 63cdf66d58..c0fa6f5a67 100644
--- a/Zend/zend_default_classes.c
+++ b/Zend/zend_default_classes.c
@@ -26,6 +26,7 @@
#include "zend_closures.h"
#include "zend_generators.h"
#include "zend_weakrefs.h"
+#include "zend_enum.h"
ZEND_API void zend_register_default_classes(void)
{
@@ -36,4 +37,5 @@ ZEND_API void zend_register_default_classes(void)
zend_register_generator_ce();
zend_register_weakref_ce();
zend_register_attribute_ce();
+ zend_register_enum_ce();
}
diff --git a/Zend/zend_enum.c b/Zend/zend_enum.c
new file mode 100644
index 0000000000..728da8af36
--- /dev/null
+++ b/Zend/zend_enum.c
@@ -0,0 +1,373 @@
+/*
+ +----------------------------------------------------------------------+
+ | Zend Engine |
+ +----------------------------------------------------------------------+
+ | Copyright (c) Zend Technologies Ltd. (http://www.zend.com) |
+ +----------------------------------------------------------------------+
+ | This source file is subject to version 2.00 of the Zend license, |
+ | that is bundled with this package in the file LICENSE, and is |
+ | available through the world-wide-web at the following url: |
+ | http://www.zend.com/license/2_00.txt. |
+ | If you did not receive a copy of the Zend license and are unable to |
+ | obtain it through the world-wide-web, please send a note to |
+ | license@zend.com so we can mail you a copy immediately. |
+ +----------------------------------------------------------------------+
+ | Authors: Ilija Tovilo <ilutov@php.net> |
+ +----------------------------------------------------------------------+
+*/
+
+#include "zend.h"
+#include "zend_API.h"
+#include "zend_compile.h"
+#include "zend_enum_arginfo.h"
+#include "zend_interfaces.h"
+
+#define ZEND_ENUM_PROPERTY_ERROR() \
+ zend_throw_error(NULL, "Enum properties are immutable")
+
+#define ZEND_ENUM_DISALLOW_MAGIC_METHOD(propertyName, methodName) \
+ do { \
+ if (ce->propertyName) { \
+ zend_error_noreturn(E_COMPILE_ERROR, "Enum may not include %s", methodName); \
+ } \
+ } while (0);
+
+ZEND_API zend_class_entry *zend_ce_unit_enum;
+ZEND_API zend_class_entry *zend_ce_backed_enum;
+
+static zend_object_handlers enum_handlers;
+
+zend_object *zend_enum_new(zval *result, zend_class_entry *ce, zend_string *case_name, zval *backing_value_zv)
+{
+ zend_object *zobj = zend_objects_new(ce);
+ ZVAL_OBJ(result, zobj);
+
+ ZVAL_STR_COPY(OBJ_PROP_NUM(zobj, 0), case_name);
+ if (backing_value_zv != NULL) {
+ ZVAL_COPY(OBJ_PROP_NUM(zobj, 1), backing_value_zv);
+ }
+
+ zobj->handlers = &enum_handlers;
+
+ return zobj;
+}
+
+static void zend_verify_enum_properties(zend_class_entry *ce)
+{
+ zend_property_info *property_info;
+
+ ZEND_HASH_FOREACH_PTR(&ce->properties_info, property_info) {
+ if (zend_string_equals_literal(property_info->name, "name")) {
+ continue;
+ }
+ if (
+ ce->enum_backing_type != IS_UNDEF
+ && zend_string_equals_literal(property_info->name, "value")
+ ) {
+ continue;
+ }
+ // FIXME: File/line number for traits?
+ zend_error_noreturn(E_COMPILE_ERROR, "Enum \"%s\" may not include properties",
+ ZSTR_VAL(ce->name));
+ } ZEND_HASH_FOREACH_END();
+}
+
+static void zend_verify_enum_magic_methods(zend_class_entry *ce)
+{
+ // Only __get, __call and __invoke are allowed
+
+ ZEND_ENUM_DISALLOW_MAGIC_METHOD(constructor, "__construct");
+ ZEND_ENUM_DISALLOW_MAGIC_METHOD(destructor, "__destruct");
+ ZEND_ENUM_DISALLOW_MAGIC_METHOD(clone, "__clone");
+ ZEND_ENUM_DISALLOW_MAGIC_METHOD(__get, "__get");
+ ZEND_ENUM_DISALLOW_MAGIC_METHOD(__set, "__set");
+ ZEND_ENUM_DISALLOW_MAGIC_METHOD(__unset, "__unset");
+ ZEND_ENUM_DISALLOW_MAGIC_METHOD(__isset, "__isset");
+ ZEND_ENUM_DISALLOW_MAGIC_METHOD(__tostring, "__toString");
+ ZEND_ENUM_DISALLOW_MAGIC_METHOD(__debugInfo, "__debugInfo");
+ ZEND_ENUM_DISALLOW_MAGIC_METHOD(__serialize, "__serialize");
+ ZEND_ENUM_DISALLOW_MAGIC_METHOD(__unserialize, "__unserialize");
+
+ const char *forbidden_methods[] = {
+ "__sleep",
+ "__wakeup",
+ "__set_state",
+ };
+
+ uint32_t forbidden_methods_length = sizeof(forbidden_methods) / sizeof(forbidden_methods[0]);
+ for (uint32_t i = 0; i < forbidden_methods_length; ++i) {
+ const char *forbidden_method = forbidden_methods[i];
+
+ if (zend_hash_str_exists(&ce->function_table, forbidden_method, strlen(forbidden_method))) {
+ zend_error_noreturn(E_COMPILE_ERROR, "Enum may not include magic method %s", forbidden_method);
+ }
+ }
+}
+
+static void zend_verify_enum_interfaces(zend_class_entry *ce)
+{
+ if (zend_class_implements_interface(ce, zend_ce_serializable)) {
+ zend_error_noreturn(E_COMPILE_ERROR,
+ "Enums may not implement the Serializable interface");
+ }
+}
+
+void zend_verify_enum(zend_class_entry *ce)
+{
+ zend_verify_enum_properties(ce);
+ zend_verify_enum_magic_methods(ce);
+ zend_verify_enum_interfaces(ce);
+}
+
+static zval *zend_enum_read_property(zend_object *zobj, zend_string *name, int type, void **cache_slot, zval *rv) /* {{{ */
+{
+ if (type == BP_VAR_W || type == BP_VAR_RW || type == BP_VAR_UNSET) {
+ zend_throw_error(NULL, "Cannot acquire reference to property %s::$%s", ZSTR_VAL(zobj->ce->name), ZSTR_VAL(name));
+ return &EG(uninitialized_zval);
+ }
+
+ return zend_std_read_property(zobj, name, type, cache_slot, rv);
+}
+
+static ZEND_COLD zval *zend_enum_write_property(zend_object *object, zend_string *member, zval *value, void **cache_slot)
+{
+ ZEND_ENUM_PROPERTY_ERROR();
+ return &EG(uninitialized_zval);
+}
+
+static ZEND_COLD void zend_enum_unset_property(zend_object *object, zend_string *member, void **cache_slot)
+{
+ ZEND_ENUM_PROPERTY_ERROR();
+}
+
+static zval *zend_enum_get_property_ptr_ptr(zend_object *zobj, zend_string *name, int type, void **cache_slot)
+{
+ return NULL;
+}
+
+static int zend_implement_unit_enum(zend_class_entry *interface, zend_class_entry *class_type)
+{
+ if (class_type->ce_flags & ZEND_ACC_ENUM) {
+ return SUCCESS;
+ }
+
+ zend_error_noreturn(E_ERROR, "Non-enum class %s cannot implement interface %s",
+ ZSTR_VAL(class_type->name),
+ ZSTR_VAL(interface->name));
+
+ return FAILURE;
+}
+
+static int zend_implement_backed_enum(zend_class_entry *interface, zend_class_entry *class_type)
+{
+ if (!(class_type->ce_flags & ZEND_ACC_ENUM)) {
+ zend_error_noreturn(E_ERROR, "Non-enum class %s cannot implement interface %s",
+ ZSTR_VAL(class_type->name),
+ ZSTR_VAL(interface->name));
+ return FAILURE;
+ }
+
+ if (class_type->enum_backing_type == IS_UNDEF) {
+ zend_error_noreturn(E_ERROR, "Non-backed enum %s cannot implement interface %s",
+ ZSTR_VAL(class_type->name),
+ ZSTR_VAL(interface->name));
+ return FAILURE;
+ }
+
+ return SUCCESS;
+}
+
+void zend_register_enum_ce(void)
+{
+ zend_ce_unit_enum = register_class_UnitEnum();
+ zend_ce_unit_enum->interface_gets_implemented = zend_implement_unit_enum;
+
+ zend_ce_backed_enum = register_class_BackedEnum(zend_ce_unit_enum);
+ zend_ce_backed_enum->interface_gets_implemented = zend_implement_backed_enum;
+
+ memcpy(&enum_handlers, &std_object_handlers, sizeof(zend_object_handlers));
+ enum_handlers.read_property = zend_enum_read_property;
+ enum_handlers.write_property = zend_enum_write_property;
+ enum_handlers.unset_property = zend_enum_unset_property;
+ enum_handlers.get_property_ptr_ptr = zend_enum_get_property_ptr_ptr;
+ enum_handlers.clone_obj = NULL;
+ enum_handlers.compare = zend_objects_not_comparable;
+}
+
+void zend_enum_add_interfaces(zend_class_entry *ce)
+{
+ uint32_t num_interfaces_before = ce->num_interfaces;
+
+ ce->num_interfaces++;
+ if (ce->enum_backing_type != IS_UNDEF) {
+ ce->num_interfaces++;
+ }
+
+ ZEND_ASSERT(!(ce->ce_flags & ZEND_ACC_RESOLVED_INTERFACES));
+
+ ce->interface_names = erealloc(ce->interface_names, sizeof(zend_class_name) * ce->num_interfaces);
+
+ ce->interface_names[num_interfaces_before].name = zend_string_copy(zend_ce_unit_enum->name);
+ ce->interface_names[num_interfaces_before].lc_name = zend_string_init("unitenum", sizeof("unitenum") - 1, 0);
+
+ if (ce->enum_backing_type != IS_UNDEF) {
+ ce->interface_names[num_interfaces_before + 1].name = zend_string_copy(zend_ce_backed_enum->name);
+ ce->interface_names[num_interfaces_before + 1].lc_name = zend_string_init("backedenum", sizeof("backedenum") - 1, 0);
+ }
+}
+
+static ZEND_NAMED_FUNCTION(zend_enum_cases_func)
+{
+ zend_class_entry *ce = execute_data->func->common.scope;
+ zend_class_constant *c;
+
+ ZEND_PARSE_PARAMETERS_NONE();
+
+ array_init(return_value);
+
+ ZEND_HASH_FOREACH_PTR(CE_CONSTANTS_TABLE(ce), c) {
+ if (!(Z_ACCESS_FLAGS(c->value) & ZEND_CLASS_CONST_IS_CASE)) {
+ continue;
+ }
+ zval *zv = &c->value;
+ if (Z_TYPE_P(zv) == IS_CONSTANT_AST) {
+ if (zval_update_constant_ex(zv, c->ce) == FAILURE) {
+ RETURN_THROWS();
+ }
+ }
+ Z_ADDREF_P(zv);
+ zend_hash_next_index_insert(Z_ARRVAL_P(return_value), zv);
+ } ZEND_HASH_FOREACH_END();
+}
+
+static void zend_enum_from_base(INTERNAL_FUNCTION_PARAMETERS, bool try)
+{
+ zend_class_entry *ce = execute_data->func->common.scope;
+ zend_string *string_key;
+ zend_long long_key;
+
+ zval *case_name_zv;
+ if (ce->enum_backing_type == IS_LONG) {
+ ZEND_PARSE_PARAMETERS_START(1, 1)
+ Z_PARAM_LONG(long_key)
+ ZEND_PARSE_PARAMETERS_END();
+
+ case_name_zv = zend_hash_index_find(ce->backed_enum_table, long_key);
+ } else {
+ ZEND_PARSE_PARAMETERS_START(1, 1)
+ Z_PARAM_STR(string_key)
+ ZEND_PARSE_PARAMETERS_END();
+
+ ZEND_ASSERT(ce->enum_backing_type == IS_STRING);
+ case_name_zv = zend_hash_find(ce->backed_enum_table, string_key);
+ }
+
+ if (case_name_zv == NULL) {
+ if (try) {
+ RETURN_NULL();
+ }
+
+ if (ce->enum_backing_type == IS_LONG) {
+ zend_value_error(ZEND_LONG_FMT " is not a valid backing value for enum \"%s\"", long_key, ZSTR_VAL(ce->name));
+ } else {
+ ZEND_ASSERT(ce->enum_backing_type == IS_STRING);
+ zend_value_error("\"%s\" is not a valid backing value for enum \"%s\"", ZSTR_VAL(string_key), ZSTR_VAL(ce->name));
+ }
+ RETURN_THROWS();
+ }
+
+ // TODO: We might want to store pointers to constants in backed_enum_table instead of names,
+ // to make this lookup more efficient.
+ ZEND_ASSERT(Z_TYPE_P(case_name_zv) == IS_STRING);
+ zend_class_constant *c = zend_hash_find_ptr(CE_CONSTANTS_TABLE(ce), Z_STR_P(case_name_zv));
+ ZEND_ASSERT(c != NULL);
+ zval *case_zv = &c->value;
+ if (Z_TYPE_P(case_zv) == IS_CONSTANT_AST) {
+ if (zval_update_constant_ex(case_zv, c->ce) == FAILURE) {
+ RETURN_THROWS();
+ }
+ }
+
+ ZVAL_COPY(return_value, case_zv);
+}
+
+static ZEND_NAMED_FUNCTION(zend_enum_from_func)
+{
+ zend_enum_from_base(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
+}
+
+static ZEND_NAMED_FUNCTION(zend_enum_try_from_func)
+{
+ zend_enum_from_base(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
+}
+
+void zend_enum_register_funcs(zend_class_entry *ce)
+{
+ const uint32_t fn_flags =
+ ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_HAS_RETURN_TYPE|ZEND_ACC_ARENA_ALLOCATED;
+ zend_internal_function *cases_function =
+ zend_arena_alloc(&CG(arena), sizeof(zend_internal_function));
+ memset(cases_function, 0, sizeof(zend_internal_function));
+ cases_function->type = ZEND_INTERNAL_FUNCTION;
+ cases_function->module = EG(current_module);
+ cases_function->handler = zend_enum_cases_func;
+ cases_function->function_name = ZSTR_KNOWN(ZEND_STR_CASES);
+ cases_function->scope = ce;
+ cases_function->fn_flags = fn_flags;
+ cases_function->arg_info = (zend_internal_arg_info *) (arginfo_class_UnitEnum_cases + 1);
+ if (!zend_hash_add_ptr(&ce->function_table, ZSTR_KNOWN(ZEND_STR_CASES), cases_function)) {
+ zend_error_noreturn(E_COMPILE_ERROR, "Cannot redeclare %s::cases()", ZSTR_VAL(ce->name));
+ }
+
+ if (ce->enum_backing_type != IS_UNDEF) {
+ zend_internal_function *from_function =
+ zend_arena_alloc(&CG(arena), sizeof(zend_internal_function));
+ memset(from_function, 0, sizeof(zend_internal_function));
+ from_function->type = ZEND_INTERNAL_FUNCTION;
+ from_function->module = EG(current_module);
+ from_function->handler = zend_enum_from_func;
+ from_function->function_name = ZSTR_KNOWN(ZEND_STR_FROM);
+ from_function->scope = ce;
+ from_function->fn_flags = fn_flags;
+ from_function->num_args = 1;
+ from_function->required_num_args = 1;
+ from_function->arg_info = (zend_internal_arg_info *) (arginfo_class_BackedEnum_from + 1);
+ if (!zend_hash_add_ptr(&ce->function_table, ZSTR_KNOWN(ZEND_STR_FROM), from_function)) {
+ zend_error_noreturn(E_COMPILE_ERROR,
+ "Cannot redeclare %s::from()", ZSTR_VAL(ce->name));
+ }
+
+ zend_internal_function *try_from_function =
+ zend_arena_alloc(&CG(arena), sizeof(zend_internal_function));
+ memset(try_from_function, 0, sizeof(zend_internal_function));
+ try_from_function->type = ZEND_INTERNAL_FUNCTION;
+ try_from_function->module = EG(current_module);
+ try_from_function->handler = zend_enum_try_from_func;
+ try_from_function->function_name = ZSTR_KNOWN(ZEND_STR_TRYFROM);
+ try_from_function->scope = ce;
+ try_from_function->fn_flags = fn_flags;
+ try_from_function->num_args = 1;
+ try_from_function->required_num_args = 1;
+ try_from_function->arg_info = (zend_internal_arg_info *) (arginfo_class_BackedEnum_tryFrom + 1);
+ if (!zend_hash_add_ptr(
+ &ce->function_table, ZSTR_KNOWN(ZEND_STR_TRYFROM_LOWERCASE), try_from_function)) {
+ zend_error_noreturn(E_COMPILE_ERROR,
+ "Cannot redeclare %s::tryFrom()", ZSTR_VAL(ce->name));
+ }
+ }
+}
+
+void zend_enum_register_props(zend_class_entry *ce)
+{
+ zval name_default_value;
+ ZVAL_UNDEF(&name_default_value);
+ zend_type name_type = ZEND_TYPE_INIT_CODE(IS_STRING, 0, 0);
+ zend_declare_typed_property(ce, ZSTR_KNOWN(ZEND_STR_NAME), &name_default_value, ZEND_ACC_PUBLIC, NULL, name_type);
+
+ if (ce->enum_backing_type != IS_UNDEF) {
+ zval value_default_value;
+ ZVAL_UNDEF(&value_default_value);
+ zend_type value_type = ZEND_TYPE_INIT_CODE(ce->enum_backing_type, 0, 0);
+ zend_declare_typed_property(ce, ZSTR_KNOWN(ZEND_STR_VALUE), &value_default_value, ZEND_ACC_PUBLIC, NULL, value_type);
+ }
+}
diff --git a/Zend/zend_enum.h b/Zend/zend_enum.h
new file mode 100644
index 0000000000..c0abe7311f
--- /dev/null
+++ b/Zend/zend_enum.h
@@ -0,0 +1,52 @@
+/*
+ +----------------------------------------------------------------------+
+ | Zend Engine |
+ +----------------------------------------------------------------------+
+ | Copyright (c) Zend Technologies Ltd. (http://www.zend.com) |
+ +----------------------------------------------------------------------+
+ | This source file is subject to version 2.00 of the Zend license, |
+ | that is bundled with this package in the file LICENSE, and is |
+ | available through the world-wide-web at the following url: |
+ | http://www.zend.com/license/2_00.txt. |
+ | If you did not receive a copy of the Zend license and are unable to |
+ | obtain it through the world-wide-web, please send a note to |
+ | license@zend.com so we can mail you a copy immediately. |
+ +----------------------------------------------------------------------+
+ | Authors: Ilija Tovilo <ilutov@php.net> |
+ +----------------------------------------------------------------------+
+*/
+
+#ifndef ZEND_ENUM_H
+#define ZEND_ENUM_H
+
+#include "zend.h"
+#include "zend_types.h"
+
+BEGIN_EXTERN_C()
+
+extern ZEND_API zend_class_entry *zend_ce_unit_enum;
+extern ZEND_API zend_class_entry *zend_ce_backed_enum;
+
+void zend_register_enum_ce(void);
+void zend_enum_add_interfaces(zend_class_entry *ce);
+zend_object *zend_enum_new(zval *result, zend_class_entry *ce, zend_string *case_name, zval *backing_value_zv);
+void zend_verify_enum(zend_class_entry *ce);
+void zend_enum_register_funcs(zend_class_entry *ce);
+void zend_enum_register_props(zend_class_entry *ce);
+
+static zend_always_inline zval *zend_enum_fetch_case_name(zend_object *zobj)
+{
+ ZEND_ASSERT(zobj->ce->ce_flags & ZEND_ACC_ENUM);
+ return OBJ_PROP_NUM(zobj, 0);
+}
+
+static zend_always_inline zval *zend_enum_fetch_case_value(zend_object *zobj)
+{
+ ZEND_ASSERT(zobj->ce->ce_flags & ZEND_ACC_ENUM);
+ ZEND_ASSERT(zobj->ce->enum_backing_type != IS_UNDEF);
+ return OBJ_PROP_NUM(zobj, 1);
+}
+
+END_EXTERN_C()
+
+#endif /* ZEND_ENUM_H */
diff --git a/Zend/zend_enum.stub.php b/Zend/zend_enum.stub.php
new file mode 100644
index 0000000000..727514a7bd
--- /dev/null
+++ b/Zend/zend_enum.stub.php
@@ -0,0 +1,15 @@
+<?php
+
+/** @generate-class-entries */
+
+interface UnitEnum
+{
+ public static function cases(): array;
+}
+
+interface BackedEnum extends UnitEnum
+{
+ public static function from(int|string $value): static;
+
+ public static function tryFrom(int|string $value): ?static;
+}
diff --git a/Zend/zend_enum_arginfo.h b/Zend/zend_enum_arginfo.h
new file mode 100644
index 0000000000..a1b53cfde4
--- /dev/null
+++ b/Zend/zend_enum_arginfo.h
@@ -0,0 +1,49 @@
+/* This is a generated file, edit the .stub.php file instead.
+ * Stub hash: 7092f1d4ba651f077cff37050899f090f00abf22 */
+
+ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_UnitEnum_cases, 0, 0, IS_ARRAY, 0)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_BackedEnum_from, 0, 1, IS_STATIC, 0)
+ ZEND_ARG_TYPE_MASK(0, value, MAY_BE_LONG|MAY_BE_STRING, NULL)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_BackedEnum_tryFrom, 0, 1, IS_STATIC, 1)
+ ZEND_ARG_TYPE_MASK(0, value, MAY_BE_LONG|MAY_BE_STRING, NULL)
+ZEND_END_ARG_INFO()
+
+
+
+
+static const zend_function_entry class_UnitEnum_methods[] = {
+ ZEND_ABSTRACT_ME_WITH_FLAGS(UnitEnum, cases, arginfo_class_UnitEnum_cases, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_ABSTRACT)
+ ZEND_FE_END
+};
+
+
+static const zend_function_entry class_BackedEnum_methods[] = {
+ ZEND_ABSTRACT_ME_WITH_FLAGS(BackedEnum, from, arginfo_class_BackedEnum_from, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_ABSTRACT)
+ ZEND_ABSTRACT_ME_WITH_FLAGS(BackedEnum, tryFrom, arginfo_class_BackedEnum_tryFrom, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_ABSTRACT)
+ ZEND_FE_END
+};
+
+static zend_class_entry *register_class_UnitEnum(void)
+{
+ zend_class_entry ce, *class_entry;
+
+ INIT_CLASS_ENTRY(ce, "UnitEnum", class_UnitEnum_methods);
+ class_entry = zend_register_internal_interface(&ce);
+
+ return class_entry;
+}
+
+static zend_class_entry *register_class_BackedEnum(zend_class_entry *class_entry_UnitEnum)
+{
+ zend_class_entry ce, *class_entry;
+
+ INIT_CLASS_ENTRY(ce, "BackedEnum", class_BackedEnum_methods);
+ class_entry = zend_register_internal_interface(&ce);
+ zend_class_implements(class_entry, 1, class_entry_UnitEnum);
+
+ return class_entry;
+}
diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c
index c4e67c1e3e..ba2610e6ab 100644
--- a/Zend/zend_inheritance.c
+++ b/Zend/zend_inheritance.c
@@ -26,6 +26,7 @@
#include "zend_smart_str.h"
#include "zend_operators.h"
#include "zend_exceptions.h"
+#include "zend_enum.h"
ZEND_API zend_class_entry* (*zend_inheritance_cache_get)(zend_class_entry *ce, zend_class_entry *parent, zend_class_entry **traits_and_interfaces) = NULL;
ZEND_API zend_class_entry* (*zend_inheritance_cache_add)(zend_class_entry *ce, zend_class_entry *proto, zend_class_entry *parent, zend_class_entry **traits_and_interfaces, HashTable *dependencies) = NULL;
@@ -2684,6 +2685,12 @@ ZEND_API zend_class_entry *zend_do_link_class(zend_class_entry *ce, zend_string
}
}
+#ifndef ZEND_WIN32
+ if (ce->ce_flags & ZEND_ACC_ENUM) {
+ /* We will add internal methods. */
+ is_cacheable = false;
+ }
+#endif
if (ce->ce_flags & ZEND_ACC_IMMUTABLE) {
if (is_cacheable) {
@@ -2725,6 +2732,12 @@ ZEND_API zend_class_entry *zend_do_link_class(zend_class_entry *ce, zend_string
orig_linking_class = CG(current_linking_class);
CG(current_linking_class) = is_cacheable ? ce : NULL;
+ if (ce->ce_flags & ZEND_ACC_ENUM) {
+ /* Only register builtin enum methods during inheritance to avoid persisting them in
+ * opcache. */
+ zend_enum_register_funcs(ce);
+ }
+
if (parent) {
if (!(parent->ce_flags & ZEND_ACC_LINKED)) {
add_dependency_obligation(ce, parent);
@@ -2756,6 +2769,9 @@ ZEND_API zend_class_entry *zend_do_link_class(zend_class_entry *ce, zend_string
) {
zend_verify_abstract_class(ce);
}
+ if (ce->ce_flags & ZEND_ACC_ENUM) {
+ zend_verify_enum(ce);
+ }
zend_build_properties_info_table(ce);
diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y
index ed800c46f8..54ec8eab25 100644
--- a/Zend/zend_language_parser.y
+++ b/Zend/zend_language_parser.y
@@ -162,6 +162,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*);
%token <ident> T_CLASS "'class'"
%token <ident> T_TRAIT "'trait'"
%token <ident> T_INTERFACE "'interface'"
+%token <ident> T_ENUM "'enum'"
%token <ident> T_EXTENDS "'extends'"
%token <ident> T_IMPLEMENTS "'implements'"
%token <ident> T_NAMESPACE "'namespace'"
@@ -268,6 +269,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*);
%type <ast> attributed_statement attributed_class_statement attributed_parameter
%type <ast> attribute_decl attribute attributes attribute_group namespace_declaration_name
%type <ast> match match_arm_list non_empty_match_arm_list match_arm match_arm_cond_list
+%type <ast> enum_declaration_statement enum_backing_type enum_case enum_case_expr
%type <num> returns_ref function fn is_reference is_variadic variable_modifiers
%type <num> method_modifiers non_empty_member_modifiers member_modifier optional_visibility_modifier
@@ -291,7 +293,7 @@ reserved_non_modifiers:
| T_THROW | T_USE | T_INSTEADOF | T_GLOBAL | T_VAR | T_UNSET | T_ISSET | T_EMPTY | T_CONTINUE | T_GOTO
| T_FUNCTION | T_CONST | T_RETURN | T_PRINT | T_YIELD | T_LIST | T_SWITCH | T_ENDSWITCH | T_CASE | T_DEFAULT | T_BREAK
| T_ARRAY | T_CALLABLE | T_EXTENDS | T_IMPLEMENTS | T_NAMESPACE | T_TRAIT | T_INTERFACE | T_CLASS
- | T_CLASS_C | T_TRAIT_C | T_FUNC_C | T_METHOD_C | T_LINE | T_FILE | T_DIR | T_NS_C | T_FN | T_MATCH
+ | T_CLASS_C | T_TRAIT_C | T_FUNC_C | T_METHOD_C | T_LINE | T_FILE | T_DIR | T_NS_C | T_FN | T_MATCH | T_ENUM
;
semi_reserved:
@@ -366,6 +368,7 @@ attributed_statement:
| class_declaration_statement { $$ = $1; }
| trait_declaration_statement { $$ = $1; }
| interface_declaration_statement { $$ = $1; }
+ | enum_declaration_statement { $$ = $1; }
;
top_statement:
@@ -592,6 +595,27 @@ interface_declaration_statement:
{ $$ = zend_ast_create_decl(ZEND_AST_CLASS, ZEND_ACC_INTERFACE, $<num>2, $5, zend_ast_get_str($3), NULL, $4, $7, NULL, NULL); }
;
+enum_declaration_statement:
+ T_ENUM { $<num>$ = CG(zend_lineno); }
+ T_STRING enum_backing_type implements_list backup_doc_comment '{' class_statement_list '}'
+ { $$ = zend_ast_create_decl(ZEND_AST_CLASS, ZEND_ACC_ENUM|ZEND_ACC_FINAL, $<num>2, $6, zend_ast_get_str($3), NULL, $5, $8, NULL, $4); }
+;
+
+enum_backing_type:
+ %empty { $$ = NULL; }
+ | ':' type_expr { $$ = $2; }
+;
+
+enum_case:
+ T_CASE identifier enum_case_expr ';'
+ { $$ = zend_ast_create(ZEND_AST_ENUM_CASE, $2, $3, NULL); }
+;
+
+enum_case_expr:
+ %empty { $$ = NULL; }
+ | '=' expr { $$ = $2; }
+;
+
extends_from:
%empty { $$ = NULL; }
| T_EXTENDS class_name { $$ = $2; }
@@ -859,6 +883,7 @@ attributed_class_statement:
return_type backup_fn_flags method_body backup_fn_flags
{ $$ = zend_ast_create_decl(ZEND_AST_METHOD, $3 | $1 | $12, $2, $5,
zend_ast_get_str($4), $7, NULL, $11, $9, NULL); CG(extra_fn_flags) = $10; }
+ | enum_case { $$ = $1; }
;
class_statement:
diff --git a/Zend/zend_language_scanner.l b/Zend/zend_language_scanner.l
index 02714b4e1f..4fb4202264 100644
--- a/Zend/zend_language_scanner.l
+++ b/Zend/zend_language_scanner.l
@@ -1538,6 +1538,19 @@ NEWLINE ("\r"|"\n"|"\r\n")
RETURN_TOKEN_WITH_IDENT(T_TRAIT);
}
+/*
+ * The enum keyword must be followed by whitespace and another identifier.
+ * This avoids the BC break of using enum in classes, namespaces, functions and constants.
+ */
+<ST_IN_SCRIPTING>"enum"{WHITESPACE}("extends"|"implements") {
+ yyless(4);
+ RETURN_TOKEN_WITH_STR(T_STRING, 0);
+}
+<ST_IN_SCRIPTING>"enum"{WHITESPACE}[a-zA-Z_\x80-\xff] {
+ yyless(4);
+ RETURN_TOKEN_WITH_IDENT(T_ENUM);
+}
+
<ST_IN_SCRIPTING>"extends" {
RETURN_TOKEN_WITH_IDENT(T_EXTENDS);
}
diff --git a/Zend/zend_objects_API.c b/Zend/zend_objects_API.c
index 104cda6141..b0e9d2f8a8 100644
--- a/Zend/zend_objects_API.c
+++ b/Zend/zend_objects_API.c
@@ -113,7 +113,10 @@ ZEND_API void ZEND_FASTCALL zend_objects_store_free_object_storage(zend_objects_
if (IS_OBJ_VALID(obj)) {
if (!(OBJ_FLAGS(obj) & IS_OBJ_FREE_CALLED)) {
GC_ADD_FLAGS(obj, IS_OBJ_FREE_CALLED);
- GC_ADDREF(obj);
+ // FIXME: This causes constant objects to leak
+ if (!(obj->ce->ce_flags & ZEND_ACC_ENUM)) {
+ GC_ADDREF(obj);
+ }
obj->handlers->free_obj(obj);
}
}
diff --git a/Zend/zend_opcode.c b/Zend/zend_opcode.c
index 4e30b9ff7a..0cced91a84 100644
--- a/Zend/zend_opcode.c
+++ b/Zend/zend_opcode.c
@@ -356,6 +356,9 @@ ZEND_API void destroy_zend_class(zval *zv)
if (ce->attributes) {
zend_hash_release(ce->attributes);
}
+ if (ce->backed_enum_table) {
+ zend_hash_release(ce->backed_enum_table);
+ }
if (ce->num_interfaces > 0 && !(ce->ce_flags & ZEND_ACC_RESOLVED_INTERFACES)) {
uint32_t i;
diff --git a/Zend/zend_string.h b/Zend/zend_string.h
index 88a2fddfb3..230552e3c4 100644
--- a/Zend/zend_string.h
+++ b/Zend/zend_string.h
@@ -553,6 +553,10 @@ EMPTY_SWITCH_DEFAULT_CASE()
_(ZEND_STR_MIXED, "mixed") \
_(ZEND_STR_SLEEP, "__sleep") \
_(ZEND_STR_WAKEUP, "__wakeup") \
+ _(ZEND_STR_CASES, "cases") \
+ _(ZEND_STR_FROM, "from") \
+ _(ZEND_STR_TRYFROM, "tryFrom") \
+ _(ZEND_STR_TRYFROM_LOWERCASE, "tryfrom") \
typedef enum _zend_known_string_id {