diff options
164 files changed, 4299 insertions, 52 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 { diff --git a/configure.ac b/configure.ac index 37300041b7..7bf90a4454 100644 --- a/configure.ac +++ b/configure.ac @@ -1474,7 +1474,7 @@ PHP_ADD_SOURCES(Zend, \ zend_closures.c zend_weakrefs.c zend_float.c zend_string.c zend_signal.c zend_generators.c \ zend_virtual_cwd.c zend_ast.c zend_objects.c zend_object_handlers.c zend_objects_API.c \ zend_default_classes.c zend_inheritance.c zend_smart_str.c zend_cpuinfo.c zend_gdb.c \ - zend_observer.c zend_system_id.c \ + zend_observer.c zend_system_id.c zend_enum.c \ Optimizer/zend_optimizer.c \ Optimizer/pass1.c \ Optimizer/pass3.c \ diff --git a/ext/json/json.c b/ext/json/json.c index 5315966f7d..d8e17ef9da 100644 --- a/ext/json/json.c +++ b/ext/json/json.c @@ -187,6 +187,8 @@ static const char *php_json_get_error_msg(php_json_error_code error_code) /* {{{ return "The decoded property name is invalid"; case PHP_JSON_ERROR_UTF16: return "Single unpaired UTF-16 surrogate in unicode escape"; + case PHP_JSON_ERROR_NON_BACKED_ENUM: + return "Non-backed enums have no default serialization"; default: return "Unknown error"; } diff --git a/ext/json/json_encoder.c b/ext/json/json_encoder.c index e78a6495e9..c76ddaf0fd 100644 --- a/ext/json/json_encoder.c +++ b/ext/json/json_encoder.c @@ -27,6 +27,7 @@ #include "php_json.h" #include "php_json_encoder.h" #include <zend_exceptions.h> +#include "zend_enum.h" static const char digits[] = "0123456789abcdef"; @@ -570,6 +571,19 @@ static int php_json_encode_serializable_object(smart_str *buf, zval *val, int op } /* }}} */ +static int php_json_encode_serializable_enum(smart_str *buf, zval *val, int options, php_json_encoder *encoder) +{ + zend_class_entry *ce = Z_OBJCE_P(val); + if (ce->enum_backing_type == IS_UNDEF) { + encoder->error_code = PHP_JSON_ERROR_NON_BACKED_ENUM; + smart_str_appendc(buf, '0'); + return FAILURE; + } + + zval *value_zv = zend_enum_fetch_case_value(Z_OBJ_P(val)); + return php_json_encode_zval(buf, value_zv, options, encoder); +} + int php_json_encode_zval(smart_str *buf, zval *val, int options, php_json_encoder *encoder) /* {{{ */ { again: @@ -606,6 +620,9 @@ again: if (instanceof_function(Z_OBJCE_P(val), php_json_serializable_ce)) { return php_json_encode_serializable_object(buf, val, options, encoder); } + if (Z_OBJCE_P(val)->ce_flags & ZEND_ACC_ENUM) { + return php_json_encode_serializable_enum(buf, val, options, encoder); + } /* fallthrough -- Non-serializable object */ case IS_ARRAY: { /* Avoid modifications (and potential freeing) of the array through a reference when a diff --git a/ext/json/php_json.h b/ext/json/php_json.h index f6d7b376bd..7d258805b2 100644 --- a/ext/json/php_json.h +++ b/ext/json/php_json.h @@ -50,7 +50,8 @@ typedef enum { PHP_JSON_ERROR_INF_OR_NAN, PHP_JSON_ERROR_UNSUPPORTED_TYPE, PHP_JSON_ERROR_INVALID_PROPERTY_NAME, - PHP_JSON_ERROR_UTF16 + PHP_JSON_ERROR_UTF16, + PHP_JSON_ERROR_NON_BACKED_ENUM, } php_json_error_code; /* json_decode() options */ diff --git a/ext/opcache/zend_file_cache.c b/ext/opcache/zend_file_cache.c index 4c7611d36d..884ee796e0 100644 --- a/ext/opcache/zend_file_cache.c +++ b/ext/opcache/zend_file_cache.c @@ -637,12 +637,12 @@ static void zend_file_cache_serialize_func(zval *zv, zend_file_cache_metainfo *info, void *buf) { - zend_op_array *op_array; - + zend_function *func; SERIALIZE_PTR(Z_PTR_P(zv)); - op_array = Z_PTR_P(zv); - UNSERIALIZE_PTR(op_array); - zend_file_cache_serialize_op_array(op_array, script, info, buf); + func = Z_PTR_P(zv); + UNSERIALIZE_PTR(func); + ZEND_ASSERT(func->type == ZEND_USER_FUNCTION); + zend_file_cache_serialize_op_array(&func->op_array, script, info, buf); } static void zend_file_cache_serialize_prop_info(zval *zv, @@ -844,6 +844,14 @@ static void zend_file_cache_serialize_class(zval *zv, } } + if (ce->backed_enum_table) { + HashTable *ht; + SERIALIZE_PTR(ce->backed_enum_table); + ht = ce->backed_enum_table; + UNSERIALIZE_PTR(ht); + zend_file_cache_serialize_hash(ht, script, info, buf, zend_file_cache_serialize_zval); + } + SERIALIZE_PTR(ce->constructor); SERIALIZE_PTR(ce->destructor); SERIALIZE_PTR(ce->clone); @@ -1432,11 +1440,11 @@ static void zend_file_cache_unserialize_func(zval *zv, zend_persistent_script *script, void *buf) { - zend_op_array *op_array; - + zend_function *func; UNSERIALIZE_PTR(Z_PTR_P(zv)); - op_array = Z_PTR_P(zv); - zend_file_cache_unserialize_op_array(op_array, script, buf); + func = Z_PTR_P(zv); + ZEND_ASSERT(func->type == ZEND_USER_FUNCTION); + zend_file_cache_unserialize_op_array(&func->op_array, script, buf); } static void zend_file_cache_unserialize_prop_info(zval *zv, @@ -1615,6 +1623,12 @@ static void zend_file_cache_unserialize_class(zval *zv, } } + if (ce->backed_enum_table) { + UNSERIALIZE_PTR(ce->backed_enum_table); + zend_file_cache_unserialize_hash( + ce->backed_enum_table, script, buf, zend_file_cache_unserialize_zval, ZVAL_PTR_DTOR); + } + UNSERIALIZE_PTR(ce->constructor); UNSERIALIZE_PTR(ce->destructor); UNSERIALIZE_PTR(ce->clone); diff --git a/ext/opcache/zend_persist.c b/ext/opcache/zend_persist.c index 7ede974618..52d961c428 100644 --- a/ext/opcache/zend_persist.c +++ b/ext/opcache/zend_persist.c @@ -326,6 +326,26 @@ uint32_t zend_accel_get_type_map_ptr(zend_string *type_name, zend_class_entry *s return ret; } +static HashTable *zend_persist_backed_enum_table(HashTable *backed_enum_table) +{ + HashTable *ptr; + Bucket *p; + zend_hash_persist(backed_enum_table); + + ZEND_HASH_FOREACH_BUCKET(backed_enum_table, p) { + if (p->key != NULL) { + zend_accel_store_interned_string(p->key); + } + zend_persist_zval(&p->val); + } ZEND_HASH_FOREACH_END(); + + ptr = zend_shared_memdup_free(backed_enum_table, sizeof(HashTable)); + GC_SET_REFCOUNT(ptr, 2); + GC_TYPE_INFO(ptr) = GC_ARRAY | ((IS_ARRAY_IMMUTABLE|GC_NOT_COLLECTABLE) << GC_FLAGS_SHIFT); + + return ptr; +} + static void zend_persist_type(zend_type *type, zend_class_entry *scope) { if (ZEND_TYPE_HAS_LIST(*type)) { zend_type_list *list = ZEND_TYPE_LIST(*type); @@ -1050,6 +1070,10 @@ zend_class_entry *zend_persist_class_entry(zend_class_entry *orig_ce) ce->trait_precedences, sizeof(zend_trait_precedence*) * (i + 1)); } } + + if (ce->backed_enum_table) { + ce->backed_enum_table = zend_persist_backed_enum_table(ce->backed_enum_table); + } } return ce; diff --git a/ext/opcache/zend_persist_calc.c b/ext/opcache/zend_persist_calc.c index ebd1c61358..c3292ec2a1 100644 --- a/ext/opcache/zend_persist_calc.c +++ b/ext/opcache/zend_persist_calc.c @@ -534,6 +534,18 @@ void zend_persist_class_entry_calc(zend_class_entry *ce) ADD_SIZE(sizeof(zend_trait_precedence*) * (i + 1)); } } + + if (ce->backed_enum_table) { + Bucket *p; + ADD_SIZE(sizeof(HashTable)); + zend_hash_persist_calc(ce->backed_enum_table); + ZEND_HASH_FOREACH_BUCKET(ce->backed_enum_table, p) { + if (p->key != NULL) { + ADD_INTERNED_STRING(p->key); + } + zend_persist_zval_calc(&p->val); + } ZEND_HASH_FOREACH_END(); + } } } diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 83d165beb2..280b3fc06c 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -43,6 +43,7 @@ #include "zend_extensions.h" #include "zend_builtin_functions.h" #include "zend_smart_str.h" +#include "zend_enum.h" #include "php_reflection_arginfo.h" /* Key used to avoid leaking addresses in ReflectionProperty::getId() */ @@ -87,6 +88,9 @@ PHPAPI zend_class_entry *reflection_extension_ptr; PHPAPI zend_class_entry *reflection_zend_extension_ptr; PHPAPI zend_class_entry *reflection_reference_ptr; PHPAPI zend_class_entry *reflection_attribute_ptr; +PHPAPI zend_class_entry *reflection_enum_ptr; +PHPAPI zend_class_entry *reflection_enum_unit_case_ptr; +PHPAPI zend_class_entry *reflection_enum_backed_case_ptr; /* Exception throwing macro */ #define _DO_THROW(msg) \ @@ -573,6 +577,9 @@ static void _class_const_string(smart_str *str, char *name, zend_class_constant if (Z_TYPE(c->value) == IS_ARRAY) { smart_str_append_printf(str, "%sConstant [ %s %s %s ] { Array }\n", indent, visibility, type, name); + } else if (Z_TYPE(c->value) == IS_OBJECT) { + smart_str_append_printf(str, "%sConstant [ %s %s %s ] { Object }\n", + indent, visibility, type, name); } else { zend_string *tmp_value_str; zend_string *value_str = zval_get_tmp_string(&c->value, &tmp_value_str); @@ -1251,6 +1258,18 @@ PHPAPI void zend_reflection_class_factory(zend_class_entry *ce, zval *object) } /* }}} */ +static void zend_reflection_enum_factory(zend_class_entry *ce, zval *object) +{ + reflection_object *intern; + + reflection_instantiate(reflection_enum_ptr, object); + intern = Z_REFLECTION_P(object); + intern->ptr = ce; + intern->ref_type = REF_TYPE_OTHER; + intern->ce = ce; + ZVAL_STR_COPY(reflection_prop_name(object), ce->name); +} + /* {{{ reflection_extension_factory */ static void reflection_extension_factory(zval *object, const char *name_str) { @@ -1430,6 +1449,24 @@ static void reflection_class_constant_factory(zend_string *name_str, zend_class_ } /* }}} */ +static void reflection_enum_case_factory(zend_class_entry *ce, zend_string *name_str, zend_class_constant *constant, zval *object) +{ + reflection_object *intern; + + zend_class_entry *case_reflection_class = ce->backed_enum_table == IS_UNDEF + ? reflection_enum_unit_case_ptr + : reflection_enum_backed_case_ptr; + reflection_instantiate(case_reflection_class, object); + intern = Z_REFLECTION_P(object); + intern->ptr = constant; + intern->ref_type = REF_TYPE_CLASS_CONSTANT; + intern->ce = constant->ce; + intern->ignore_visibility = 0; + + ZVAL_STR_COPY(reflection_prop_name(object), name_str); + ZVAL_STR_COPY(reflection_prop_class(object), constant->ce->name); +} + static int get_parameter_default(zval *result, parameter_reference *param) { if (param->fptr->type == ZEND_INTERNAL_FUNCTION) { if (param->fptr->common.fn_flags & ZEND_ACC_USER_ARG_INFO) { @@ -3665,10 +3702,10 @@ ZEND_METHOD(ReflectionClassConstant, getValue) } GET_REFLECTION_OBJECT_PTR(ref); - ZVAL_COPY_OR_DUP(return_value, &ref->value); - if (Z_TYPE_P(return_value) == IS_CONSTANT_AST) { - zval_update_constant_ex(return_value, ref->ce); + if (Z_TYPE(ref->value) == IS_CONSTANT_AST) { + zval_update_constant_ex(&ref->value, ref->ce); } + ZVAL_COPY_OR_DUP(return_value, &ref->value); } /* }}} */ @@ -3718,6 +3755,16 @@ ZEND_METHOD(ReflectionClassConstant, getAttributes) } /* }}} */ +ZEND_METHOD(ReflectionClassConstant, isEnumCase) +{ + reflection_object *intern; + zend_class_constant *ref; + + GET_REFLECTION_OBJECT_PTR(ref); + + RETURN_BOOL(Z_ACCESS_FLAGS(ref->value) & ZEND_CLASS_CONST_IS_CASE); +} + /* {{{ reflection_class_object_ctor */ static void reflection_class_object_ctor(INTERNAL_FUNCTION_PARAMETERS, int is_object) { @@ -4637,6 +4684,11 @@ ZEND_METHOD(ReflectionClass, isTrait) } /* }}} */ +ZEND_METHOD(ReflectionClass, isEnum) +{ + _class_check_flag(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_ACC_ENUM); +} + /* {{{ Returns whether this class is final */ ZEND_METHOD(ReflectionClass, isFinal) { @@ -6494,6 +6546,194 @@ ZEND_METHOD(ReflectionAttribute, newInstance) RETURN_COPY_VALUE(&obj); } +ZEND_METHOD(ReflectionEnum, __construct) +{ + reflection_class_object_ctor(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0); + if (EG(exception)) { + RETURN_THROWS(); + } + + reflection_object *intern; + zend_class_entry *ce; + GET_REFLECTION_OBJECT_PTR(ce); + + if (!(ce->ce_flags & ZEND_ACC_ENUM)) { + zend_throw_exception_ex(reflection_exception_ptr, -1, "Class \"%s\" is not an enum", ZSTR_VAL(ce->name)); + RETURN_THROWS(); + } +} + +ZEND_METHOD(ReflectionEnum, hasCase) +{ + reflection_object *intern; + zend_class_entry *ce; + zend_string *name; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &name) == FAILURE) { + RETURN_THROWS(); + } + + GET_REFLECTION_OBJECT_PTR(ce); + + zend_class_constant *class_const = zend_hash_find_ptr(&ce->constants_table, name); + if (class_const == NULL) { + RETURN_FALSE; + } + + RETURN_BOOL(Z_ACCESS_FLAGS(class_const->value) & ZEND_CLASS_CONST_IS_CASE); +} + +ZEND_METHOD(ReflectionEnum, getCase) +{ + reflection_object *intern; + zend_class_entry *ce; + zend_string *name; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &name) == FAILURE) { + RETURN_THROWS(); + } + + GET_REFLECTION_OBJECT_PTR(ce); + + zend_class_constant *constant = zend_hash_find_ptr(&ce->constants_table, name); + if (constant == NULL) { + zend_throw_exception_ex(reflection_exception_ptr, 0, "Case %s::%s does not exist", ZSTR_VAL(ce->name), ZSTR_VAL(name)); + RETURN_THROWS(); + } + if (!(Z_ACCESS_FLAGS(constant->value) & ZEND_CLASS_CONST_IS_CASE)) { + zend_throw_exception_ex(reflection_exception_ptr, 0, "%s::%s is not a case", ZSTR_VAL(ce->name), ZSTR_VAL(name)); + RETURN_THROWS(); + } + + reflection_enum_case_factory(ce, name, constant, return_value); +} + +ZEND_METHOD(ReflectionEnum, getCases) +{ + reflection_object *intern; + zend_class_entry *ce; + zend_string *name; + zend_class_constant *constant; + + if (zend_parse_parameters_none() == FAILURE) { + RETURN_THROWS(); + } + + GET_REFLECTION_OBJECT_PTR(ce); + + array_init(return_value); + ZEND_HASH_FOREACH_STR_KEY_PTR(&ce->constants_table, name, constant) { + if (Z_ACCESS_FLAGS(constant->value) & ZEND_CLASS_CONST_IS_CASE) { + zval class_const; + reflection_enum_case_factory(ce, name, constant, &class_const); + zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &class_const); + } + } ZEND_HASH_FOREACH_END(); +} + +ZEND_METHOD(ReflectionEnum, isBacked) +{ + reflection_object *intern; + zend_class_entry *ce; + + if (zend_parse_parameters_none() == FAILURE) { + RETURN_THROWS(); + } + + GET_REFLECTION_OBJECT_PTR(ce); + RETURN_BOOL(ce->enum_backing_type != IS_UNDEF); +} + +ZEND_METHOD(ReflectionEnum, getBackingType) +{ + reflection_object *intern; + zend_class_entry *ce; + + if (zend_parse_parameters_none() == FAILURE) { + RETURN_THROWS(); + } + + GET_REFLECTION_OBJECT_PTR(ce); + + if (ce->enum_backing_type == IS_UNDEF) { + RETURN_NULL(); + } else { + zend_type type = ZEND_TYPE_INIT_CODE(ce->enum_backing_type, 0, 0); + reflection_type_factory(type, return_value, 0); + } +} + +ZEND_METHOD(ReflectionEnumUnitCase, __construct) +{ + ZEND_MN(ReflectionClassConstant___construct)(INTERNAL_FUNCTION_PARAM_PASSTHRU); + if (EG(exception)) { + RETURN_THROWS(); + } + + reflection_object *intern; + zend_class_constant *ref; + + GET_REFLECTION_OBJECT_PTR(ref); + + if (!(Z_ACCESS_FLAGS(ref->value) & ZEND_CLASS_CONST_IS_CASE)) { + zval *case_name = reflection_prop_name(ZEND_THIS); + zend_throw_exception_ex(reflection_exception_ptr, 0, "Constant %s::%s is not a case", ZSTR_VAL(ref->ce->name), Z_STRVAL_P(case_name)); + RETURN_THROWS(); + } +} + +ZEND_METHOD(ReflectionEnumUnitCase, getEnum) +{ + reflection_object *intern; + zend_class_constant *ref; + + if (zend_parse_parameters_none() == FAILURE) { + RETURN_THROWS(); + } + GET_REFLECTION_OBJECT_PTR(ref); + + zend_reflection_enum_factory(ref->ce, return_value); +} + +ZEND_METHOD(ReflectionEnumBackedCase, __construct) +{ + ZEND_MN(ReflectionEnumUnitCase___construct)(INTERNAL_FUNCTION_PARAM_PASSTHRU); + if (EG(exception)) { + RETURN_THROWS(); + } + + reflection_object *intern; + zend_class_constant *ref; + + GET_REFLECTION_OBJECT_PTR(ref); + + if (ref->ce->enum_backing_type == IS_UNDEF) { + zval *case_name = reflection_prop_name(ZEND_THIS); + zend_throw_exception_ex(reflection_exception_ptr, 0, "Enum case %s::%s is not a backed case", ZSTR_VAL(ref->ce->name), Z_STRVAL_P(case_name)); + RETURN_THROWS(); + } +} + +ZEND_METHOD(ReflectionEnumBackedCase, getBackingValue) +{ + reflection_object *intern; + zend_class_constant *ref; + + if (zend_parse_parameters_none() == FAILURE) { + RETURN_THROWS(); + } + GET_REFLECTION_OBJECT_PTR(ref); + + if (Z_TYPE(ref->value) == IS_CONSTANT_AST) { + zval_update_constant_ex(&ref->value, ref->ce); + } + + ZEND_ASSERT(intern->ce->enum_backing_type != IS_UNDEF); + zval *member_p = zend_enum_fetch_case_value(Z_OBJ(ref->value)); + + ZVAL_COPY_OR_DUP(return_value, member_p); +} + /* {{{ _reflection_write_property */ static zval *_reflection_write_property(zend_object *object, zend_string *name, zval *value, void **cache_slot) { @@ -6603,6 +6843,15 @@ PHP_MINIT_FUNCTION(reflection) /* {{{ */ reflection_attribute_ptr = register_class_ReflectionAttribute(); reflection_init_class_handlers(reflection_attribute_ptr); + reflection_enum_ptr = register_class_ReflectionEnum(reflection_class_ptr); + reflection_init_class_handlers(reflection_enum_ptr); + + reflection_enum_unit_case_ptr = register_class_ReflectionEnumUnitCase(reflection_class_constant_ptr); + reflection_init_class_handlers(reflection_enum_unit_case_ptr); + + reflection_enum_backed_case_ptr = register_class_ReflectionEnumBackedCase(reflection_enum_unit_case_ptr); + reflection_init_class_handlers(reflection_enum_backed_case_ptr); + REGISTER_REFLECTION_CLASS_CONST_LONG(attribute, "IS_INSTANCEOF", REFLECTION_ATTRIBUTE_IS_INSTANCEOF); REFLECTION_G(key_initialized) = 0; diff --git a/ext/reflection/php_reflection.h b/ext/reflection/php_reflection.h index 654ba55256..de368a86c0 100644 --- a/ext/reflection/php_reflection.h +++ b/ext/reflection/php_reflection.h @@ -43,6 +43,9 @@ extern PHPAPI zend_class_entry *reflection_extension_ptr; extern PHPAPI zend_class_entry *reflection_zend_extension_ptr; extern PHPAPI zend_class_entry *reflection_reference_ptr; extern PHPAPI zend_class_entry *reflection_attribute_ptr; +extern PHPAPI zend_class_entry *reflection_enum_ptr; +extern PHPAPI zend_class_entry *reflection_enum_unit_case_ptr; +extern PHPAPI zend_class_entry *reflection_enum_backed_case_ptr; PHPAPI void zend_reflection_class_factory(zend_class_entry *ce, zval *object); diff --git a/ext/reflection/php_reflection.stub.php b/ext/reflection/php_reflection.stub.php index 9287ae7e9c..e9815558d6 100644 --- a/ext/reflection/php_reflection.stub.php +++ b/ext/reflection/php_reflection.stub.php @@ -298,6 +298,8 @@ class ReflectionClass implements Reflector /** @return bool */ public function isTrait() {} + public function isEnum(): bool {} + /** @return bool */ public function isAbstract() {} @@ -479,6 +481,8 @@ class ReflectionClassConstant implements Reflector /** @return ReflectionAttribute[] */ public function getAttributes(?string $name = null, int $flags = 0): array {} + + public function isEnumCase(): bool {} } class ReflectionParameter implements Reflector @@ -683,3 +687,36 @@ final class ReflectionAttribute private function __construct() {} } + +final class ReflectionEnum extends ReflectionClass +{ + public function __construct(object|string $objectOrClass) {} + + public function hasCase(string $name): bool {} + + public function getCase(string $name): ReflectionEnumUnitCase {} + + /** @return ReflectionEnumUnitCase[] */ + public function getCases(): array {} + + public function isBacked(): bool {} + + public function getBackingType(): ReflectionType|null {} +} + +class ReflectionEnumUnitCase extends ReflectionClassConstant +{ + public function __construct(object|string $class, string $constant) {} + + public function getEnum(): ReflectionEnum {} + + /** @implementation-alias ReflectionClassConstant::getValue */ + public function getValue(): UnitEnum {} +} + +final class ReflectionEnumBackedCase extends ReflectionEnumUnitCase +{ + public function __construct(object|string $class, string $constant) {} + + public function getBackingValue(): int|string {} +} diff --git a/ext/reflection/php_reflection_arginfo.h b/ext/reflection/php_reflection_arginfo.h index a9df516fc2..36e6dc3cec 100644 --- a/ext/reflection/php_reflection_arginfo.h +++ b/ext/reflection/php_reflection_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: fb4e29d088862cc76d22c9902c79c86dbfa7ac95 */ + * Stub hash: 3594ec0b0c3ed7266223be9c6b426aac56e3aabe */ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Reflection_getModifierNames, 0, 0, 1) ZEND_ARG_TYPE_INFO(0, modifiers, IS_LONG, 0) @@ -218,6 +218,9 @@ ZEND_END_ARG_INFO() #define arginfo_class_ReflectionClass_isTrait arginfo_class_ReflectionFunctionAbstract_inNamespace +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_ReflectionClass_isEnum, 0, 0, _IS_BOOL, 0) +ZEND_END_ARG_INFO() + #define arginfo_class_ReflectionClass_isAbstract arginfo_class_ReflectionFunctionAbstract_inNamespace #define arginfo_class_ReflectionClass_isFinal arginfo_class_ReflectionFunctionAbstract_inNamespace @@ -308,8 +311,7 @@ ZEND_END_ARG_INFO() #define arginfo_class_ReflectionProperty_isDefault arginfo_class_ReflectionFunctionAbstract_inNamespace -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_ReflectionProperty_isPromoted, 0, 0, _IS_BOOL, 0) -ZEND_END_ARG_INFO() +#define arginfo_class_ReflectionProperty_isPromoted arginfo_class_ReflectionClass_isEnum #define arginfo_class_ReflectionProperty_getModifiers arginfo_class_ReflectionFunctionAbstract_inNamespace @@ -323,7 +325,7 @@ ZEND_END_ARG_INFO() #define arginfo_class_ReflectionProperty_hasType arginfo_class_ReflectionFunctionAbstract_inNamespace -#define arginfo_class_ReflectionProperty_hasDefaultValue arginfo_class_ReflectionProperty_isPromoted +#define arginfo_class_ReflectionProperty_hasDefaultValue arginfo_class_ReflectionClass_isEnum #define arginfo_class_ReflectionProperty_getDefaultValue arginfo_class_ReflectionFunctionAbstract_inNamespace @@ -356,6 +358,8 @@ ZEND_END_ARG_INFO() #define arginfo_class_ReflectionClassConstant_getAttributes arginfo_class_ReflectionFunctionAbstract_getAttributes +#define arginfo_class_ReflectionClassConstant_isEnumCase arginfo_class_ReflectionClass_isEnum + #define arginfo_class_ReflectionParameter___clone arginfo_class_ReflectionFunctionAbstract___clone ZEND_BEGIN_ARG_INFO_EX(arginfo_class_ReflectionParameter___construct, 0, 0, 2) @@ -401,7 +405,7 @@ ZEND_END_ARG_INFO() #define arginfo_class_ReflectionParameter_isVariadic arginfo_class_ReflectionFunctionAbstract_inNamespace -#define arginfo_class_ReflectionParameter_isPromoted arginfo_class_ReflectionProperty_isPromoted +#define arginfo_class_ReflectionParameter_isPromoted arginfo_class_ReflectionClass_isEnum #define arginfo_class_ReflectionParameter_getAttributes arginfo_class_ReflectionFunctionAbstract_getAttributes @@ -478,7 +482,7 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_ReflectionAttribute_getTarget, 0, 0, IS_LONG, 0) ZEND_END_ARG_INFO() -#define arginfo_class_ReflectionAttribute_isRepeated arginfo_class_ReflectionProperty_isPromoted +#define arginfo_class_ReflectionAttribute_isRepeated arginfo_class_ReflectionClass_isEnum #define arginfo_class_ReflectionAttribute_getArguments arginfo_class_ReflectionUnionType_getTypes @@ -489,6 +493,36 @@ ZEND_END_ARG_INFO() #define arginfo_class_ReflectionAttribute___construct arginfo_class_ReflectionFunctionAbstract_inNamespace +#define arginfo_class_ReflectionEnum___construct arginfo_class_ReflectionClass___construct + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_ReflectionEnum_hasCase, 0, 1, _IS_BOOL, 0) + ZEND_ARG_TYPE_INFO(0, name, IS_STRING, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_ReflectionEnum_getCase, 0, 1, ReflectionEnumUnitCase, 0) + ZEND_ARG_TYPE_INFO(0, name, IS_STRING, 0) +ZEND_END_ARG_INFO() + +#define arginfo_class_ReflectionEnum_getCases arginfo_class_ReflectionUnionType_getTypes + +#define arginfo_class_ReflectionEnum_isBacked arginfo_class_ReflectionClass_isEnum + +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_ReflectionEnum_getBackingType, 0, 0, ReflectionType, 1) +ZEND_END_ARG_INFO() + +#define arginfo_class_ReflectionEnumUnitCase___construct arginfo_class_ReflectionClassConstant___construct + +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_ReflectionEnumUnitCase_getEnum, 0, 0, ReflectionEnum, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_ReflectionEnumUnitCase_getValue, 0, 0, UnitEnum, 0) +ZEND_END_ARG_INFO() + +#define arginfo_class_ReflectionEnumBackedCase___construct arginfo_class_ReflectionClassConstant___construct + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_class_ReflectionEnumBackedCase_getBackingValue, 0, 0, MAY_BE_LONG|MAY_BE_STRING) +ZEND_END_ARG_INFO() + ZEND_METHOD(Reflection, getModifierNames); ZEND_METHOD(ReflectionClass, __clone); @@ -579,6 +613,7 @@ ZEND_METHOD(ReflectionClass, getTraits); ZEND_METHOD(ReflectionClass, getTraitNames); ZEND_METHOD(ReflectionClass, getTraitAliases); ZEND_METHOD(ReflectionClass, isTrait); +ZEND_METHOD(ReflectionClass, isEnum); ZEND_METHOD(ReflectionClass, isAbstract); ZEND_METHOD(ReflectionClass, isFinal); ZEND_METHOD(ReflectionClass, getModifiers); @@ -633,6 +668,7 @@ ZEND_METHOD(ReflectionClassConstant, getModifiers); ZEND_METHOD(ReflectionClassConstant, getDeclaringClass); ZEND_METHOD(ReflectionClassConstant, getDocComment); ZEND_METHOD(ReflectionClassConstant, getAttributes); +ZEND_METHOD(ReflectionClassConstant, isEnumCase); ZEND_METHOD(ReflectionParameter, __construct); ZEND_METHOD(ReflectionParameter, __toString); ZEND_METHOD(ReflectionParameter, getName); @@ -690,6 +726,16 @@ ZEND_METHOD(ReflectionAttribute, getArguments); ZEND_METHOD(ReflectionAttribute, newInstance); ZEND_METHOD(ReflectionAttribute, __clone); ZEND_METHOD(ReflectionAttribute, __construct); +ZEND_METHOD(ReflectionEnum, __construct); +ZEND_METHOD(ReflectionEnum, hasCase); +ZEND_METHOD(ReflectionEnum, getCase); +ZEND_METHOD(ReflectionEnum, getCases); +ZEND_METHOD(ReflectionEnum, isBacked); +ZEND_METHOD(ReflectionEnum, getBackingType); +ZEND_METHOD(ReflectionEnumUnitCase, __construct); +ZEND_METHOD(ReflectionEnumUnitCase, getEnum); +ZEND_METHOD(ReflectionEnumBackedCase, __construct); +ZEND_METHOD(ReflectionEnumBackedCase, getBackingValue); static const zend_function_entry class_ReflectionException_methods[] = { @@ -818,6 +864,7 @@ static const zend_function_entry class_ReflectionClass_methods[] = { ZEND_ME(ReflectionClass, getTraitNames, arginfo_class_ReflectionClass_getTraitNames, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionClass, getTraitAliases, arginfo_class_ReflectionClass_getTraitAliases, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionClass, isTrait, arginfo_class_ReflectionClass_isTrait, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionClass, isEnum, arginfo_class_ReflectionClass_isEnum, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionClass, isAbstract, arginfo_class_ReflectionClass_isAbstract, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionClass, isFinal, arginfo_class_ReflectionClass_isFinal, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionClass, getModifiers, arginfo_class_ReflectionClass_getModifiers, ZEND_ACC_PUBLIC) @@ -890,6 +937,7 @@ static const zend_function_entry class_ReflectionClassConstant_methods[] = { ZEND_ME(ReflectionClassConstant, getDeclaringClass, arginfo_class_ReflectionClassConstant_getDeclaringClass, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionClassConstant, getDocComment, arginfo_class_ReflectionClassConstant_getDocComment, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionClassConstant, getAttributes, arginfo_class_ReflectionClassConstant_getAttributes, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionClassConstant, isEnumCase, arginfo_class_ReflectionClassConstant_isEnumCase, ZEND_ACC_PUBLIC) ZEND_FE_END }; @@ -995,6 +1043,32 @@ static const zend_function_entry class_ReflectionAttribute_methods[] = { ZEND_FE_END }; + +static const zend_function_entry class_ReflectionEnum_methods[] = { + ZEND_ME(ReflectionEnum, __construct, arginfo_class_ReflectionEnum___construct, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionEnum, hasCase, arginfo_class_ReflectionEnum_hasCase, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionEnum, getCase, arginfo_class_ReflectionEnum_getCase, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionEnum, getCases, arginfo_class_ReflectionEnum_getCases, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionEnum, isBacked, arginfo_class_ReflectionEnum_isBacked, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionEnum, getBackingType, arginfo_class_ReflectionEnum_getBackingType, ZEND_ACC_PUBLIC) + ZEND_FE_END +}; + + +static const zend_function_entry class_ReflectionEnumUnitCase_methods[] = { + ZEND_ME(ReflectionEnumUnitCase, __construct, arginfo_class_ReflectionEnumUnitCase___construct, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionEnumUnitCase, getEnum, arginfo_class_ReflectionEnumUnitCase_getEnum, ZEND_ACC_PUBLIC) + ZEND_MALIAS(ReflectionClassConstant, getValue, getValue, arginfo_class_ReflectionEnumUnitCase_getValue, ZEND_ACC_PUBLIC) + ZEND_FE_END +}; + + +static const zend_function_entry class_ReflectionEnumBackedCase_methods[] = { + ZEND_ME(ReflectionEnumBackedCase, __construct, arginfo_class_ReflectionEnumBackedCase___construct, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionEnumBackedCase, getBackingValue, arginfo_class_ReflectionEnumBackedCase_getBackingValue, ZEND_ACC_PUBLIC) + ZEND_FE_END +}; + static zend_class_entry *register_class_ReflectionException(zend_class_entry *class_entry_Exception) { zend_class_entry ce, *class_entry; @@ -1258,3 +1332,35 @@ static zend_class_entry *register_class_ReflectionAttribute(void) return class_entry; } + +static zend_class_entry *register_class_ReflectionEnum(zend_class_entry *class_entry_ReflectionClass) +{ + zend_class_entry ce, *class_entry; + + INIT_CLASS_ENTRY(ce, "ReflectionEnum", class_ReflectionEnum_methods); + class_entry = zend_register_internal_class_ex(&ce, class_entry_ReflectionClass); + class_entry->ce_flags |= ZEND_ACC_FINAL; + + return class_entry; +} + +static zend_class_entry *register_class_ReflectionEnumUnitCase(zend_class_entry *class_entry_ReflectionClassConstant) +{ + zend_class_entry ce, *class_entry; + + INIT_CLASS_ENTRY(ce, "ReflectionEnumUnitCase", class_ReflectionEnumUnitCase_methods); + class_entry = zend_register_internal_class_ex(&ce, class_entry_ReflectionClassConstant); + + return class_entry; +} + +static zend_class_entry *register_class_ReflectionEnumBackedCase(zend_class_entry *class_entry_ReflectionEnumUnitCase) +{ + zend_class_entry ce, *class_entry; + + INIT_CLASS_ENTRY(ce, "ReflectionEnumBackedCase", class_ReflectionEnumBackedCase_methods); + class_entry = zend_register_internal_class_ex(&ce, class_entry_ReflectionEnumUnitCase); + class_entry->ce_flags |= ZEND_ACC_FINAL; + + return class_entry; +} diff --git a/ext/reflection/tests/ReflectionClassConstant_isEnumCase.phpt b/ext/reflection/tests/ReflectionClassConstant_isEnumCase.phpt new file mode 100644 index 0000000000..7125075085 --- /dev/null +++ b/ext/reflection/tests/ReflectionClassConstant_isEnumCase.phpt @@ -0,0 +1,23 @@ +--TEST-- +ReflectionClassConstant::isEnumCase() +--FILE-- +<?php + +enum Foo { + case Bar; + const Baz = self::Bar; +} + +class Qux { + const Quux = 0; +} + +var_dump((new ReflectionClassConstant(Foo::class, 'Bar'))->isEnumCase()); +var_dump((new ReflectionClassConstant(Foo::class, 'Baz'))->isEnumCase()); +var_dump((new ReflectionClassConstant(Qux::class, 'Quux'))->isEnumCase()); + +?> +--EXPECT-- +bool(true) +bool(false) +bool(false) diff --git a/ext/reflection/tests/ReflectionClass_isEnum.phpt b/ext/reflection/tests/ReflectionClass_isEnum.phpt new file mode 100644 index 0000000000..b781c61509 --- /dev/null +++ b/ext/reflection/tests/ReflectionClass_isEnum.phpt @@ -0,0 +1,20 @@ +--TEST-- +Testing ReflectionClass::isEnum() +--FILE-- +<?php + +class Foo {} +enum Bar { + case Baz; +} + +$fooReflection = new ReflectionClass(Foo::class); +$barReflection = new ReflectionClass(Bar::class); + +var_dump($fooReflection->isEnum()); +var_dump($barReflection->isEnum()); + +?> +--EXPECT-- +bool(false) +bool(true) diff --git a/ext/reflection/tests/ReflectionClass_toString_001.phpt b/ext/reflection/tests/ReflectionClass_toString_001.phpt index e218e8b470..e8baf72689 100644 --- a/ext/reflection/tests/ReflectionClass_toString_001.phpt +++ b/ext/reflection/tests/ReflectionClass_toString_001.phpt @@ -27,7 +27,7 @@ Class [ <internal:Reflection> class ReflectionClass implements Reflector, String Property [ public string $name ] } - - Methods [54] { + - Methods [55] { Method [ <internal:Reflection> final private method __clone ] { - Parameters [0] { @@ -234,6 +234,13 @@ Class [ <internal:Reflection> class ReflectionClass implements Reflector, String } } + Method [ <internal:Reflection> public method isEnum ] { + + - Parameters [0] { + } + - Return [ bool ] + } + Method [ <internal:Reflection> public method isAbstract ] { - Parameters [0] { diff --git a/ext/reflection/tests/ReflectionEnumBackedCase_getBackingValue.phpt b/ext/reflection/tests/ReflectionEnumBackedCase_getBackingValue.phpt new file mode 100644 index 0000000000..f81241d73a --- /dev/null +++ b/ext/reflection/tests/ReflectionEnumBackedCase_getBackingValue.phpt @@ -0,0 +1,38 @@ +--TEST-- +ReflectionEnumBackedCase::getBackingValue() +--FILE-- +<?php + +enum Enum_ { + case Foo; +} + +enum IntEnum: int { + case Foo = 0; +} + +enum StringEnum: string { + case Foo = 'Foo'; +} + +try { + var_dump(new ReflectionEnumBackedCase(Enum_::class, 'Foo')); +} catch (ReflectionException $e) { + echo $e->getMessage() . "\n"; +} + +try { + var_dump(new ReflectionEnumBackedCase([], 'Foo')); +} catch (Error $e) { + echo $e->getMessage() . "\n"; +} + +var_dump((new ReflectionEnumBackedCase(IntEnum::class, 'Foo'))->getBackingValue()); +var_dump((new ReflectionEnumBackedCase(StringEnum::class, 'Foo'))->getBackingValue()); + +?> +--EXPECT-- +Enum case Enum_::Foo is not a backed case +ReflectionEnumBackedCase::__construct(): Argument #1 ($class) must be of type object|string, array given +int(0) +string(3) "Foo" diff --git a/ext/reflection/tests/ReflectionEnumUnitCase_construct.phpt b/ext/reflection/tests/ReflectionEnumUnitCase_construct.phpt new file mode 100644 index 0000000000..de5af5c549 --- /dev/null +++ b/ext/reflection/tests/ReflectionEnumUnitCase_construct.phpt @@ -0,0 +1,36 @@ +--TEST-- +ReflectionEnumUnitCase::__construct() +--FILE-- +<?php + +enum Foo { + case Bar; + const Baz = self::Bar; +} + +echo (new ReflectionEnumUnitCase(Foo::class, 'Bar'))->getName() . "\n"; + +try { + new ReflectionEnumUnitCase(Foo::class, 'Baz'); +} catch (\Exception $e) { + echo $e->getMessage() . "\n"; +} + +try { + new ReflectionEnumUnitCase(Foo::class, 'Qux'); +} catch (\Exception $e) { + echo $e->getMessage() . "\n"; +} + +try { + new ReflectionEnumUnitCase([], 'Foo'); +} catch (Error $e) { + echo $e->getMessage() . "\n"; +} + +?> +--EXPECT-- +Bar +Constant Foo::Baz is not a case +Constant Foo::Qux does not exist +ReflectionEnumUnitCase::__construct(): Argument #1 ($class) must be of type object|string, array given diff --git a/ext/reflection/tests/ReflectionEnumUnitCase_getEnum.phpt b/ext/reflection/tests/ReflectionEnumUnitCase_getEnum.phpt new file mode 100644 index 0000000000..3d3bcc227e --- /dev/null +++ b/ext/reflection/tests/ReflectionEnumUnitCase_getEnum.phpt @@ -0,0 +1,39 @@ +--TEST-- +ReflectionEnumUnitCase::getEnum() +--FILE-- +<?php + +enum Foo { + case Bar; +} + +echo (new ReflectionEnumUnitCase(Foo::class, 'Bar'))->getEnum(); + +?> +--EXPECTF-- +Class [ <user> final class Foo implements UnitEnum ] { + @@ %sReflectionEnumUnitCase_getEnum.php 3-5 + + - Constants [1] { + Constant [ public Foo Bar ] { Object } + } + + - Static properties [0] { + } + + - Static methods [1] { + Method [ <internal, prototype UnitEnum> static public method cases ] { + + - Parameters [0] { + } + - Return [ array ] + } + } + + - Properties [1] { + Property [ public string $name ] + } + + - Methods [0] { + } +} diff --git a/ext/reflection/tests/ReflectionEnumUnitCase_getValue.phpt b/ext/reflection/tests/ReflectionEnumUnitCase_getValue.phpt new file mode 100644 index 0000000000..ec5f22d9f8 --- /dev/null +++ b/ext/reflection/tests/ReflectionEnumUnitCase_getValue.phpt @@ -0,0 +1,30 @@ +--TEST-- +ReflectionEnumUnitCase::getValue() +--FILE-- +<?php + +enum Foo { + case Bar; + case Baz; +} + +$barFromReflection = (new ReflectionEnumUnitCase(Foo::class, 'Bar'))->getValue(); +$bazFromReflection = (new ReflectionEnumUnitCase(Foo::class, 'Baz'))->getValue(); + +var_dump($barFromReflection); +var_dump($bazFromReflection); + +var_dump(Foo::Bar === $barFromReflection); +var_dump(Foo::Baz === $barFromReflection); + +var_dump(Foo::Bar === $bazFromReflection); +var_dump(Foo::Baz === $bazFromReflection); + +?> +--EXPECT-- +enum(Foo::Bar) +enum(Foo::Baz) +bool(true) +bool(false) +bool(false) +bool(true) diff --git a/ext/reflection/tests/ReflectionEnum_construct.phpt b/ext/reflection/tests/ReflectionEnum_construct.phpt new file mode 100644 index 0000000000..4b4a039105 --- /dev/null +++ b/ext/reflection/tests/ReflectionEnum_construct.phpt @@ -0,0 +1,34 @@ +--TEST-- +ReflectionEnum::__construct() +--FILE-- +<?php + +enum Foo {} +class Bar {} + +echo (new ReflectionEnum(Foo::class))->getName() . "\n"; + +try { + new ReflectionEnum('Bar'); +} catch (\Exception $e) { + echo $e->getMessage() . "\n"; +} + +try { + new ReflectionEnum('Baz'); +} catch (\Exception $e) { + echo $e->getMessage() . "\n"; +} + +try { + new ReflectionEnum([]); +} catch (Error $e) { + echo $e->getMessage() . "\n"; +} + +?> +--EXPECT-- +Foo +Class "Bar" is not an enum +Class "Baz" does not exist +ReflectionEnum::__construct(): Argument #1 ($objectOrClass) must be of type object|string, array given diff --git a/ext/reflection/tests/ReflectionEnum_getBackingType.phpt b/ext/reflection/tests/ReflectionEnum_getBackingType.phpt new file mode 100644 index 0000000000..e9d429f592 --- /dev/null +++ b/ext/reflection/tests/ReflectionEnum_getBackingType.phpt @@ -0,0 +1,20 @@ +--TEST-- +ReflectionEnum::getBackingType() +--FILE-- +<?php + +enum Enum_ {} +enum IntEnum: int {} +enum StringEnum: string {} + +function test(): string {} + +var_dump((new ReflectionEnum(Enum_::class))->getBackingType()); +echo (new ReflectionEnum(IntEnum::class))->getBackingType() . "\n"; +echo (new ReflectionEnum(StringEnum::class))->getBackingType() . "\n"; + +?> +--EXPECT-- +NULL +int +string diff --git a/ext/reflection/tests/ReflectionEnum_getCase.phpt b/ext/reflection/tests/ReflectionEnum_getCase.phpt new file mode 100644 index 0000000000..f2b53cf657 --- /dev/null +++ b/ext/reflection/tests/ReflectionEnum_getCase.phpt @@ -0,0 +1,50 @@ +--TEST-- +ReflectionEnum::getCases() +--FILE-- +<?php + +enum Enum_ { + case Foo; + const Bar = self::Foo; +} + +enum IntEnum: int { + case Foo = 0; + const Bar = self::Foo; +} + +function test(string $enumName, string $caseName) { + try { + $reflectionEnum = new ReflectionEnum($enumName); + var_dump($reflectionEnum->getCase($caseName)); + } catch (Throwable $e) { + echo get_class($e) . ': ' . $e->getMessage() . "\n"; + } +} + +test(Enum_::class, 'Foo'); +test(Enum_::class, 'Bar'); +test(Enum_::class, 'Baz'); + +test(IntEnum::class, 'Foo'); +test(IntEnum::class, 'Bar'); +test(IntEnum::class, 'Baz'); + +?> +--EXPECT-- +object(ReflectionEnumUnitCase)#2 (2) { + ["name"]=> + string(3) "Foo" + ["class"]=> + string(5) "Enum_" +} +ReflectionException: Enum_::Bar is not a case +ReflectionException: Case Enum_::Baz does not exist +object(ReflectionEnumBackedCase)#2 (2) { + ["name"]=> + string(3) "Foo" + ["class"]=> + string(7) "IntEnum" +} +ReflectionException: IntEnum::Bar is not a case +ReflectionException: Case IntEnum::Baz does not exist diff --git a/ext/reflection/tests/ReflectionEnum_getCases.phpt b/ext/reflection/tests/ReflectionEnum_getCases.phpt new file mode 100644 index 0000000000..9365008774 --- /dev/null +++ b/ext/reflection/tests/ReflectionEnum_getCases.phpt @@ -0,0 +1,54 @@ +--TEST-- +ReflectionEnum::getCases() +--FILE-- +<?php + +enum Enum_ { + case Foo; + case Bar; + const Baz = self::Bar; +} + +enum IntEnum: int { + case Foo = 0; + case Bar = 1; + const Baz = self::Bar; +} + +var_dump((new ReflectionEnum(Enum_::class))->getCases()); +var_dump((new ReflectionEnum(IntEnum::class))->getCases()); + +?> +--EXPECT-- +array(2) { + [0]=> + object(ReflectionEnumUnitCase)#2 (2) { + ["name"]=> + string(3) "Foo" + ["class"]=> + string(5) "Enum_" + } + [1]=> + object(ReflectionEnumUnitCase)#3 (2) { + ["name"]=> + string(3) "Bar" + ["class"]=> + string(5) "Enum_" + } +} +array(2) { + [0]=> + object(ReflectionEnumBackedCase)#2 (2) { + ["name"]=> + string(3) "Foo" + ["class"]=> + string(7) "IntEnum" + } + [1]=> + object(ReflectionEnumBackedCase)#1 (2) { + ["name"]=> + string(3) "Bar" + ["class"]=> + string(7) "IntEnum" + } +} diff --git a/ext/reflection/tests/ReflectionEnum_hasCase.phpt b/ext/reflection/tests/ReflectionEnum_hasCase.phpt new file mode 100644 index 0000000000..3310f0ef6b --- /dev/null +++ b/ext/reflection/tests/ReflectionEnum_hasCase.phpt @@ -0,0 +1,20 @@ +--TEST-- +ReflectionEnum::hasCase() +--FILE-- +<?php + +enum Foo { + case Bar; + const Baz = self::Bar; +} + +$reflectionEnum = new ReflectionEnum(Foo::class); +var_dump($reflectionEnum->hasCase('Bar')); +var_dump($reflectionEnum->hasCase('Baz')); +var_dump($reflectionEnum->hasCase('Qux')); + +?> +--EXPECT-- +bool(true) +bool(false) +bool(false) diff --git a/ext/reflection/tests/ReflectionEnum_isBacked.phpt b/ext/reflection/tests/ReflectionEnum_isBacked.phpt new file mode 100644 index 0000000000..cb449e951a --- /dev/null +++ b/ext/reflection/tests/ReflectionEnum_isBacked.phpt @@ -0,0 +1,20 @@ +--TEST-- +ReflectionEnum::isBacked() +--FILE-- +<?php + +enum Enum_ {} +enum IntEnum: int {} +enum StringEnum: string {} + +function test(): string {} + +var_dump((new ReflectionEnum(Enum_::class))->isBacked()); +var_dump((new ReflectionEnum(IntEnum::class))->isBacked()); +var_dump((new ReflectionEnum(StringEnum::class))->isBacked()); + +?> +--EXPECT-- +bool(false) +bool(true) +bool(true) diff --git a/ext/reflection/tests/ReflectionEnum_toString.phpt b/ext/reflection/tests/ReflectionEnum_toString.phpt new file mode 100644 index 0000000000..567191e594 --- /dev/null +++ b/ext/reflection/tests/ReflectionEnum_toString.phpt @@ -0,0 +1,39 @@ +--TEST-- +ReflectionEnum::__toString() +--FILE-- +<?php + +enum Foo { + case Bar; +} + +echo new ReflectionEnum(Foo::class); + +?> +--EXPECTF-- +Class [ <user> final class Foo implements UnitEnum ] { + @@ %sReflectionEnum_toString.php 3-5 + + - Constants [1] { + Constant [ public Foo Bar ] { Object } + } + + - Static properties [0] { + } + + - Static methods [1] { + Method [ <internal, prototype UnitEnum> static public method cases ] { + + - Parameters [0] { + } + - Return [ array ] + } + } + + - Properties [1] { + Property [ public string $name ] + } + + - Methods [0] { + } +} diff --git a/ext/reflection/tests/ReflectionExtension_getClasses_basic.phpt b/ext/reflection/tests/ReflectionExtension_getClasses_basic.phpt index 81d3c8d55b..084e05e118 100644 --- a/ext/reflection/tests/ReflectionExtension_getClasses_basic.phpt +++ b/ext/reflection/tests/ReflectionExtension_getClasses_basic.phpt @@ -8,7 +8,7 @@ $ext = new ReflectionExtension('reflection'); var_dump($ext->getClasses()); ?> --EXPECT-- -array(19) { +array(22) { ["ReflectionException"]=> object(ReflectionClass)#2 (1) { ["name"]=> @@ -104,4 +104,19 @@ array(19) { ["name"]=> string(19) "ReflectionAttribute" } + ["ReflectionEnum"]=> + object(ReflectionClass)#21 (1) { + ["name"]=> + string(14) "ReflectionEnum" + } + ["ReflectionEnumUnitCase"]=> + object(ReflectionClass)#22 (1) { + ["name"]=> + string(22) "ReflectionEnumUnitCase" + } + ["ReflectionEnumBackedCase"]=> + object(ReflectionClass)#23 (1) { + ["name"]=> + string(24) "ReflectionEnumBackedCase" + } } diff --git a/ext/reflection/tests/ReflectionProperty_setValue_readonly.phpt b/ext/reflection/tests/ReflectionProperty_setValue_readonly.phpt new file mode 100644 index 0000000000..6efea38913 --- /dev/null +++ b/ext/reflection/tests/ReflectionProperty_setValue_readonly.phpt @@ -0,0 +1,23 @@ +--TEST-- +Test ReflectionProperty::setValue() error cases. +--FILE-- +<?php + +enum Foo: int { + case Bar = 0; +} + +$reflection = new ReflectionProperty(Foo::class, 'value'); + +try { + $reflection->setValue(Foo::Bar, 1); +} catch (Error $e) { + echo $e->getMessage() . "\n"; +} + +var_dump(Foo::Bar->value); + +?> +--EXPECT-- +Enum properties are immutable +int(0) diff --git a/ext/reflection/tests/bug36337.phpt b/ext/reflection/tests/bug36337.phpt index ebe64431a7..d4a155bda9 100644 --- a/ext/reflection/tests/bug36337.phpt +++ b/ext/reflection/tests/bug36337.phpt @@ -3,7 +3,7 @@ Reflection Bug #36337 (ReflectionProperty fails to return correct visibility) --FILE-- <?php -abstract class enum { +abstract class enum_ { protected $_values; public function __construct() { @@ -13,7 +13,7 @@ abstract class enum { } -final class myEnum extends enum { +final class myEnum extends enum_ { public $_values = array( 0 => 'No value', ); diff --git a/ext/standard/var.c b/ext/standard/var.c index 06b98b5b9d..bcc6cf5a88 100644 --- a/ext/standard/var.c +++ b/ext/standard/var.c @@ -26,6 +26,7 @@ #include "zend_smart_str.h" #include "basic_functions.h" #include "php_incomplete_class.h" +#include "zend_enum.h" /* }}} */ struct php_serialize_data { @@ -144,7 +145,14 @@ again: } PUTS("}\n"); break; - case IS_OBJECT: + case IS_OBJECT: { + zend_class_entry *ce = Z_OBJCE_P(struc); + if (ce->ce_flags & ZEND_ACC_ENUM) { + zval *case_name_zval = zend_enum_fetch_case_name(Z_OBJ_P(struc)); + php_printf("%senum(%s::%s)\n", COMMON, ZSTR_VAL(ce->name), Z_STRVAL_P(case_name_zval)); + return; + } + if (Z_IS_RECURSIVE_P(struc)) { PUTS("*RECURSION*\n"); return; @@ -183,6 +191,7 @@ again: PUTS("}\n"); Z_UNPROTECT_RECURSION_P(struc); break; + } case IS_RESOURCE: { const char *type_name = zend_rsrc_list_get_rsrc_type(Z_RES_P(struc)); php_printf("%sresource(%d) of type (%s)\n", COMMON, Z_RES_P(struc)->handle, type_name ? type_name : "Unknown"); @@ -568,27 +577,39 @@ again: buffer_append_spaces(buf, level - 1); } + zend_class_entry *ce = Z_OBJCE_P(struc); + bool is_enum = ce->ce_flags & ZEND_ACC_ENUM; + /* stdClass has no __set_state method, but can be casted to */ - if (Z_OBJCE_P(struc) == zend_standard_class_def) { + if (ce == zend_standard_class_def) { smart_str_appendl(buf, "(object) array(\n", 16); } else { - smart_str_append(buf, Z_OBJCE_P(struc)->name); - smart_str_appendl(buf, "::__set_state(array(\n", 21); + smart_str_append(buf, ce->name); + if (is_enum) { + zend_object *zobj = Z_OBJ_P(struc); + zval *case_name_zval = zend_enum_fetch_case_name(zobj); + smart_str_appendl(buf, "::", 2); + smart_str_append(buf, Z_STR_P(case_name_zval)); + } else { + smart_str_appendl(buf, "::__set_state(array(\n", 21); + } } - if (myht) { + if (myht && !is_enum) { ZEND_HASH_FOREACH_KEY_VAL_IND(myht, index, key, val) { php_object_element_export(val, index, key, level, buf); } ZEND_HASH_FOREACH_END(); GC_TRY_UNPROTECT_RECURSION(myht); + } + if (myht) { zend_release_properties(myht); } - if (level > 1) { + if (level > 1 && !is_enum) { buffer_append_spaces(buf, level - 1); } - if (Z_OBJCE_P(struc) == zend_standard_class_def) { + if (ce == zend_standard_class_def) { smart_str_appendc(buf, ')'); - } else { + } else if (!is_enum) { smart_str_appendl(buf, "))", 2); } @@ -1048,6 +1069,23 @@ again: bool incomplete_class; uint32_t count; + if (ce->ce_flags & ZEND_ACC_ENUM) { + PHP_CLASS_ATTRIBUTES; + + zval *case_name_zval = zend_enum_fetch_case_name(Z_OBJ_P(struc)); + + PHP_SET_CLASS_ATTRIBUTES(struc); + smart_str_appendl(buf, "E:", 2); + smart_str_append_unsigned(buf, ZSTR_LEN(class_name) + strlen(":") + Z_STRLEN_P(case_name_zval)); + smart_str_appendl(buf, ":\"", 2); + smart_str_append(buf, class_name); + smart_str_appendc(buf, ':'); + smart_str_append(buf, Z_STR_P(case_name_zval)); + smart_str_appendl(buf, "\";", 2); + PHP_CLEANUP_CLASS_ATTRIBUTES(); + return; + } + if (ce->__serialize) { zval retval, obj; zend_string *key; diff --git a/ext/standard/var_unserializer.re b/ext/standard/var_unserializer.re index a14e34125d..d3a3b91ede 100644 --- a/ext/standard/var_unserializer.re +++ b/ext/standard/var_unserializer.re @@ -1309,6 +1309,86 @@ object ":" uiv ":" ["] { return object_common(UNSERIALIZE_PASSTHRU, elements, has_unserialize); } +"E:" uiv ":" ["] { + if (!var_hash) return 0; + + size_t len = parse_uiv(start + 2); + size_t maxlen = max - YYCURSOR; + if (maxlen < len || len == 0) { + *p = start + 2; + return 0; + } + + char *str = (char *) YYCURSOR; + YYCURSOR += len; + + if (*(YYCURSOR) != '"') { + *p = YYCURSOR; + return 0; + } + if (*(YYCURSOR+1) != ';') { + *p = YYCURSOR+1; + return 0; + } + + char *colon_ptr = memchr(str, ':', len); + if (colon_ptr == NULL) { + php_error_docref(NULL, E_WARNING, "Invalid enum name '%.*s' (missing colon)", (int) len, str); + return 0; + } + size_t colon_pos = colon_ptr - str; + + zend_string *enum_name = zend_string_init(str, colon_pos, 0); + zend_string *case_name = zend_string_init(&str[colon_pos + 1], len - colon_pos - 1, 0); + + if (!zend_is_valid_class_name(enum_name)) { + goto fail; + } + + zend_class_entry *ce = zend_lookup_class(enum_name); + if (!ce) { + php_error_docref(NULL, E_WARNING, "Class '%s' not found", ZSTR_VAL(enum_name)); + goto fail; + } + if (!(ce->ce_flags & ZEND_ACC_ENUM)) { + php_error_docref(NULL, E_WARNING, "Class '%s' is not an enum", ZSTR_VAL(enum_name)); + goto fail; + } + + YYCURSOR += 2; + *p = YYCURSOR; + + zend_class_constant *c = zend_hash_find_ptr(CE_CONSTANTS_TABLE(ce), case_name); + if (!c) { + php_error_docref(NULL, E_WARNING, "Undefined constant %s::%s", ZSTR_VAL(enum_name), ZSTR_VAL(case_name)); + goto fail; + } + + if (!(Z_ACCESS_FLAGS(c->value) & ZEND_CLASS_CONST_IS_CASE)) { + php_error_docref(NULL, E_WARNING, "%s::%s is not an enum case", ZSTR_VAL(enum_name), ZSTR_VAL(case_name)); + goto fail; + } + + zend_string_release_ex(enum_name, 0); + zend_string_release_ex(case_name, 0); + + zval *value = &c->value; + if (Z_TYPE_P(value) == IS_CONSTANT_AST) { + if (zval_update_constant_ex(value, c->ce) == FAILURE) { + return 0; + } + } + ZEND_ASSERT(Z_TYPE_P(value) == IS_OBJECT); + ZVAL_COPY(rval, value); + + return 1; + +fail: + zend_string_release_ex(enum_name, 0); + zend_string_release_ex(case_name, 0); + return 0; +} + "}" { /* this is the case where we have less data than planned */ php_error_docref(NULL, E_NOTICE, "Unexpected end of serialized data"); diff --git a/ext/tokenizer/tokenizer_data.c b/ext/tokenizer/tokenizer_data.c index 10663442c4..96571bb1d7 100644 --- a/ext/tokenizer/tokenizer_data.c +++ b/ext/tokenizer/tokenizer_data.c @@ -134,6 +134,7 @@ void tokenizer_register_constants(INIT_FUNC_ARGS) { REGISTER_LONG_CONSTANT("T_CLASS", T_CLASS, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_TRAIT", T_TRAIT, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_INTERFACE", T_INTERFACE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("T_ENUM", T_ENUM, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_EXTENDS", T_EXTENDS, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_IMPLEMENTS", T_IMPLEMENTS, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_NAMESPACE", T_NAMESPACE, CONST_CS | CONST_PERSISTENT); @@ -283,6 +284,7 @@ char *get_token_type_name(int token_type) case T_CLASS: return "T_CLASS"; case T_TRAIT: return "T_TRAIT"; case T_INTERFACE: return "T_INTERFACE"; + case T_ENUM: return "T_ENUM"; case T_EXTENDS: return "T_EXTENDS"; case T_IMPLEMENTS: return "T_IMPLEMENTS"; case T_NAMESPACE: return "T_NAMESPACE"; diff --git a/tests/classes/interface_member.phpt b/tests/classes/interface_member.phpt index d44b497ca4..04fff625f3 100644 --- a/tests/classes/interface_member.phpt +++ b/tests/classes/interface_member.phpt @@ -8,4 +8,4 @@ interface if_a { } ?> --EXPECTF-- -Fatal error: Interfaces may not include member variables in %s on line %d +Fatal error: Interfaces may not include properties in %s on line %d diff --git a/win32/build/config.w32 b/win32/build/config.w32 index 64053cc056..d8d5671b10 100644 --- a/win32/build/config.w32 +++ b/win32/build/config.w32 @@ -237,7 +237,8 @@ ADD_SOURCES("Zend", "zend_language_parser.c zend_language_scanner.c \ zend_object_handlers.c zend_objects_API.c \ zend_default_classes.c zend_execute.c zend_strtod.c zend_gc.c zend_closures.c zend_weakrefs.c \ zend_float.c zend_string.c zend_generators.c zend_virtual_cwd.c zend_ast.c \ - zend_inheritance.c zend_smart_str.c zend_cpuinfo.c zend_observer.c zend_system_id.c"); + zend_inheritance.c zend_smart_str.c zend_cpuinfo.c zend_observer.c zend_system_id.c \ + zend_enum.c"); ADD_SOURCES("Zend\\Optimizer", "zend_optimizer.c pass1.c pass3.c optimize_func_calls.c block_pass.c optimize_temp_vars_5.c nop_removal.c compact_literals.c zend_cfg.c zend_dfg.c dfa_pass.c zend_ssa.c zend_inference.c zend_func_info.c zend_call_graph.c zend_dump.c escape_analysis.c compact_vars.c dce.c sccp.c scdf.c"); ADD_FLAG("CFLAGS_BD_ZEND", "/D ZEND_ENABLE_STATIC_TSRMLS_CACHE=1"); |