Skip to content

Commit a528053

Browse files
committed
Add support for backed enums (class SortOrder: string { ... })
1 parent 94ed624 commit a528053

File tree

2 files changed

+94
-5
lines changed

2 files changed

+94
-5
lines changed

src/main/php/lang/ast/emit/PHP.class.php

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -363,7 +363,7 @@ protected function emitEnum($result, $enum) {
363363
array_unshift($result->meta, []);
364364
$result->locals= [[], []];
365365

366-
$result->out->write('final class '.$this->declaration($enum->name).' implements \UnitEnum');
366+
$result->out->write('final class '.$this->declaration($enum->name).' implements \\'.($enum->base ? 'BackedEnum' : 'UnitEnum'));
367367
$enum->implements && $result->out->write(', '.implode(', ', $enum->implements));
368368
$result->out->write('{');
369369

@@ -373,8 +373,28 @@ protected function emitEnum($result, $enum) {
373373
$this->emitOne($result, $member);
374374
}
375375

376-
// Name and constructor
377-
$result->out->write('public $name; private function __construct($name) { $this->name= $name; }');
376+
// Constructors
377+
if ($enum->base) {
378+
$result->out->write('public $name, $value;');
379+
$result->out->write('private static $values= [];');
380+
$result->out->write('private function __construct($name, $value) {
381+
$this->name= $name;
382+
$this->value= $value;
383+
self::$values[$value]= $this;
384+
}');
385+
$result->out->write('public static function tryFrom($value) {
386+
return self::$values[$value] ?? null;
387+
}');
388+
$result->out->write('public static function from($value) {
389+
if ($r= self::$values[$value] ?? null) return $r;
390+
throw new \ValueError("Not an enum value: ".\util\Objects::stringOf($value));
391+
}');
392+
} else {
393+
$result->out->write('public $name;');
394+
$result->out->write('private function __construct($name) {
395+
$this->name= $name;
396+
}');
397+
}
378398

379399
// Enum cases
380400
$result->out->write('public static function cases() { return [');
@@ -385,8 +405,16 @@ protected function emitEnum($result, $enum) {
385405

386406
// Initializations
387407
$result->out->write('static function __init() {');
388-
foreach ($cases as $case) {
389-
$result->out->write('self::$'.$case->name.'= new self("'.$case->name.'");');
408+
if ($enum->base) {
409+
foreach ($cases as $case) {
410+
$result->out->write('self::$'.$case->name.'= new self("'.$case->name.'", ');
411+
$this->emitOne($result, $case->expression);
412+
$result->out->write(');');
413+
}
414+
} else {
415+
foreach ($cases as $case) {
416+
$result->out->write('self::$'.$case->name.'= new self("'.$case->name.'");');
417+
}
390418
}
391419
$this->emitInitializations($result, $result->locals[0]);
392420
$this->emitMeta($result, $enum->name, $enum->annotations, $enum->comment);

src/test/php/lang/ast/unittest/emit/EnumTest.class.php

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,67 @@ public static function run($order= self::ASC) {
5151
Assert::equals('ASC', $t->getMethod('run')->invoke(null));
5252
}
5353

54+
#[Test]
55+
public function value_property_of_backed_enum() {
56+
$t= $this->type('enum <T>: string {
57+
case ASC = "asc";
58+
case DESC = "desc";
59+
60+
public static function run() {
61+
return self::DESC->value;
62+
}
63+
}');
64+
65+
Assert::equals('desc', $t->getMethod('run')->invoke(null));
66+
}
67+
68+
#[Test, Values([['asc', 'ASC'], ['desc', 'DESC']])]
69+
public function backed_enum_from($arg, $expected) {
70+
$t= $this->type('enum <T>: string {
71+
case ASC = "asc";
72+
case DESC = "desc";
73+
74+
public static function run($arg) {
75+
return self::from($arg)->name;
76+
}
77+
}');
78+
79+
Assert::equals($expected, $t->getMethod('run')->invoke(null, [$arg]));
80+
}
81+
82+
#[Test]
83+
public function backed_enum_from_nonexistant() {
84+
$t= $this->type('enum <T>: string {
85+
case ASC = "asc";
86+
case DESC = "desc";
87+
88+
public static function run() {
89+
try {
90+
self::from("illegal");
91+
throw new \lang\IllegalStateException("No exception raised");
92+
} catch (\ValueError $expected) {
93+
return $expected->getMessage();
94+
}
95+
}
96+
}');
97+
98+
Assert::equals('Not an enum value: "illegal"', $t->getMethod('run')->invoke(null));
99+
}
100+
101+
#[Test, Values([['asc', 'ASC'], ['desc', 'DESC'], ['illegal', null]])]
102+
public function backed_enum_tryFrom($arg, $expected) {
103+
$t= $this->type('enum <T>: string {
104+
case ASC = "asc";
105+
case DESC = "desc";
106+
107+
public static function run($arg) {
108+
return self::tryFrom($arg)?->name;
109+
}
110+
}');
111+
112+
Assert::equals($expected, $t->getMethod('run')->invoke(null, [$arg]));
113+
}
114+
54115
#[Test]
55116
public function enum_values() {
56117
$t= $this->type('use lang\Enum; enum <T> {

0 commit comments

Comments
 (0)