Implement enums

RFC: https://wiki.php.net/rfc/enumerations

Co-authored-by: Nikita Popov <nikita.ppv@gmail.com>

Closes GH-6489.
This commit is contained in:
Ilija Tovilo 2020-06-10 23:10:18 +02:00
parent a6fc427b8c
commit 269c8dac1d
No known key found for this signature in database
GPG key ID: A4F5D403F118200A
164 changed files with 4301 additions and 54 deletions

View file

@ -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;

View file

@ -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"
}
}

View file

@ -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"
}
}

View file

@ -0,0 +1,19 @@
--TEST--
Enum __CLASS__
--FILE--
<?php
enum Foo {
case Bar;
public function printClass()
{
echo __CLASS__ . "\n";
}
}
Foo::Bar->printClass();
?>
--EXPECT--
Foo

View file

@ -0,0 +1,19 @@
--TEST--
Enum __FUNCTION__
--FILE--
<?php
enum Foo {
case Bar;
public function printFunction()
{
echo __FUNCTION__ . "\n";
}
}
Foo::Bar->printFunction();
?>
--EXPECT--
printFunction

View file

@ -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

View file

@ -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"
}

View file

@ -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

View file

@ -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

View file

@ -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;
}())

View file

@ -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)
}

View file

@ -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)
}

View file

@ -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

View file

@ -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

View file

@ -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"

View file

@ -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"

View file

@ -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

View file

@ -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)

View file

@ -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)

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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"
}

View file

@ -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

View file

@ -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
}

View file

@ -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)

View file

@ -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

View file

@ -0,0 +1,14 @@
--TEST--
Enum allows constants
--FILE--
<?php
enum Foo {
const BAR = 'Bar';
}
echo Foo::BAR . "\n";
?>
--EXPECT--
Bar

View file

@ -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)

View file

@ -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)

View file

@ -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

View file

@ -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"
}

View file

@ -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)

View file

@ -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)

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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

View file

@ -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"

View file

@ -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--

View file

@ -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"

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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
)

View file

@ -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)
}

View file

@ -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)

View file

@ -0,0 +1,14 @@
--TEST--
Enum serialize
--FILE--
<?php
enum Foo {
case Bar;
}
echo serialize(Foo::Bar);
?>
--EXPECT--
E:7:"Foo:Bar";

View file

@ -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"

View file

@ -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)

View file

@ -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

View file

@ -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)
}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)
}

Some files were not shown because too many files have changed in this diff Show more